Completed
Push — master ( 32327c...c614a1 )
by Robin
68:10 queued 24:44
created
lib/private/Share20/Manager.php 1 patch
Indentation   +1810 added lines, -1810 removed lines patch added patch discarded remove patch
@@ -63,1831 +63,1831 @@
 block discarded – undo
63 63
  */
64 64
 class Manager implements IManager {
65 65
 
66
-	private ?IL10N $l;
67
-	private LegacyHooks $legacyHooks;
68
-
69
-	public function __construct(
70
-		private LoggerInterface $logger,
71
-		private IConfig $config,
72
-		private ISecureRandom $secureRandom,
73
-		private IHasher $hasher,
74
-		private IMountManager $mountManager,
75
-		private IGroupManager $groupManager,
76
-		private IFactory $l10nFactory,
77
-		private IProviderFactory $factory,
78
-		private IUserManager $userManager,
79
-		private IRootFolder $rootFolder,
80
-		private IEventDispatcher $dispatcher,
81
-		private IUserSession $userSession,
82
-		private KnownUserService $knownUserService,
83
-		private ShareDisableChecker $shareDisableChecker,
84
-		private IDateTimeZone $dateTimeZone,
85
-		private IAppConfig $appConfig,
86
-	) {
87
-		$this->l = $this->l10nFactory->get('lib');
88
-		// The constructor of LegacyHooks registers the listeners of share events
89
-		// do not remove if those are not properly migrated
90
-		$this->legacyHooks = new LegacyHooks($this->dispatcher);
91
-	}
92
-
93
-	/**
94
-	 * Convert from a full share id to a tuple (providerId, shareId)
95
-	 *
96
-	 * @return string[]
97
-	 */
98
-	private function splitFullId(string $id): array {
99
-		return explode(':', $id, 2);
100
-	}
101
-
102
-	/**
103
-	 * Verify if a password meets all requirements
104
-	 *
105
-	 * @throws HintException
106
-	 */
107
-	protected function verifyPassword(?string $password): void {
108
-		if ($password === null) {
109
-			// No password is set, check if this is allowed.
110
-			if ($this->shareApiLinkEnforcePassword()) {
111
-				throw new \InvalidArgumentException($this->l->t('Passwords are enforced for link and mail shares'));
112
-			}
113
-
114
-			return;
115
-		}
116
-
117
-		// Let others verify the password
118
-		try {
119
-			$event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING);
120
-			$this->dispatcher->dispatchTyped($event);
121
-		} catch (HintException $e) {
122
-			/* Wrap in a 400 bad request error */
123
-			throw new HintException($e->getMessage(), $e->getHint(), 400, $e);
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * Check for generic requirements before creating a share
129
-	 *
130
-	 * @param IShare $share
131
-	 * @throws \InvalidArgumentException
132
-	 * @throws GenericShareException
133
-	 *
134
-	 * @suppress PhanUndeclaredClassMethod
135
-	 */
136
-	protected function generalCreateChecks(IShare $share, bool $isUpdate = false): void {
137
-		if ($share->getShareType() === IShare::TYPE_USER) {
138
-			// We expect a valid user as sharedWith for user shares
139
-			if (!$this->userManager->userExists($share->getSharedWith())) {
140
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid user'));
141
-			}
142
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
143
-			// We expect a valid group as sharedWith for group shares
144
-			if (!$this->groupManager->groupExists($share->getSharedWith())) {
145
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid group'));
146
-			}
147
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
148
-			// No check for TYPE_EMAIL here as we have a recipient for them
149
-			if ($share->getSharedWith() !== null) {
150
-				throw new \InvalidArgumentException($this->l->t('Share recipient should be empty'));
151
-			}
152
-		} elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
153
-			if ($share->getSharedWith() === null) {
154
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
155
-			}
156
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
157
-			if ($share->getSharedWith() === null) {
158
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
159
-			}
160
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
161
-			if ($share->getSharedWith() === null) {
162
-				throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
163
-			}
164
-		} elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
165
-			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
166
-			if ($circle === null) {
167
-				throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid circle'));
168
-			}
169
-		} elseif ($share->getShareType() !== IShare::TYPE_ROOM && $share->getShareType() !== IShare::TYPE_DECK) {
170
-			// We cannot handle other types yet
171
-			throw new \InvalidArgumentException($this->l->t('Unknown share type'));
172
-		}
173
-
174
-		// Verify the initiator of the share is set
175
-		if ($share->getSharedBy() === null) {
176
-			throw new \InvalidArgumentException($this->l->t('Share initiator must be set'));
177
-		}
178
-
179
-		// Cannot share with yourself
180
-		if ($share->getShareType() === IShare::TYPE_USER
181
-			&& $share->getSharedWith() === $share->getSharedBy()) {
182
-			throw new \InvalidArgumentException($this->l->t('Cannot share with yourself'));
183
-		}
184
-
185
-		// The path should be set
186
-		if ($share->getNode() === null) {
187
-			throw new \InvalidArgumentException($this->l->t('Shared path must be set'));
188
-		}
189
-
190
-		// And it should be a file or a folder
191
-		if (!($share->getNode() instanceof \OCP\Files\File)
192
-			&& !($share->getNode() instanceof \OCP\Files\Folder)) {
193
-			throw new \InvalidArgumentException($this->l->t('Shared path must be either a file or a folder'));
194
-		}
195
-
196
-		// And you cannot share your rootfolder
197
-		if ($this->userManager->userExists($share->getSharedBy())) {
198
-			$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
199
-		} else {
200
-			$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
201
-		}
202
-		if ($userFolder->getId() === $share->getNode()->getId()) {
203
-			throw new \InvalidArgumentException($this->l->t('You cannot share your root folder'));
204
-		}
205
-
206
-		// Check if we actually have share permissions
207
-		if (!$share->getNode()->isShareable()) {
208
-			throw new GenericShareException($this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]), code: 404);
209
-		}
210
-
211
-		// Permissions should be set
212
-		if ($share->getPermissions() === null) {
213
-			throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
214
-		}
215
-
216
-		// Permissions must be valid
217
-		if ($share->getPermissions() < 0 || $share->getPermissions() > \OCP\Constants::PERMISSION_ALL) {
218
-			throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
219
-		}
220
-
221
-		// Single file shares should never have delete or create permissions
222
-		if (($share->getNode() instanceof File)
223
-			&& (($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE)) !== 0)) {
224
-			throw new \InvalidArgumentException($this->l->t('File shares cannot have create or delete permissions'));
225
-		}
226
-
227
-		$permissions = 0;
228
-		$nodesForUser = $userFolder->getById($share->getNodeId());
229
-		foreach ($nodesForUser as $node) {
230
-			if ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof MoveableMount) {
231
-				// for the root of non-movable mount, the permissions we see if limited by the mount itself,
232
-				// so we instead use the "raw" permissions from the storage
233
-				$permissions |= $node->getStorage()->getPermissions('');
234
-			} else {
235
-				$permissions |= $node->getPermissions();
236
-			}
237
-		}
238
-
239
-		// Check that we do not share with more permissions than we have
240
-		if ($share->getPermissions() & ~$permissions) {
241
-			$path = $userFolder->getRelativePath($share->getNode()->getPath());
242
-			throw new GenericShareException($this->l->t('Cannot increase permissions of %s', [$path]), code: 404);
243
-		}
244
-
245
-		// Check that read permissions are always set
246
-		// Link shares are allowed to have no read permissions to allow upload to hidden folders
247
-		$noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
248
-			|| $share->getShareType() === IShare::TYPE_EMAIL;
249
-		if (!$noReadPermissionRequired
250
-			&& ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
251
-			throw new \InvalidArgumentException($this->l->t('Shares need at least read permissions'));
252
-		}
253
-
254
-		if ($share->getNode() instanceof \OCP\Files\File) {
255
-			if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
256
-				throw new GenericShareException($this->l->t('Files cannot be shared with delete permissions'));
257
-			}
258
-			if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
259
-				throw new GenericShareException($this->l->t('Files cannot be shared with create permissions'));
260
-			}
261
-		}
262
-	}
263
-
264
-	/**
265
-	 * Validate if the expiration date fits the system settings
266
-	 *
267
-	 * @param IShare $share The share to validate the expiration date of
268
-	 * @return IShare The modified share object
269
-	 * @throws GenericShareException
270
-	 * @throws \InvalidArgumentException
271
-	 * @throws \Exception
272
-	 */
273
-	protected function validateExpirationDateInternal(IShare $share): IShare {
274
-		$isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
275
-
276
-		$expirationDate = $share->getExpirationDate();
277
-
278
-		if ($isRemote) {
279
-			$defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
280
-			$defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
281
-			$configProp = 'remote_defaultExpDays';
282
-			$isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
283
-		} else {
284
-			$defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
285
-			$defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
286
-			$configProp = 'internal_defaultExpDays';
287
-			$isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
288
-		}
289
-
290
-		// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
291
-		// Then skip expiration date validation as null is accepted
292
-		if (!$share->getNoExpirationDate() || $isEnforced) {
293
-			if ($expirationDate !== null) {
294
-				$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
295
-				$expirationDate->setTime(0, 0, 0);
296
-
297
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
298
-				$date->setTime(0, 0, 0);
299
-				if ($date >= $expirationDate) {
300
-					throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
301
-				}
302
-			}
303
-
304
-			// If expiredate is empty set a default one if there is a default
305
-			$fullId = null;
306
-			try {
307
-				$fullId = $share->getFullId();
308
-			} catch (\UnexpectedValueException $e) {
309
-				// This is a new share
310
-			}
311
-
312
-			if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
313
-				$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
314
-				$expirationDate->setTime(0, 0, 0);
315
-				$days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
316
-				if ($days > $defaultExpireDays) {
317
-					$days = $defaultExpireDays;
318
-				}
319
-				$expirationDate->add(new \DateInterval('P' . $days . 'D'));
320
-			}
321
-
322
-			// If we enforce the expiration date check that is does not exceed
323
-			if ($isEnforced) {
324
-				if (empty($expirationDate)) {
325
-					throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
326
-				}
327
-
328
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
329
-				$date->setTime(0, 0, 0);
330
-				$date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
331
-				if ($date < $expirationDate) {
332
-					throw new GenericShareException($this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays), code: 404);
333
-				}
334
-			}
335
-		}
336
-
337
-		$accepted = true;
338
-		$message = '';
339
-		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
340
-			'expirationDate' => &$expirationDate,
341
-			'accepted' => &$accepted,
342
-			'message' => &$message,
343
-			'passwordSet' => $share->getPassword() !== null,
344
-		]);
345
-
346
-		if (!$accepted) {
347
-			throw new \Exception($message);
348
-		}
349
-
350
-		$share->setExpirationDate($expirationDate);
351
-
352
-		return $share;
353
-	}
354
-
355
-	/**
356
-	 * Validate if the expiration date fits the system settings
357
-	 *
358
-	 * @param IShare $share The share to validate the expiration date of
359
-	 * @return IShare The modified share object
360
-	 * @throws GenericShareException
361
-	 * @throws \InvalidArgumentException
362
-	 * @throws \Exception
363
-	 */
364
-	protected function validateExpirationDateLink(IShare $share): IShare {
365
-		$expirationDate = $share->getExpirationDate();
366
-		$isEnforced = $this->shareApiLinkDefaultExpireDateEnforced();
367
-
368
-		// If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
369
-		// Then skip expiration date validation as null is accepted
370
-		if (!($share->getNoExpirationDate() && !$isEnforced)) {
371
-			if ($expirationDate !== null) {
372
-				$expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
373
-				$expirationDate->setTime(0, 0, 0);
374
-
375
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
376
-				$date->setTime(0, 0, 0);
377
-				if ($date >= $expirationDate) {
378
-					throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
379
-				}
380
-			}
381
-
382
-			// If expiredate is empty set a default one if there is a default
383
-			$fullId = null;
384
-			try {
385
-				$fullId = $share->getFullId();
386
-			} catch (\UnexpectedValueException $e) {
387
-				// This is a new share
388
-			}
389
-
390
-			if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
391
-				$expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
392
-				$expirationDate->setTime(0, 0, 0);
393
-
394
-				$days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
395
-				if ($days > $this->shareApiLinkDefaultExpireDays()) {
396
-					$days = $this->shareApiLinkDefaultExpireDays();
397
-				}
398
-				$expirationDate->add(new \DateInterval('P' . $days . 'D'));
399
-			}
400
-
401
-			// If we enforce the expiration date check that is does not exceed
402
-			if ($isEnforced) {
403
-				if (empty($expirationDate)) {
404
-					throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
405
-				}
406
-
407
-				$date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
408
-				$date->setTime(0, 0, 0);
409
-				$date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
410
-				if ($date < $expirationDate) {
411
-					throw new GenericShareException(
412
-						$this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()),
413
-						code: 404,
414
-					);
415
-				}
416
-			}
417
-
418
-		}
419
-
420
-		$accepted = true;
421
-		$message = '';
422
-		\OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
423
-			'expirationDate' => &$expirationDate,
424
-			'accepted' => &$accepted,
425
-			'message' => &$message,
426
-			'passwordSet' => $share->getPassword() !== null,
427
-		]);
428
-
429
-		if (!$accepted) {
430
-			throw new \Exception($message);
431
-		}
432
-
433
-		$share->setExpirationDate($expirationDate);
434
-
435
-		return $share;
436
-	}
437
-
438
-	/**
439
-	 * Check for pre share requirements for user shares
440
-	 *
441
-	 * @throws \Exception
442
-	 */
443
-	protected function userCreateChecks(IShare $share): void {
444
-		// Check if we can share with group members only
445
-		if ($this->shareWithGroupMembersOnly()) {
446
-			$sharedBy = $this->userManager->get($share->getSharedBy());
447
-			$sharedWith = $this->userManager->get($share->getSharedWith());
448
-			// Verify we can share with this user
449
-			$groups = array_intersect(
450
-				$this->groupManager->getUserGroupIds($sharedBy),
451
-				$this->groupManager->getUserGroupIds($sharedWith)
452
-			);
453
-
454
-			// optional excluded groups
455
-			$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
456
-			$groups = array_diff($groups, $excludedGroups);
457
-
458
-			if (empty($groups)) {
459
-				throw new \Exception($this->l->t('Sharing is only allowed with group members'));
460
-			}
461
-		}
462
-
463
-		/*
66
+    private ?IL10N $l;
67
+    private LegacyHooks $legacyHooks;
68
+
69
+    public function __construct(
70
+        private LoggerInterface $logger,
71
+        private IConfig $config,
72
+        private ISecureRandom $secureRandom,
73
+        private IHasher $hasher,
74
+        private IMountManager $mountManager,
75
+        private IGroupManager $groupManager,
76
+        private IFactory $l10nFactory,
77
+        private IProviderFactory $factory,
78
+        private IUserManager $userManager,
79
+        private IRootFolder $rootFolder,
80
+        private IEventDispatcher $dispatcher,
81
+        private IUserSession $userSession,
82
+        private KnownUserService $knownUserService,
83
+        private ShareDisableChecker $shareDisableChecker,
84
+        private IDateTimeZone $dateTimeZone,
85
+        private IAppConfig $appConfig,
86
+    ) {
87
+        $this->l = $this->l10nFactory->get('lib');
88
+        // The constructor of LegacyHooks registers the listeners of share events
89
+        // do not remove if those are not properly migrated
90
+        $this->legacyHooks = new LegacyHooks($this->dispatcher);
91
+    }
92
+
93
+    /**
94
+     * Convert from a full share id to a tuple (providerId, shareId)
95
+     *
96
+     * @return string[]
97
+     */
98
+    private function splitFullId(string $id): array {
99
+        return explode(':', $id, 2);
100
+    }
101
+
102
+    /**
103
+     * Verify if a password meets all requirements
104
+     *
105
+     * @throws HintException
106
+     */
107
+    protected function verifyPassword(?string $password): void {
108
+        if ($password === null) {
109
+            // No password is set, check if this is allowed.
110
+            if ($this->shareApiLinkEnforcePassword()) {
111
+                throw new \InvalidArgumentException($this->l->t('Passwords are enforced for link and mail shares'));
112
+            }
113
+
114
+            return;
115
+        }
116
+
117
+        // Let others verify the password
118
+        try {
119
+            $event = new ValidatePasswordPolicyEvent($password, PasswordContext::SHARING);
120
+            $this->dispatcher->dispatchTyped($event);
121
+        } catch (HintException $e) {
122
+            /* Wrap in a 400 bad request error */
123
+            throw new HintException($e->getMessage(), $e->getHint(), 400, $e);
124
+        }
125
+    }
126
+
127
+    /**
128
+     * Check for generic requirements before creating a share
129
+     *
130
+     * @param IShare $share
131
+     * @throws \InvalidArgumentException
132
+     * @throws GenericShareException
133
+     *
134
+     * @suppress PhanUndeclaredClassMethod
135
+     */
136
+    protected function generalCreateChecks(IShare $share, bool $isUpdate = false): void {
137
+        if ($share->getShareType() === IShare::TYPE_USER) {
138
+            // We expect a valid user as sharedWith for user shares
139
+            if (!$this->userManager->userExists($share->getSharedWith())) {
140
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid user'));
141
+            }
142
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
143
+            // We expect a valid group as sharedWith for group shares
144
+            if (!$this->groupManager->groupExists($share->getSharedWith())) {
145
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid group'));
146
+            }
147
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
148
+            // No check for TYPE_EMAIL here as we have a recipient for them
149
+            if ($share->getSharedWith() !== null) {
150
+                throw new \InvalidArgumentException($this->l->t('Share recipient should be empty'));
151
+            }
152
+        } elseif ($share->getShareType() === IShare::TYPE_EMAIL) {
153
+            if ($share->getSharedWith() === null) {
154
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
155
+            }
156
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE) {
157
+            if ($share->getSharedWith() === null) {
158
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
159
+            }
160
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
161
+            if ($share->getSharedWith() === null) {
162
+                throw new \InvalidArgumentException($this->l->t('Share recipient should not be empty'));
163
+            }
164
+        } elseif ($share->getShareType() === IShare::TYPE_CIRCLE) {
165
+            $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($share->getSharedWith());
166
+            if ($circle === null) {
167
+                throw new \InvalidArgumentException($this->l->t('Share recipient is not a valid circle'));
168
+            }
169
+        } elseif ($share->getShareType() !== IShare::TYPE_ROOM && $share->getShareType() !== IShare::TYPE_DECK) {
170
+            // We cannot handle other types yet
171
+            throw new \InvalidArgumentException($this->l->t('Unknown share type'));
172
+        }
173
+
174
+        // Verify the initiator of the share is set
175
+        if ($share->getSharedBy() === null) {
176
+            throw new \InvalidArgumentException($this->l->t('Share initiator must be set'));
177
+        }
178
+
179
+        // Cannot share with yourself
180
+        if ($share->getShareType() === IShare::TYPE_USER
181
+            && $share->getSharedWith() === $share->getSharedBy()) {
182
+            throw new \InvalidArgumentException($this->l->t('Cannot share with yourself'));
183
+        }
184
+
185
+        // The path should be set
186
+        if ($share->getNode() === null) {
187
+            throw new \InvalidArgumentException($this->l->t('Shared path must be set'));
188
+        }
189
+
190
+        // And it should be a file or a folder
191
+        if (!($share->getNode() instanceof \OCP\Files\File)
192
+            && !($share->getNode() instanceof \OCP\Files\Folder)) {
193
+            throw new \InvalidArgumentException($this->l->t('Shared path must be either a file or a folder'));
194
+        }
195
+
196
+        // And you cannot share your rootfolder
197
+        if ($this->userManager->userExists($share->getSharedBy())) {
198
+            $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
199
+        } else {
200
+            $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
201
+        }
202
+        if ($userFolder->getId() === $share->getNode()->getId()) {
203
+            throw new \InvalidArgumentException($this->l->t('You cannot share your root folder'));
204
+        }
205
+
206
+        // Check if we actually have share permissions
207
+        if (!$share->getNode()->isShareable()) {
208
+            throw new GenericShareException($this->l->t('You are not allowed to share %s', [$share->getNode()->getName()]), code: 404);
209
+        }
210
+
211
+        // Permissions should be set
212
+        if ($share->getPermissions() === null) {
213
+            throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
214
+        }
215
+
216
+        // Permissions must be valid
217
+        if ($share->getPermissions() < 0 || $share->getPermissions() > \OCP\Constants::PERMISSION_ALL) {
218
+            throw new \InvalidArgumentException($this->l->t('Valid permissions are required for sharing'));
219
+        }
220
+
221
+        // Single file shares should never have delete or create permissions
222
+        if (($share->getNode() instanceof File)
223
+            && (($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE)) !== 0)) {
224
+            throw new \InvalidArgumentException($this->l->t('File shares cannot have create or delete permissions'));
225
+        }
226
+
227
+        $permissions = 0;
228
+        $nodesForUser = $userFolder->getById($share->getNodeId());
229
+        foreach ($nodesForUser as $node) {
230
+            if ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof MoveableMount) {
231
+                // for the root of non-movable mount, the permissions we see if limited by the mount itself,
232
+                // so we instead use the "raw" permissions from the storage
233
+                $permissions |= $node->getStorage()->getPermissions('');
234
+            } else {
235
+                $permissions |= $node->getPermissions();
236
+            }
237
+        }
238
+
239
+        // Check that we do not share with more permissions than we have
240
+        if ($share->getPermissions() & ~$permissions) {
241
+            $path = $userFolder->getRelativePath($share->getNode()->getPath());
242
+            throw new GenericShareException($this->l->t('Cannot increase permissions of %s', [$path]), code: 404);
243
+        }
244
+
245
+        // Check that read permissions are always set
246
+        // Link shares are allowed to have no read permissions to allow upload to hidden folders
247
+        $noReadPermissionRequired = $share->getShareType() === IShare::TYPE_LINK
248
+            || $share->getShareType() === IShare::TYPE_EMAIL;
249
+        if (!$noReadPermissionRequired
250
+            && ($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) {
251
+            throw new \InvalidArgumentException($this->l->t('Shares need at least read permissions'));
252
+        }
253
+
254
+        if ($share->getNode() instanceof \OCP\Files\File) {
255
+            if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) {
256
+                throw new GenericShareException($this->l->t('Files cannot be shared with delete permissions'));
257
+            }
258
+            if ($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE) {
259
+                throw new GenericShareException($this->l->t('Files cannot be shared with create permissions'));
260
+            }
261
+        }
262
+    }
263
+
264
+    /**
265
+     * Validate if the expiration date fits the system settings
266
+     *
267
+     * @param IShare $share The share to validate the expiration date of
268
+     * @return IShare The modified share object
269
+     * @throws GenericShareException
270
+     * @throws \InvalidArgumentException
271
+     * @throws \Exception
272
+     */
273
+    protected function validateExpirationDateInternal(IShare $share): IShare {
274
+        $isRemote = $share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP;
275
+
276
+        $expirationDate = $share->getExpirationDate();
277
+
278
+        if ($isRemote) {
279
+            $defaultExpireDate = $this->shareApiRemoteDefaultExpireDate();
280
+            $defaultExpireDays = $this->shareApiRemoteDefaultExpireDays();
281
+            $configProp = 'remote_defaultExpDays';
282
+            $isEnforced = $this->shareApiRemoteDefaultExpireDateEnforced();
283
+        } else {
284
+            $defaultExpireDate = $this->shareApiInternalDefaultExpireDate();
285
+            $defaultExpireDays = $this->shareApiInternalDefaultExpireDays();
286
+            $configProp = 'internal_defaultExpDays';
287
+            $isEnforced = $this->shareApiInternalDefaultExpireDateEnforced();
288
+        }
289
+
290
+        // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
291
+        // Then skip expiration date validation as null is accepted
292
+        if (!$share->getNoExpirationDate() || $isEnforced) {
293
+            if ($expirationDate !== null) {
294
+                $expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
295
+                $expirationDate->setTime(0, 0, 0);
296
+
297
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
298
+                $date->setTime(0, 0, 0);
299
+                if ($date >= $expirationDate) {
300
+                    throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
301
+                }
302
+            }
303
+
304
+            // If expiredate is empty set a default one if there is a default
305
+            $fullId = null;
306
+            try {
307
+                $fullId = $share->getFullId();
308
+            } catch (\UnexpectedValueException $e) {
309
+                // This is a new share
310
+            }
311
+
312
+            if ($fullId === null && $expirationDate === null && $defaultExpireDate) {
313
+                $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
314
+                $expirationDate->setTime(0, 0, 0);
315
+                $days = (int)$this->config->getAppValue('core', $configProp, (string)$defaultExpireDays);
316
+                if ($days > $defaultExpireDays) {
317
+                    $days = $defaultExpireDays;
318
+                }
319
+                $expirationDate->add(new \DateInterval('P' . $days . 'D'));
320
+            }
321
+
322
+            // If we enforce the expiration date check that is does not exceed
323
+            if ($isEnforced) {
324
+                if (empty($expirationDate)) {
325
+                    throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
326
+                }
327
+
328
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
329
+                $date->setTime(0, 0, 0);
330
+                $date->add(new \DateInterval('P' . $defaultExpireDays . 'D'));
331
+                if ($date < $expirationDate) {
332
+                    throw new GenericShareException($this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $defaultExpireDays), code: 404);
333
+                }
334
+            }
335
+        }
336
+
337
+        $accepted = true;
338
+        $message = '';
339
+        \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
340
+            'expirationDate' => &$expirationDate,
341
+            'accepted' => &$accepted,
342
+            'message' => &$message,
343
+            'passwordSet' => $share->getPassword() !== null,
344
+        ]);
345
+
346
+        if (!$accepted) {
347
+            throw new \Exception($message);
348
+        }
349
+
350
+        $share->setExpirationDate($expirationDate);
351
+
352
+        return $share;
353
+    }
354
+
355
+    /**
356
+     * Validate if the expiration date fits the system settings
357
+     *
358
+     * @param IShare $share The share to validate the expiration date of
359
+     * @return IShare The modified share object
360
+     * @throws GenericShareException
361
+     * @throws \InvalidArgumentException
362
+     * @throws \Exception
363
+     */
364
+    protected function validateExpirationDateLink(IShare $share): IShare {
365
+        $expirationDate = $share->getExpirationDate();
366
+        $isEnforced = $this->shareApiLinkDefaultExpireDateEnforced();
367
+
368
+        // If $expirationDate is falsy, noExpirationDate is true and expiration not enforced
369
+        // Then skip expiration date validation as null is accepted
370
+        if (!($share->getNoExpirationDate() && !$isEnforced)) {
371
+            if ($expirationDate !== null) {
372
+                $expirationDate->setTimezone($this->dateTimeZone->getTimeZone());
373
+                $expirationDate->setTime(0, 0, 0);
374
+
375
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
376
+                $date->setTime(0, 0, 0);
377
+                if ($date >= $expirationDate) {
378
+                    throw new GenericShareException($this->l->t('Expiration date is in the past'), code: 404);
379
+                }
380
+            }
381
+
382
+            // If expiredate is empty set a default one if there is a default
383
+            $fullId = null;
384
+            try {
385
+                $fullId = $share->getFullId();
386
+            } catch (\UnexpectedValueException $e) {
387
+                // This is a new share
388
+            }
389
+
390
+            if ($fullId === null && $expirationDate === null && $this->shareApiLinkDefaultExpireDate()) {
391
+                $expirationDate = new \DateTime('now', $this->dateTimeZone->getTimeZone());
392
+                $expirationDate->setTime(0, 0, 0);
393
+
394
+                $days = (int)$this->config->getAppValue('core', 'link_defaultExpDays', (string)$this->shareApiLinkDefaultExpireDays());
395
+                if ($days > $this->shareApiLinkDefaultExpireDays()) {
396
+                    $days = $this->shareApiLinkDefaultExpireDays();
397
+                }
398
+                $expirationDate->add(new \DateInterval('P' . $days . 'D'));
399
+            }
400
+
401
+            // If we enforce the expiration date check that is does not exceed
402
+            if ($isEnforced) {
403
+                if (empty($expirationDate)) {
404
+                    throw new \InvalidArgumentException($this->l->t('Expiration date is enforced'));
405
+                }
406
+
407
+                $date = new \DateTime('now', $this->dateTimeZone->getTimeZone());
408
+                $date->setTime(0, 0, 0);
409
+                $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D'));
410
+                if ($date < $expirationDate) {
411
+                    throw new GenericShareException(
412
+                        $this->l->n('Cannot set expiration date more than %n day in the future', 'Cannot set expiration date more than %n days in the future', $this->shareApiLinkDefaultExpireDays()),
413
+                        code: 404,
414
+                    );
415
+                }
416
+            }
417
+
418
+        }
419
+
420
+        $accepted = true;
421
+        $message = '';
422
+        \OCP\Util::emitHook('\OC\Share', 'verifyExpirationDate', [
423
+            'expirationDate' => &$expirationDate,
424
+            'accepted' => &$accepted,
425
+            'message' => &$message,
426
+            'passwordSet' => $share->getPassword() !== null,
427
+        ]);
428
+
429
+        if (!$accepted) {
430
+            throw new \Exception($message);
431
+        }
432
+
433
+        $share->setExpirationDate($expirationDate);
434
+
435
+        return $share;
436
+    }
437
+
438
+    /**
439
+     * Check for pre share requirements for user shares
440
+     *
441
+     * @throws \Exception
442
+     */
443
+    protected function userCreateChecks(IShare $share): void {
444
+        // Check if we can share with group members only
445
+        if ($this->shareWithGroupMembersOnly()) {
446
+            $sharedBy = $this->userManager->get($share->getSharedBy());
447
+            $sharedWith = $this->userManager->get($share->getSharedWith());
448
+            // Verify we can share with this user
449
+            $groups = array_intersect(
450
+                $this->groupManager->getUserGroupIds($sharedBy),
451
+                $this->groupManager->getUserGroupIds($sharedWith)
452
+            );
453
+
454
+            // optional excluded groups
455
+            $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
456
+            $groups = array_diff($groups, $excludedGroups);
457
+
458
+            if (empty($groups)) {
459
+                throw new \Exception($this->l->t('Sharing is only allowed with group members'));
460
+            }
461
+        }
462
+
463
+        /*
464 464
 		 * TODO: Could be costly, fix
465 465
 		 *
466 466
 		 * Also this is not what we want in the future.. then we want to squash identical shares.
467 467
 		 */
468
-		$provider = $this->factory->getProviderForType(IShare::TYPE_USER);
469
-		$existingShares = $provider->getSharesByPath($share->getNode());
470
-		foreach ($existingShares as $existingShare) {
471
-			// Ignore if it is the same share
472
-			try {
473
-				if ($existingShare->getFullId() === $share->getFullId()) {
474
-					continue;
475
-				}
476
-			} catch (\UnexpectedValueException $e) {
477
-				//Shares are not identical
478
-			}
479
-
480
-			// Identical share already exists
481
-			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
482
-				throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
483
-			}
484
-
485
-			// The share is already shared with this user via a group share
486
-			if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
487
-				$group = $this->groupManager->get($existingShare->getSharedWith());
488
-				if (!is_null($group)) {
489
-					$user = $this->userManager->get($share->getSharedWith());
490
-
491
-					if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
492
-						throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
493
-					}
494
-				}
495
-			}
496
-		}
497
-	}
498
-
499
-	/**
500
-	 * Check for pre share requirements for group shares
501
-	 *
502
-	 * @throws \Exception
503
-	 */
504
-	protected function groupCreateChecks(IShare $share): void {
505
-		// Verify group shares are allowed
506
-		if (!$this->allowGroupSharing()) {
507
-			throw new \Exception($this->l->t('Group sharing is now allowed'));
508
-		}
509
-
510
-		// Verify if the user can share with this group
511
-		if ($this->shareWithGroupMembersOnly()) {
512
-			$sharedBy = $this->userManager->get($share->getSharedBy());
513
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
514
-
515
-			// optional excluded groups
516
-			$excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
517
-			if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
518
-				throw new \Exception($this->l->t('Sharing is only allowed within your own groups'));
519
-			}
520
-		}
521
-
522
-		/*
468
+        $provider = $this->factory->getProviderForType(IShare::TYPE_USER);
469
+        $existingShares = $provider->getSharesByPath($share->getNode());
470
+        foreach ($existingShares as $existingShare) {
471
+            // Ignore if it is the same share
472
+            try {
473
+                if ($existingShare->getFullId() === $share->getFullId()) {
474
+                    continue;
475
+                }
476
+            } catch (\UnexpectedValueException $e) {
477
+                //Shares are not identical
478
+            }
479
+
480
+            // Identical share already exists
481
+            if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
482
+                throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
483
+            }
484
+
485
+            // The share is already shared with this user via a group share
486
+            if ($existingShare->getShareType() === IShare::TYPE_GROUP) {
487
+                $group = $this->groupManager->get($existingShare->getSharedWith());
488
+                if (!is_null($group)) {
489
+                    $user = $this->userManager->get($share->getSharedWith());
490
+
491
+                    if ($group->inGroup($user) && $existingShare->getShareOwner() !== $share->getShareOwner()) {
492
+                        throw new AlreadySharedException($this->l->t('Sharing %s failed, because this item is already shared with the account %s', [$share->getNode()->getName(), $share->getSharedWithDisplayName()]), $existingShare);
493
+                    }
494
+                }
495
+            }
496
+        }
497
+    }
498
+
499
+    /**
500
+     * Check for pre share requirements for group shares
501
+     *
502
+     * @throws \Exception
503
+     */
504
+    protected function groupCreateChecks(IShare $share): void {
505
+        // Verify group shares are allowed
506
+        if (!$this->allowGroupSharing()) {
507
+            throw new \Exception($this->l->t('Group sharing is now allowed'));
508
+        }
509
+
510
+        // Verify if the user can share with this group
511
+        if ($this->shareWithGroupMembersOnly()) {
512
+            $sharedBy = $this->userManager->get($share->getSharedBy());
513
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
514
+
515
+            // optional excluded groups
516
+            $excludedGroups = $this->shareWithGroupMembersOnlyExcludeGroupsList();
517
+            if (is_null($sharedWith) || in_array($share->getSharedWith(), $excludedGroups) || !$sharedWith->inGroup($sharedBy)) {
518
+                throw new \Exception($this->l->t('Sharing is only allowed within your own groups'));
519
+            }
520
+        }
521
+
522
+        /*
523 523
 		 * TODO: Could be costly, fix
524 524
 		 *
525 525
 		 * Also this is not what we want in the future.. then we want to squash identical shares.
526 526
 		 */
527
-		$provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
528
-		$existingShares = $provider->getSharesByPath($share->getNode());
529
-		foreach ($existingShares as $existingShare) {
530
-			try {
531
-				if ($existingShare->getFullId() === $share->getFullId()) {
532
-					continue;
533
-				}
534
-			} catch (\UnexpectedValueException $e) {
535
-				//It is a new share so just continue
536
-			}
537
-
538
-			if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
539
-				throw new AlreadySharedException($this->l->t('Path is already shared with this group'), $existingShare);
540
-			}
541
-		}
542
-	}
543
-
544
-	/**
545
-	 * Check for pre share requirements for link shares
546
-	 *
547
-	 * @throws \Exception
548
-	 */
549
-	protected function linkCreateChecks(IShare $share): void {
550
-		// Are link shares allowed?
551
-		if (!$this->shareApiAllowLinks()) {
552
-			throw new \Exception($this->l->t('Link sharing is not allowed'));
553
-		}
554
-
555
-		// Check if public upload is allowed
556
-		if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()
557
-			&& ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
558
-			throw new \InvalidArgumentException($this->l->t('Public upload is not allowed'));
559
-		}
560
-	}
561
-
562
-	/**
563
-	 * To make sure we don't get invisible link shares we set the parent
564
-	 * of a link if it is a reshare. This is a quick word around
565
-	 * until we can properly display multiple link shares in the UI
566
-	 *
567
-	 * See: https://github.com/owncloud/core/issues/22295
568
-	 *
569
-	 * FIXME: Remove once multiple link shares can be properly displayed
570
-	 */
571
-	protected function setLinkParent(IShare $share): void {
572
-		$storage = $share->getNode()->getStorage();
573
-		if ($storage->instanceOfStorage(SharedStorage::class)) {
574
-			/** @var \OCA\Files_Sharing\SharedStorage $storage */
575
-			$share->setParent((int)$storage->getShareId());
576
-		}
577
-	}
578
-
579
-	protected function pathCreateChecks(Node $path): void {
580
-		// Make sure that we do not share a path that contains a shared mountpoint
581
-		if ($path instanceof \OCP\Files\Folder) {
582
-			$mounts = $this->mountManager->findIn($path->getPath());
583
-			foreach ($mounts as $mount) {
584
-				if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
585
-					// Using a flat sharing model ensures the file owner can always see who has access.
586
-					// Allowing parent folder sharing would require tracking inherited access, which adds complexity
587
-					// and hurts performance/scalability.
588
-					// So we forbid sharing a parent folder of a share you received.
589
-					throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
590
-				}
591
-			}
592
-		}
593
-	}
594
-
595
-	/**
596
-	 * Check if the user that is sharing can actually share
597
-	 *
598
-	 * @throws \Exception
599
-	 */
600
-	protected function canShare(IShare $share): void {
601
-		if (!$this->shareApiEnabled()) {
602
-			throw new \Exception($this->l->t('Sharing is disabled'));
603
-		}
604
-
605
-		if ($this->sharingDisabledForUser($share->getSharedBy())) {
606
-			throw new \Exception($this->l->t('Sharing is disabled for you'));
607
-		}
608
-	}
609
-
610
-	#[Override]
611
-	public function createShare(IShare $share): IShare {
612
-		// TODO: handle link share permissions or check them
613
-		$this->canShare($share);
614
-
615
-		$this->generalCreateChecks($share);
616
-
617
-		// Verify if there are any issues with the path
618
-		$this->pathCreateChecks($share->getNode());
619
-
620
-		/*
527
+        $provider = $this->factory->getProviderForType(IShare::TYPE_GROUP);
528
+        $existingShares = $provider->getSharesByPath($share->getNode());
529
+        foreach ($existingShares as $existingShare) {
530
+            try {
531
+                if ($existingShare->getFullId() === $share->getFullId()) {
532
+                    continue;
533
+                }
534
+            } catch (\UnexpectedValueException $e) {
535
+                //It is a new share so just continue
536
+            }
537
+
538
+            if ($existingShare->getSharedWith() === $share->getSharedWith() && $existingShare->getShareType() === $share->getShareType()) {
539
+                throw new AlreadySharedException($this->l->t('Path is already shared with this group'), $existingShare);
540
+            }
541
+        }
542
+    }
543
+
544
+    /**
545
+     * Check for pre share requirements for link shares
546
+     *
547
+     * @throws \Exception
548
+     */
549
+    protected function linkCreateChecks(IShare $share): void {
550
+        // Are link shares allowed?
551
+        if (!$this->shareApiAllowLinks()) {
552
+            throw new \Exception($this->l->t('Link sharing is not allowed'));
553
+        }
554
+
555
+        // Check if public upload is allowed
556
+        if ($share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()
557
+            && ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE))) {
558
+            throw new \InvalidArgumentException($this->l->t('Public upload is not allowed'));
559
+        }
560
+    }
561
+
562
+    /**
563
+     * To make sure we don't get invisible link shares we set the parent
564
+     * of a link if it is a reshare. This is a quick word around
565
+     * until we can properly display multiple link shares in the UI
566
+     *
567
+     * See: https://github.com/owncloud/core/issues/22295
568
+     *
569
+     * FIXME: Remove once multiple link shares can be properly displayed
570
+     */
571
+    protected function setLinkParent(IShare $share): void {
572
+        $storage = $share->getNode()->getStorage();
573
+        if ($storage->instanceOfStorage(SharedStorage::class)) {
574
+            /** @var \OCA\Files_Sharing\SharedStorage $storage */
575
+            $share->setParent((int)$storage->getShareId());
576
+        }
577
+    }
578
+
579
+    protected function pathCreateChecks(Node $path): void {
580
+        // Make sure that we do not share a path that contains a shared mountpoint
581
+        if ($path instanceof \OCP\Files\Folder) {
582
+            $mounts = $this->mountManager->findIn($path->getPath());
583
+            foreach ($mounts as $mount) {
584
+                if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) {
585
+                    // Using a flat sharing model ensures the file owner can always see who has access.
586
+                    // Allowing parent folder sharing would require tracking inherited access, which adds complexity
587
+                    // and hurts performance/scalability.
588
+                    // So we forbid sharing a parent folder of a share you received.
589
+                    throw new \InvalidArgumentException($this->l->t('You cannot share a folder that contains other shares'));
590
+                }
591
+            }
592
+        }
593
+    }
594
+
595
+    /**
596
+     * Check if the user that is sharing can actually share
597
+     *
598
+     * @throws \Exception
599
+     */
600
+    protected function canShare(IShare $share): void {
601
+        if (!$this->shareApiEnabled()) {
602
+            throw new \Exception($this->l->t('Sharing is disabled'));
603
+        }
604
+
605
+        if ($this->sharingDisabledForUser($share->getSharedBy())) {
606
+            throw new \Exception($this->l->t('Sharing is disabled for you'));
607
+        }
608
+    }
609
+
610
+    #[Override]
611
+    public function createShare(IShare $share): IShare {
612
+        // TODO: handle link share permissions or check them
613
+        $this->canShare($share);
614
+
615
+        $this->generalCreateChecks($share);
616
+
617
+        // Verify if there are any issues with the path
618
+        $this->pathCreateChecks($share->getNode());
619
+
620
+        /*
621 621
 		 * On creation of a share the owner is always the owner of the path
622 622
 		 * Except for mounted federated shares.
623 623
 		 */
624
-		$storage = $share->getNode()->getStorage();
625
-		if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
626
-			$parent = $share->getNode()->getParent();
627
-			while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
628
-				$parent = $parent->getParent();
629
-			}
630
-			$share->setShareOwner($parent->getOwner()->getUID());
631
-		} else {
632
-			if ($share->getNode()->getOwner()) {
633
-				$share->setShareOwner($share->getNode()->getOwner()->getUID());
634
-			} else {
635
-				$share->setShareOwner($share->getSharedBy());
636
-			}
637
-		}
638
-
639
-		try {
640
-			// Verify share type
641
-			if ($share->getShareType() === IShare::TYPE_USER) {
642
-				$this->userCreateChecks($share);
643
-
644
-				// Verify the expiration date
645
-				$share = $this->validateExpirationDateInternal($share);
646
-			} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
647
-				$this->groupCreateChecks($share);
648
-
649
-				// Verify the expiration date
650
-				$share = $this->validateExpirationDateInternal($share);
651
-			} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
652
-				// Verify the expiration date
653
-				$share = $this->validateExpirationDateInternal($share);
654
-			} elseif ($share->getShareType() === IShare::TYPE_LINK
655
-				|| $share->getShareType() === IShare::TYPE_EMAIL) {
656
-				$this->linkCreateChecks($share);
657
-				$this->setLinkParent($share);
658
-
659
-				$token = $this->generateToken();
660
-				// Set the unique token
661
-				$share->setToken($token);
662
-
663
-				// Verify the expiration date
664
-				$share = $this->validateExpirationDateLink($share);
665
-
666
-				// Verify the password
667
-				$this->verifyPassword($share->getPassword());
668
-
669
-				// If a password is set. Hash it!
670
-				if ($share->getShareType() === IShare::TYPE_LINK
671
-					&& $share->getPassword() !== null) {
672
-					$share->setPassword($this->hasher->hash($share->getPassword()));
673
-				}
674
-			}
675
-
676
-			// Cannot share with the owner
677
-			if ($share->getShareType() === IShare::TYPE_USER
678
-				&& $share->getSharedWith() === $share->getShareOwner()) {
679
-				throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
680
-			}
681
-
682
-			// Generate the target
683
-			$shareFolder = $this->config->getSystemValue('share_folder', '/');
684
-			if ($share->getShareType() === IShare::TYPE_USER) {
685
-				$allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
686
-				if ($allowCustomShareFolder) {
687
-					$shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $shareFolder);
688
-				}
689
-			}
690
-
691
-			$target = $shareFolder . '/' . $share->getNode()->getName();
692
-			$target = \OC\Files\Filesystem::normalizePath($target);
693
-			$share->setTarget($target);
694
-
695
-			// Pre share event
696
-			$event = new Share\Events\BeforeShareCreatedEvent($share);
697
-			$this->dispatchEvent($event, 'before share created');
698
-			if ($event->isPropagationStopped() && $event->getError()) {
699
-				throw new \Exception($event->getError());
700
-			}
701
-
702
-			$oldShare = $share;
703
-			$provider = $this->factory->getProviderForType($share->getShareType());
704
-			$share = $provider->create($share);
705
-
706
-			// Reuse the node we already have
707
-			$share->setNode($oldShare->getNode());
708
-
709
-			// Reset the target if it is null for the new share
710
-			if ($share->getTarget() === '') {
711
-				$share->setTarget($target);
712
-			}
713
-		} catch (AlreadySharedException $e) {
714
-			// If a share for the same target already exists, dont create a new one,
715
-			// but do trigger the hooks and notifications again
716
-			$oldShare = $share;
717
-
718
-			// Reuse the node we already have
719
-			$share = $e->getExistingShare();
720
-			$share->setNode($oldShare->getNode());
721
-		}
722
-
723
-		// Post share event
724
-		$this->dispatchEvent(new ShareCreatedEvent($share), 'share created');
725
-
726
-		// Send email if needed
727
-		if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
728
-			if ($share->getMailSend()) {
729
-				$provider = $this->factory->getProviderForType($share->getShareType());
730
-				if ($provider instanceof IShareProviderWithNotification) {
731
-					$provider->sendMailNotification($share);
732
-				} else {
733
-					$this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
734
-				}
735
-			} else {
736
-				$this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
737
-			}
738
-		} else {
739
-			$this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
740
-		}
741
-
742
-		return $share;
743
-	}
744
-
745
-	#[Override]
746
-	public function updateShare(IShare $share, bool $onlyValid = true): IShare {
747
-		$expirationDateUpdated = false;
748
-
749
-		$this->canShare($share);
750
-
751
-		try {
752
-			$originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
753
-		} catch (\UnexpectedValueException $e) {
754
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
755
-		}
756
-
757
-		// We cannot change the share type!
758
-		if ($share->getShareType() !== $originalShare->getShareType()) {
759
-			throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
760
-		}
761
-
762
-		// We can only change the recipient on user shares
763
-		if ($share->getSharedWith() !== $originalShare->getSharedWith()
764
-			&& $share->getShareType() !== IShare::TYPE_USER) {
765
-			throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
766
-		}
767
-
768
-		// Cannot share with the owner
769
-		if ($share->getShareType() === IShare::TYPE_USER
770
-			&& $share->getSharedWith() === $share->getShareOwner()) {
771
-			throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
772
-		}
773
-
774
-		$this->generalCreateChecks($share, true);
775
-
776
-		if ($share->getShareType() === IShare::TYPE_USER) {
777
-			$this->userCreateChecks($share);
778
-
779
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
780
-				// Verify the expiration date
781
-				$this->validateExpirationDateInternal($share);
782
-				$expirationDateUpdated = true;
783
-			}
784
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
785
-			$this->groupCreateChecks($share);
786
-
787
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
788
-				// Verify the expiration date
789
-				$this->validateExpirationDateInternal($share);
790
-				$expirationDateUpdated = true;
791
-			}
792
-		} elseif ($share->getShareType() === IShare::TYPE_LINK
793
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
794
-			$this->linkCreateChecks($share);
795
-
796
-			// The new password is not set again if it is the same as the old
797
-			// one, unless when switching from sending by Talk to sending by
798
-			// mail.
799
-			$plainTextPassword = $share->getPassword();
800
-			$updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
801
-
802
-			/**
803
-			 * Cannot enable the getSendPasswordByTalk if there is no password set
804
-			 */
805
-			if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
806
-				throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
807
-			}
808
-
809
-			/**
810
-			 * If we're in a mail share, we need to force a password change
811
-			 * as either the user is not aware of the password or is already (received by mail)
812
-			 * Thus the SendPasswordByTalk feature would not make sense
813
-			 */
814
-			if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
815
-				if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
816
-					throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
817
-				}
818
-				if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
819
-					throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
820
-				}
821
-			}
822
-
823
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
824
-				// Verify the expiration date
825
-				$this->validateExpirationDateLink($share);
826
-				$expirationDateUpdated = true;
827
-			}
828
-		} elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
829
-			if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
830
-				// Verify the expiration date
831
-				$this->validateExpirationDateInternal($share);
832
-				$expirationDateUpdated = true;
833
-			}
834
-		}
835
-
836
-		$this->pathCreateChecks($share->getNode());
837
-
838
-		// Now update the share!
839
-		$provider = $this->factory->getProviderForType($share->getShareType());
840
-		if ($share->getShareType() === IShare::TYPE_EMAIL) {
841
-			/** @var ShareByMailProvider $provider */
842
-			$share = $provider->update($share, $plainTextPassword);
843
-		} else {
844
-			$share = $provider->update($share);
845
-		}
846
-
847
-		if ($expirationDateUpdated === true) {
848
-			\OC_Hook::emit(Share::class, 'post_set_expiration_date', [
849
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
850
-				'itemSource' => $share->getNode()->getId(),
851
-				'date' => $share->getExpirationDate(),
852
-				'uidOwner' => $share->getSharedBy(),
853
-			]);
854
-		}
855
-
856
-		if ($share->getPassword() !== $originalShare->getPassword()) {
857
-			\OC_Hook::emit(Share::class, 'post_update_password', [
858
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
859
-				'itemSource' => $share->getNode()->getId(),
860
-				'uidOwner' => $share->getSharedBy(),
861
-				'token' => $share->getToken(),
862
-				'disabled' => is_null($share->getPassword()),
863
-			]);
864
-		}
865
-
866
-		if ($share->getPermissions() !== $originalShare->getPermissions()) {
867
-			if ($this->userManager->userExists($share->getShareOwner())) {
868
-				$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
869
-			} else {
870
-				$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
871
-			}
872
-			\OC_Hook::emit(Share::class, 'post_update_permissions', [
873
-				'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
874
-				'itemSource' => $share->getNode()->getId(),
875
-				'shareType' => $share->getShareType(),
876
-				'shareWith' => $share->getSharedWith(),
877
-				'uidOwner' => $share->getSharedBy(),
878
-				'permissions' => $share->getPermissions(),
879
-				'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
880
-				'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
881
-			]);
882
-		}
883
-
884
-		return $share;
885
-	}
886
-
887
-	#[Override]
888
-	public function acceptShare(IShare $share, string $recipientId): IShare {
889
-		[$providerId,] = $this->splitFullId($share->getFullId());
890
-		$provider = $this->factory->getProvider($providerId);
891
-
892
-		if (!($provider instanceof IShareProviderSupportsAccept)) {
893
-			throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
894
-		}
895
-		/** @var IShareProvider&IShareProviderSupportsAccept $provider */
896
-		$provider->acceptShare($share, $recipientId);
897
-
898
-		$event = new ShareAcceptedEvent($share);
899
-		$this->dispatchEvent($event, 'share accepted');
900
-
901
-		return $share;
902
-	}
903
-
904
-	/**
905
-	 * Updates the password of the given share if it is not the same as the
906
-	 * password of the original share.
907
-	 *
908
-	 * @param IShare $share the share to update its password.
909
-	 * @param IShare $originalShare the original share to compare its
910
-	 *                              password with.
911
-	 * @return bool whether the password was updated or not.
912
-	 */
913
-	private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare): bool {
914
-		$passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword())
915
-			&& (($share->getPassword() !== null && $originalShare->getPassword() === null)
916
-				|| ($share->getPassword() === null && $originalShare->getPassword() !== null)
917
-				|| ($share->getPassword() !== null && $originalShare->getPassword() !== null
918
-					&& !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
919
-
920
-		// Password updated.
921
-		if ($passwordsAreDifferent) {
922
-			// Verify the password
923
-			$this->verifyPassword($share->getPassword());
924
-
925
-			// If a password is set. Hash it!
926
-			if (!empty($share->getPassword())) {
927
-				$share->setPassword($this->hasher->hash($share->getPassword()));
928
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
929
-					// Shares shared by email have temporary passwords
930
-					$this->setSharePasswordExpirationTime($share);
931
-				}
932
-
933
-				return true;
934
-			} else {
935
-				// Empty string and null are seen as NOT password protected
936
-				$share->setPassword(null);
937
-				if ($share->getShareType() === IShare::TYPE_EMAIL) {
938
-					$share->setPasswordExpirationTime(null);
939
-				}
940
-				return true;
941
-			}
942
-		} else {
943
-			// Reset the password to the original one, as it is either the same
944
-			// as the "new" password or a hashed version of it.
945
-			$share->setPassword($originalShare->getPassword());
946
-		}
947
-
948
-		return false;
949
-	}
950
-
951
-	/**
952
-	 * Set the share's password expiration time
953
-	 */
954
-	private function setSharePasswordExpirationTime(IShare $share): void {
955
-		if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
956
-			// Sets password expiration date to NULL
957
-			$share->setPasswordExpirationTime();
958
-			return;
959
-		}
960
-		// Sets password expiration date
961
-		$expirationTime = null;
962
-		$now = new \DateTime();
963
-		$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
964
-		$expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
965
-		$share->setPasswordExpirationTime($expirationTime);
966
-	}
967
-
968
-
969
-	/**
970
-	 * Delete all the children of this share
971
-	 *
972
-	 * @param IShare $share
973
-	 * @return list<IShare> List of deleted shares
974
-	 */
975
-	protected function deleteChildren(IShare $share): array {
976
-		$deletedShares = [];
977
-
978
-		$provider = $this->factory->getProviderForType($share->getShareType());
979
-
980
-		foreach ($provider->getChildren($share) as $child) {
981
-			$this->dispatchEvent(new BeforeShareDeletedEvent($child), 'before share deleted');
982
-
983
-			$deletedChildren = $this->deleteChildren($child);
984
-			$deletedShares = array_merge($deletedShares, $deletedChildren);
985
-
986
-			$provider->delete($child);
987
-			$this->dispatchEvent(new ShareDeletedEvent($child), 'share deleted');
988
-			$deletedShares[] = $child;
989
-		}
990
-
991
-		return $deletedShares;
992
-	}
993
-
994
-	/** Promote re-shares into direct shares so that target user keeps access */
995
-	protected function promoteReshares(IShare $share): void {
996
-		try {
997
-			$node = $share->getNode();
998
-		} catch (NotFoundException) {
999
-			/* Skip if node not found */
1000
-			return;
1001
-		}
1002
-
1003
-		$userIds = [];
1004
-
1005
-		if ($share->getShareType() === IShare::TYPE_USER) {
1006
-			$userIds[] = $share->getSharedWith();
1007
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1008
-			$group = $this->groupManager->get($share->getSharedWith());
1009
-			$users = $group?->getUsers() ?? [];
1010
-
1011
-			foreach ($users as $user) {
1012
-				/* Skip share owner */
1013
-				if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1014
-					continue;
1015
-				}
1016
-				$userIds[] = $user->getUID();
1017
-			}
1018
-		} else {
1019
-			/* We only support user and group shares */
1020
-			return;
1021
-		}
1022
-
1023
-		$reshareRecords = [];
1024
-		$shareTypes = [
1025
-			IShare::TYPE_GROUP,
1026
-			IShare::TYPE_USER,
1027
-			IShare::TYPE_LINK,
1028
-			IShare::TYPE_REMOTE,
1029
-			IShare::TYPE_EMAIL,
1030
-		];
1031
-
1032
-		foreach ($userIds as $userId) {
1033
-			foreach ($shareTypes as $shareType) {
1034
-				try {
1035
-					$provider = $this->factory->getProviderForType($shareType);
1036
-				} catch (ProviderException $e) {
1037
-					continue;
1038
-				}
1039
-
1040
-				if ($node instanceof Folder) {
1041
-					/* We need to get all shares by this user to get subshares */
1042
-					$shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1043
-
1044
-					foreach ($shares as $share) {
1045
-						try {
1046
-							$path = $share->getNode()->getPath();
1047
-						} catch (NotFoundException) {
1048
-							/* Ignore share of non-existing node */
1049
-							continue;
1050
-						}
1051
-						if ($node->getRelativePath($path) !== null) {
1052
-							/* If relative path is not null it means the shared node is the same or in a subfolder */
1053
-							$reshareRecords[] = $share;
1054
-						}
1055
-					}
1056
-				} else {
1057
-					$shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1058
-					foreach ($shares as $child) {
1059
-						$reshareRecords[] = $child;
1060
-					}
1061
-				}
1062
-			}
1063
-		}
1064
-
1065
-		foreach ($reshareRecords as $child) {
1066
-			try {
1067
-				/* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1068
-				$this->generalCreateChecks($child);
1069
-			} catch (GenericShareException $e) {
1070
-				/* The check is invalid, promote it to a direct share from the sharer of parent share */
1071
-				$this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1072
-				try {
1073
-					$child->setSharedBy($share->getSharedBy());
1074
-					$this->updateShare($child);
1075
-				} catch (GenericShareException|\InvalidArgumentException $e) {
1076
-					$this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1077
-				}
1078
-			}
1079
-		}
1080
-	}
1081
-
1082
-	#[Override]
1083
-	public function deleteShare(IShare $share): void {
1084
-		try {
1085
-			$share->getFullId();
1086
-		} catch (\UnexpectedValueException $e) {
1087
-			throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1088
-		}
1089
-
1090
-		$this->dispatchEvent(new BeforeShareDeletedEvent($share), 'before share deleted');
1091
-
1092
-		// Get all children and delete them as well
1093
-		$this->deleteChildren($share);
1094
-
1095
-		// Do the actual delete
1096
-		$provider = $this->factory->getProviderForType($share->getShareType());
1097
-		$provider->delete($share);
1098
-
1099
-		$this->dispatchEvent(new ShareDeletedEvent($share), 'share deleted');
1100
-
1101
-		// Promote reshares of the deleted share
1102
-		$this->promoteReshares($share);
1103
-	}
1104
-
1105
-	#[Override]
1106
-	public function deleteFromSelf(IShare $share, string $recipientId): void {
1107
-		[$providerId,] = $this->splitFullId($share->getFullId());
1108
-		$provider = $this->factory->getProvider($providerId);
1109
-
1110
-		$provider->deleteFromSelf($share, $recipientId);
1111
-		$event = new ShareDeletedFromSelfEvent($share);
1112
-		$this->dispatchEvent($event, 'leave share');
1113
-	}
1114
-
1115
-	#[Override]
1116
-	public function restoreShare(IShare $share, string $recipientId): IShare {
1117
-		[$providerId,] = $this->splitFullId($share->getFullId());
1118
-		$provider = $this->factory->getProvider($providerId);
1119
-
1120
-		return $provider->restore($share, $recipientId);
1121
-	}
1122
-
1123
-	#[Override]
1124
-	public function moveShare(IShare $share, string $recipientId): IShare {
1125
-		if ($share->getShareType() === IShare::TYPE_LINK
1126
-			|| $share->getShareType() === IShare::TYPE_EMAIL) {
1127
-			throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1128
-		}
1129
-
1130
-		if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1131
-			throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1132
-		}
1133
-
1134
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
1135
-			$sharedWith = $this->groupManager->get($share->getSharedWith());
1136
-			if (is_null($sharedWith)) {
1137
-				throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1138
-			}
1139
-			$recipient = $this->userManager->get($recipientId);
1140
-			if (!$sharedWith->inGroup($recipient)) {
1141
-				throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1142
-			}
1143
-		}
1144
-
1145
-		[$providerId,] = $this->splitFullId($share->getFullId());
1146
-		$provider = $this->factory->getProvider($providerId);
1147
-
1148
-		return $provider->move($share, $recipientId);
1149
-	}
1150
-
1151
-	#[Override]
1152
-	public function getSharesInFolder($userId, Folder $node, bool $reshares = false, bool $shallow = true): array {
1153
-		$providers = $this->factory->getAllProviders();
1154
-		if (!$shallow) {
1155
-			throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1156
-		}
1157
-
1158
-		$isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1159
-
1160
-		$shares = [];
1161
-		foreach ($providers as $provider) {
1162
-			if ($isOwnerless) {
1163
-				// If the provider does not implement the additional interface,
1164
-				// we lack a performant way of querying all shares and therefore ignore the provider.
1165
-				if ($provider instanceof IShareProviderSupportsAllSharesInFolder) {
1166
-					foreach ($provider->getAllSharesInFolder($node) as $fid => $data) {
1167
-						$shares[$fid] ??= [];
1168
-						$shares[$fid] = array_merge($shares[$fid], $data);
1169
-					}
1170
-				}
1171
-			} else {
1172
-				foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1173
-					$shares[$fid] ??= [];
1174
-					$shares[$fid] = array_merge($shares[$fid], $data);
1175
-				}
1176
-			}
1177
-		}
1178
-
1179
-		return $shares;
1180
-	}
1181
-
1182
-	#[Override]
1183
-	public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array {
1184
-		if ($path !== null
1185
-			&& !($path instanceof \OCP\Files\File)
1186
-			&& !($path instanceof \OCP\Files\Folder)) {
1187
-			throw new \InvalidArgumentException($this->l->t('Invalid path'));
1188
-		}
1189
-
1190
-		try {
1191
-			$provider = $this->factory->getProviderForType($shareType);
1192
-		} catch (ProviderException $e) {
1193
-			return [];
1194
-		}
1195
-
1196
-		if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1197
-			$shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1198
-		} else {
1199
-			$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1200
-		}
1201
-
1202
-		/*
624
+        $storage = $share->getNode()->getStorage();
625
+        if ($storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
626
+            $parent = $share->getNode()->getParent();
627
+            while ($parent->getStorage()->instanceOfStorage('OCA\Files_Sharing\External\Storage')) {
628
+                $parent = $parent->getParent();
629
+            }
630
+            $share->setShareOwner($parent->getOwner()->getUID());
631
+        } else {
632
+            if ($share->getNode()->getOwner()) {
633
+                $share->setShareOwner($share->getNode()->getOwner()->getUID());
634
+            } else {
635
+                $share->setShareOwner($share->getSharedBy());
636
+            }
637
+        }
638
+
639
+        try {
640
+            // Verify share type
641
+            if ($share->getShareType() === IShare::TYPE_USER) {
642
+                $this->userCreateChecks($share);
643
+
644
+                // Verify the expiration date
645
+                $share = $this->validateExpirationDateInternal($share);
646
+            } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
647
+                $this->groupCreateChecks($share);
648
+
649
+                // Verify the expiration date
650
+                $share = $this->validateExpirationDateInternal($share);
651
+            } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
652
+                // Verify the expiration date
653
+                $share = $this->validateExpirationDateInternal($share);
654
+            } elseif ($share->getShareType() === IShare::TYPE_LINK
655
+                || $share->getShareType() === IShare::TYPE_EMAIL) {
656
+                $this->linkCreateChecks($share);
657
+                $this->setLinkParent($share);
658
+
659
+                $token = $this->generateToken();
660
+                // Set the unique token
661
+                $share->setToken($token);
662
+
663
+                // Verify the expiration date
664
+                $share = $this->validateExpirationDateLink($share);
665
+
666
+                // Verify the password
667
+                $this->verifyPassword($share->getPassword());
668
+
669
+                // If a password is set. Hash it!
670
+                if ($share->getShareType() === IShare::TYPE_LINK
671
+                    && $share->getPassword() !== null) {
672
+                    $share->setPassword($this->hasher->hash($share->getPassword()));
673
+                }
674
+            }
675
+
676
+            // Cannot share with the owner
677
+            if ($share->getShareType() === IShare::TYPE_USER
678
+                && $share->getSharedWith() === $share->getShareOwner()) {
679
+                throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
680
+            }
681
+
682
+            // Generate the target
683
+            $shareFolder = $this->config->getSystemValue('share_folder', '/');
684
+            if ($share->getShareType() === IShare::TYPE_USER) {
685
+                $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
686
+                if ($allowCustomShareFolder) {
687
+                    $shareFolder = $this->config->getUserValue($share->getSharedWith(), Application::APP_ID, 'share_folder', $shareFolder);
688
+                }
689
+            }
690
+
691
+            $target = $shareFolder . '/' . $share->getNode()->getName();
692
+            $target = \OC\Files\Filesystem::normalizePath($target);
693
+            $share->setTarget($target);
694
+
695
+            // Pre share event
696
+            $event = new Share\Events\BeforeShareCreatedEvent($share);
697
+            $this->dispatchEvent($event, 'before share created');
698
+            if ($event->isPropagationStopped() && $event->getError()) {
699
+                throw new \Exception($event->getError());
700
+            }
701
+
702
+            $oldShare = $share;
703
+            $provider = $this->factory->getProviderForType($share->getShareType());
704
+            $share = $provider->create($share);
705
+
706
+            // Reuse the node we already have
707
+            $share->setNode($oldShare->getNode());
708
+
709
+            // Reset the target if it is null for the new share
710
+            if ($share->getTarget() === '') {
711
+                $share->setTarget($target);
712
+            }
713
+        } catch (AlreadySharedException $e) {
714
+            // If a share for the same target already exists, dont create a new one,
715
+            // but do trigger the hooks and notifications again
716
+            $oldShare = $share;
717
+
718
+            // Reuse the node we already have
719
+            $share = $e->getExistingShare();
720
+            $share->setNode($oldShare->getNode());
721
+        }
722
+
723
+        // Post share event
724
+        $this->dispatchEvent(new ShareCreatedEvent($share), 'share created');
725
+
726
+        // Send email if needed
727
+        if ($this->config->getSystemValueBool('sharing.enable_share_mail', true)) {
728
+            if ($share->getMailSend()) {
729
+                $provider = $this->factory->getProviderForType($share->getShareType());
730
+                if ($provider instanceof IShareProviderWithNotification) {
731
+                    $provider->sendMailNotification($share);
732
+                } else {
733
+                    $this->logger->debug('Share notification not sent because the provider does not support it.', ['app' => 'share']);
734
+                }
735
+            } else {
736
+                $this->logger->debug('Share notification not sent because mailsend is false.', ['app' => 'share']);
737
+            }
738
+        } else {
739
+            $this->logger->debug('Share notification not sent because sharing notification emails is disabled.', ['app' => 'share']);
740
+        }
741
+
742
+        return $share;
743
+    }
744
+
745
+    #[Override]
746
+    public function updateShare(IShare $share, bool $onlyValid = true): IShare {
747
+        $expirationDateUpdated = false;
748
+
749
+        $this->canShare($share);
750
+
751
+        try {
752
+            $originalShare = $this->getShareById($share->getFullId(), onlyValid: $onlyValid);
753
+        } catch (\UnexpectedValueException $e) {
754
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
755
+        }
756
+
757
+        // We cannot change the share type!
758
+        if ($share->getShareType() !== $originalShare->getShareType()) {
759
+            throw new \InvalidArgumentException($this->l->t('Cannot change share type'));
760
+        }
761
+
762
+        // We can only change the recipient on user shares
763
+        if ($share->getSharedWith() !== $originalShare->getSharedWith()
764
+            && $share->getShareType() !== IShare::TYPE_USER) {
765
+            throw new \InvalidArgumentException($this->l->t('Can only update recipient on user shares'));
766
+        }
767
+
768
+        // Cannot share with the owner
769
+        if ($share->getShareType() === IShare::TYPE_USER
770
+            && $share->getSharedWith() === $share->getShareOwner()) {
771
+            throw new \InvalidArgumentException($this->l->t('Cannot share with the share owner'));
772
+        }
773
+
774
+        $this->generalCreateChecks($share, true);
775
+
776
+        if ($share->getShareType() === IShare::TYPE_USER) {
777
+            $this->userCreateChecks($share);
778
+
779
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
780
+                // Verify the expiration date
781
+                $this->validateExpirationDateInternal($share);
782
+                $expirationDateUpdated = true;
783
+            }
784
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
785
+            $this->groupCreateChecks($share);
786
+
787
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
788
+                // Verify the expiration date
789
+                $this->validateExpirationDateInternal($share);
790
+                $expirationDateUpdated = true;
791
+            }
792
+        } elseif ($share->getShareType() === IShare::TYPE_LINK
793
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
794
+            $this->linkCreateChecks($share);
795
+
796
+            // The new password is not set again if it is the same as the old
797
+            // one, unless when switching from sending by Talk to sending by
798
+            // mail.
799
+            $plainTextPassword = $share->getPassword();
800
+            $updatedPassword = $this->updateSharePasswordIfNeeded($share, $originalShare);
801
+
802
+            /**
803
+             * Cannot enable the getSendPasswordByTalk if there is no password set
804
+             */
805
+            if (empty($plainTextPassword) && $share->getSendPasswordByTalk()) {
806
+                throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk with an empty password'));
807
+            }
808
+
809
+            /**
810
+             * If we're in a mail share, we need to force a password change
811
+             * as either the user is not aware of the password or is already (received by mail)
812
+             * Thus the SendPasswordByTalk feature would not make sense
813
+             */
814
+            if (!$updatedPassword && $share->getShareType() === IShare::TYPE_EMAIL) {
815
+                if (!$originalShare->getSendPasswordByTalk() && $share->getSendPasswordByTalk()) {
816
+                    throw new \InvalidArgumentException($this->l->t('Cannot enable sending the password by Talk without setting a new password'));
817
+                }
818
+                if ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()) {
819
+                    throw new \InvalidArgumentException($this->l->t('Cannot disable sending the password by Talk without setting a new password'));
820
+                }
821
+            }
822
+
823
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
824
+                // Verify the expiration date
825
+                $this->validateExpirationDateLink($share);
826
+                $expirationDateUpdated = true;
827
+            }
828
+        } elseif ($share->getShareType() === IShare::TYPE_REMOTE || $share->getShareType() === IShare::TYPE_REMOTE_GROUP) {
829
+            if ($share->getExpirationDate() != $originalShare->getExpirationDate()) {
830
+                // Verify the expiration date
831
+                $this->validateExpirationDateInternal($share);
832
+                $expirationDateUpdated = true;
833
+            }
834
+        }
835
+
836
+        $this->pathCreateChecks($share->getNode());
837
+
838
+        // Now update the share!
839
+        $provider = $this->factory->getProviderForType($share->getShareType());
840
+        if ($share->getShareType() === IShare::TYPE_EMAIL) {
841
+            /** @var ShareByMailProvider $provider */
842
+            $share = $provider->update($share, $plainTextPassword);
843
+        } else {
844
+            $share = $provider->update($share);
845
+        }
846
+
847
+        if ($expirationDateUpdated === true) {
848
+            \OC_Hook::emit(Share::class, 'post_set_expiration_date', [
849
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
850
+                'itemSource' => $share->getNode()->getId(),
851
+                'date' => $share->getExpirationDate(),
852
+                'uidOwner' => $share->getSharedBy(),
853
+            ]);
854
+        }
855
+
856
+        if ($share->getPassword() !== $originalShare->getPassword()) {
857
+            \OC_Hook::emit(Share::class, 'post_update_password', [
858
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
859
+                'itemSource' => $share->getNode()->getId(),
860
+                'uidOwner' => $share->getSharedBy(),
861
+                'token' => $share->getToken(),
862
+                'disabled' => is_null($share->getPassword()),
863
+            ]);
864
+        }
865
+
866
+        if ($share->getPermissions() !== $originalShare->getPermissions()) {
867
+            if ($this->userManager->userExists($share->getShareOwner())) {
868
+                $userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
869
+            } else {
870
+                $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
871
+            }
872
+            \OC_Hook::emit(Share::class, 'post_update_permissions', [
873
+                'itemType' => $share->getNode() instanceof \OCP\Files\File ? 'file' : 'folder',
874
+                'itemSource' => $share->getNode()->getId(),
875
+                'shareType' => $share->getShareType(),
876
+                'shareWith' => $share->getSharedWith(),
877
+                'uidOwner' => $share->getSharedBy(),
878
+                'permissions' => $share->getPermissions(),
879
+                'attributes' => $share->getAttributes() !== null ? $share->getAttributes()->toArray() : null,
880
+                'path' => $userFolder->getRelativePath($share->getNode()->getPath()),
881
+            ]);
882
+        }
883
+
884
+        return $share;
885
+    }
886
+
887
+    #[Override]
888
+    public function acceptShare(IShare $share, string $recipientId): IShare {
889
+        [$providerId,] = $this->splitFullId($share->getFullId());
890
+        $provider = $this->factory->getProvider($providerId);
891
+
892
+        if (!($provider instanceof IShareProviderSupportsAccept)) {
893
+            throw new \InvalidArgumentException($this->l->t('Share provider does not support accepting'));
894
+        }
895
+        /** @var IShareProvider&IShareProviderSupportsAccept $provider */
896
+        $provider->acceptShare($share, $recipientId);
897
+
898
+        $event = new ShareAcceptedEvent($share);
899
+        $this->dispatchEvent($event, 'share accepted');
900
+
901
+        return $share;
902
+    }
903
+
904
+    /**
905
+     * Updates the password of the given share if it is not the same as the
906
+     * password of the original share.
907
+     *
908
+     * @param IShare $share the share to update its password.
909
+     * @param IShare $originalShare the original share to compare its
910
+     *                              password with.
911
+     * @return bool whether the password was updated or not.
912
+     */
913
+    private function updateSharePasswordIfNeeded(IShare $share, IShare $originalShare): bool {
914
+        $passwordsAreDifferent = ($share->getPassword() !== $originalShare->getPassword())
915
+            && (($share->getPassword() !== null && $originalShare->getPassword() === null)
916
+                || ($share->getPassword() === null && $originalShare->getPassword() !== null)
917
+                || ($share->getPassword() !== null && $originalShare->getPassword() !== null
918
+                    && !$this->hasher->verify($share->getPassword(), $originalShare->getPassword())));
919
+
920
+        // Password updated.
921
+        if ($passwordsAreDifferent) {
922
+            // Verify the password
923
+            $this->verifyPassword($share->getPassword());
924
+
925
+            // If a password is set. Hash it!
926
+            if (!empty($share->getPassword())) {
927
+                $share->setPassword($this->hasher->hash($share->getPassword()));
928
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
929
+                    // Shares shared by email have temporary passwords
930
+                    $this->setSharePasswordExpirationTime($share);
931
+                }
932
+
933
+                return true;
934
+            } else {
935
+                // Empty string and null are seen as NOT password protected
936
+                $share->setPassword(null);
937
+                if ($share->getShareType() === IShare::TYPE_EMAIL) {
938
+                    $share->setPasswordExpirationTime(null);
939
+                }
940
+                return true;
941
+            }
942
+        } else {
943
+            // Reset the password to the original one, as it is either the same
944
+            // as the "new" password or a hashed version of it.
945
+            $share->setPassword($originalShare->getPassword());
946
+        }
947
+
948
+        return false;
949
+    }
950
+
951
+    /**
952
+     * Set the share's password expiration time
953
+     */
954
+    private function setSharePasswordExpirationTime(IShare $share): void {
955
+        if (!$this->config->getSystemValueBool('sharing.enable_mail_link_password_expiration', false)) {
956
+            // Sets password expiration date to NULL
957
+            $share->setPasswordExpirationTime();
958
+            return;
959
+        }
960
+        // Sets password expiration date
961
+        $expirationTime = null;
962
+        $now = new \DateTime();
963
+        $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
964
+        $expirationTime = $now->add(new \DateInterval('PT' . $expirationInterval . 'S'));
965
+        $share->setPasswordExpirationTime($expirationTime);
966
+    }
967
+
968
+
969
+    /**
970
+     * Delete all the children of this share
971
+     *
972
+     * @param IShare $share
973
+     * @return list<IShare> List of deleted shares
974
+     */
975
+    protected function deleteChildren(IShare $share): array {
976
+        $deletedShares = [];
977
+
978
+        $provider = $this->factory->getProviderForType($share->getShareType());
979
+
980
+        foreach ($provider->getChildren($share) as $child) {
981
+            $this->dispatchEvent(new BeforeShareDeletedEvent($child), 'before share deleted');
982
+
983
+            $deletedChildren = $this->deleteChildren($child);
984
+            $deletedShares = array_merge($deletedShares, $deletedChildren);
985
+
986
+            $provider->delete($child);
987
+            $this->dispatchEvent(new ShareDeletedEvent($child), 'share deleted');
988
+            $deletedShares[] = $child;
989
+        }
990
+
991
+        return $deletedShares;
992
+    }
993
+
994
+    /** Promote re-shares into direct shares so that target user keeps access */
995
+    protected function promoteReshares(IShare $share): void {
996
+        try {
997
+            $node = $share->getNode();
998
+        } catch (NotFoundException) {
999
+            /* Skip if node not found */
1000
+            return;
1001
+        }
1002
+
1003
+        $userIds = [];
1004
+
1005
+        if ($share->getShareType() === IShare::TYPE_USER) {
1006
+            $userIds[] = $share->getSharedWith();
1007
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1008
+            $group = $this->groupManager->get($share->getSharedWith());
1009
+            $users = $group?->getUsers() ?? [];
1010
+
1011
+            foreach ($users as $user) {
1012
+                /* Skip share owner */
1013
+                if ($user->getUID() === $share->getShareOwner() || $user->getUID() === $share->getSharedBy()) {
1014
+                    continue;
1015
+                }
1016
+                $userIds[] = $user->getUID();
1017
+            }
1018
+        } else {
1019
+            /* We only support user and group shares */
1020
+            return;
1021
+        }
1022
+
1023
+        $reshareRecords = [];
1024
+        $shareTypes = [
1025
+            IShare::TYPE_GROUP,
1026
+            IShare::TYPE_USER,
1027
+            IShare::TYPE_LINK,
1028
+            IShare::TYPE_REMOTE,
1029
+            IShare::TYPE_EMAIL,
1030
+        ];
1031
+
1032
+        foreach ($userIds as $userId) {
1033
+            foreach ($shareTypes as $shareType) {
1034
+                try {
1035
+                    $provider = $this->factory->getProviderForType($shareType);
1036
+                } catch (ProviderException $e) {
1037
+                    continue;
1038
+                }
1039
+
1040
+                if ($node instanceof Folder) {
1041
+                    /* We need to get all shares by this user to get subshares */
1042
+                    $shares = $provider->getSharesBy($userId, $shareType, null, false, -1, 0);
1043
+
1044
+                    foreach ($shares as $share) {
1045
+                        try {
1046
+                            $path = $share->getNode()->getPath();
1047
+                        } catch (NotFoundException) {
1048
+                            /* Ignore share of non-existing node */
1049
+                            continue;
1050
+                        }
1051
+                        if ($node->getRelativePath($path) !== null) {
1052
+                            /* If relative path is not null it means the shared node is the same or in a subfolder */
1053
+                            $reshareRecords[] = $share;
1054
+                        }
1055
+                    }
1056
+                } else {
1057
+                    $shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
1058
+                    foreach ($shares as $child) {
1059
+                        $reshareRecords[] = $child;
1060
+                    }
1061
+                }
1062
+            }
1063
+        }
1064
+
1065
+        foreach ($reshareRecords as $child) {
1066
+            try {
1067
+                /* Check if the share is still valid (means the resharer still has access to the file through another mean) */
1068
+                $this->generalCreateChecks($child);
1069
+            } catch (GenericShareException $e) {
1070
+                /* The check is invalid, promote it to a direct share from the sharer of parent share */
1071
+                $this->logger->debug('Promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1072
+                try {
1073
+                    $child->setSharedBy($share->getSharedBy());
1074
+                    $this->updateShare($child);
1075
+                } catch (GenericShareException|\InvalidArgumentException $e) {
1076
+                    $this->logger->warning('Failed to promote reshare because of exception ' . $e->getMessage(), ['exception' => $e, 'fullId' => $child->getFullId()]);
1077
+                }
1078
+            }
1079
+        }
1080
+    }
1081
+
1082
+    #[Override]
1083
+    public function deleteShare(IShare $share): void {
1084
+        try {
1085
+            $share->getFullId();
1086
+        } catch (\UnexpectedValueException $e) {
1087
+            throw new \InvalidArgumentException($this->l->t('Share does not have a full ID'));
1088
+        }
1089
+
1090
+        $this->dispatchEvent(new BeforeShareDeletedEvent($share), 'before share deleted');
1091
+
1092
+        // Get all children and delete them as well
1093
+        $this->deleteChildren($share);
1094
+
1095
+        // Do the actual delete
1096
+        $provider = $this->factory->getProviderForType($share->getShareType());
1097
+        $provider->delete($share);
1098
+
1099
+        $this->dispatchEvent(new ShareDeletedEvent($share), 'share deleted');
1100
+
1101
+        // Promote reshares of the deleted share
1102
+        $this->promoteReshares($share);
1103
+    }
1104
+
1105
+    #[Override]
1106
+    public function deleteFromSelf(IShare $share, string $recipientId): void {
1107
+        [$providerId,] = $this->splitFullId($share->getFullId());
1108
+        $provider = $this->factory->getProvider($providerId);
1109
+
1110
+        $provider->deleteFromSelf($share, $recipientId);
1111
+        $event = new ShareDeletedFromSelfEvent($share);
1112
+        $this->dispatchEvent($event, 'leave share');
1113
+    }
1114
+
1115
+    #[Override]
1116
+    public function restoreShare(IShare $share, string $recipientId): IShare {
1117
+        [$providerId,] = $this->splitFullId($share->getFullId());
1118
+        $provider = $this->factory->getProvider($providerId);
1119
+
1120
+        return $provider->restore($share, $recipientId);
1121
+    }
1122
+
1123
+    #[Override]
1124
+    public function moveShare(IShare $share, string $recipientId): IShare {
1125
+        if ($share->getShareType() === IShare::TYPE_LINK
1126
+            || $share->getShareType() === IShare::TYPE_EMAIL) {
1127
+            throw new \InvalidArgumentException($this->l->t('Cannot change target of link share'));
1128
+        }
1129
+
1130
+        if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
1131
+            throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1132
+        }
1133
+
1134
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
1135
+            $sharedWith = $this->groupManager->get($share->getSharedWith());
1136
+            if (is_null($sharedWith)) {
1137
+                throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
1138
+            }
1139
+            $recipient = $this->userManager->get($recipientId);
1140
+            if (!$sharedWith->inGroup($recipient)) {
1141
+                throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
1142
+            }
1143
+        }
1144
+
1145
+        [$providerId,] = $this->splitFullId($share->getFullId());
1146
+        $provider = $this->factory->getProvider($providerId);
1147
+
1148
+        return $provider->move($share, $recipientId);
1149
+    }
1150
+
1151
+    #[Override]
1152
+    public function getSharesInFolder($userId, Folder $node, bool $reshares = false, bool $shallow = true): array {
1153
+        $providers = $this->factory->getAllProviders();
1154
+        if (!$shallow) {
1155
+            throw new \Exception('non-shallow getSharesInFolder is no longer supported');
1156
+        }
1157
+
1158
+        $isOwnerless = $node->getMountPoint() instanceof IShareOwnerlessMount;
1159
+
1160
+        $shares = [];
1161
+        foreach ($providers as $provider) {
1162
+            if ($isOwnerless) {
1163
+                // If the provider does not implement the additional interface,
1164
+                // we lack a performant way of querying all shares and therefore ignore the provider.
1165
+                if ($provider instanceof IShareProviderSupportsAllSharesInFolder) {
1166
+                    foreach ($provider->getAllSharesInFolder($node) as $fid => $data) {
1167
+                        $shares[$fid] ??= [];
1168
+                        $shares[$fid] = array_merge($shares[$fid], $data);
1169
+                    }
1170
+                }
1171
+            } else {
1172
+                foreach ($provider->getSharesInFolder($userId, $node, $reshares) as $fid => $data) {
1173
+                    $shares[$fid] ??= [];
1174
+                    $shares[$fid] = array_merge($shares[$fid], $data);
1175
+                }
1176
+            }
1177
+        }
1178
+
1179
+        return $shares;
1180
+    }
1181
+
1182
+    #[Override]
1183
+    public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array {
1184
+        if ($path !== null
1185
+            && !($path instanceof \OCP\Files\File)
1186
+            && !($path instanceof \OCP\Files\Folder)) {
1187
+            throw new \InvalidArgumentException($this->l->t('Invalid path'));
1188
+        }
1189
+
1190
+        try {
1191
+            $provider = $this->factory->getProviderForType($shareType);
1192
+        } catch (ProviderException $e) {
1193
+            return [];
1194
+        }
1195
+
1196
+        if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1197
+            $shares = array_filter($provider->getSharesByPath($path), static fn (IShare $share) => $share->getShareType() === $shareType);
1198
+        } else {
1199
+            $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1200
+        }
1201
+
1202
+        /*
1203 1203
 		 * Work around so we don't return expired shares but still follow
1204 1204
 		 * proper pagination.
1205 1205
 		 */
1206 1206
 
1207
-		$shares2 = [];
1208
-
1209
-		while (true) {
1210
-			$added = 0;
1211
-			foreach ($shares as $share) {
1212
-				$added++;
1213
-				if ($onlyValid) {
1214
-					try {
1215
-						$this->checkShare($share, $added);
1216
-					} catch (ShareNotFound $e) {
1217
-						// Ignore since this basically means the share is deleted
1218
-						continue;
1219
-					}
1220
-				}
1221
-
1222
-				$shares2[] = $share;
1223
-
1224
-				if (count($shares2) === $limit) {
1225
-					break;
1226
-				}
1227
-			}
1228
-
1229
-			// If we did not fetch more shares than the limit then there are no more shares
1230
-			if (count($shares) < $limit) {
1231
-				break;
1232
-			}
1233
-
1234
-			if (count($shares2) === $limit) {
1235
-				break;
1236
-			}
1237
-
1238
-			// If there was no limit on the select we are done
1239
-			if ($limit === -1) {
1240
-				break;
1241
-			}
1242
-
1243
-			$offset += $added;
1244
-
1245
-			// Fetch again $limit shares
1246
-			if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1247
-				// We already fetched all shares, so end here
1248
-				$shares = [];
1249
-			} else {
1250
-				$shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1251
-			}
1252
-
1253
-			// No more shares means we are done
1254
-			if (empty($shares)) {
1255
-				break;
1256
-			}
1257
-		}
1258
-
1259
-		$shares = $shares2;
1260
-
1261
-		return $shares;
1262
-	}
1263
-
1264
-	#[Override]
1265
-	public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array {
1266
-		try {
1267
-			$provider = $this->factory->getProviderForType($shareType);
1268
-		} catch (ProviderException $e) {
1269
-			return [];
1270
-		}
1271
-
1272
-		$shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1273
-
1274
-		// remove all shares which are already expired
1275
-		foreach ($shares as $key => $share) {
1276
-			try {
1277
-				$this->checkShare($share);
1278
-			} catch (ShareNotFound $e) {
1279
-				unset($shares[$key]);
1280
-			}
1281
-		}
1282
-
1283
-		return $shares;
1284
-	}
1285
-
1286
-	#[Override]
1287
-	public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array {
1288
-		$shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1289
-
1290
-		// Only get shares deleted shares and where the owner still exists
1291
-		return array_filter($shares, fn (IShare $share): bool => $share->getPermissions() === 0
1292
-			&& $this->userManager->userExists($share->getShareOwner()));
1293
-	}
1294
-
1295
-	#[Override]
1296
-	public function getShareById($id, $recipient = null, bool $onlyValid = true): IShare {
1297
-		if ($id === null) {
1298
-			throw new ShareNotFound();
1299
-		}
1300
-
1301
-		[$providerId, $id] = $this->splitFullId($id);
1302
-
1303
-		try {
1304
-			$provider = $this->factory->getProvider($providerId);
1305
-		} catch (ProviderException $e) {
1306
-			throw new ShareNotFound();
1307
-		}
1308
-
1309
-		$share = $provider->getShareById($id, $recipient);
1310
-
1311
-		if ($onlyValid) {
1312
-			$this->checkShare($share);
1313
-		}
1314
-
1315
-		return $share;
1316
-	}
1317
-
1318
-	#[Override]
1319
-	public function getShareByToken(string $token): IShare {
1320
-		// tokens cannot be valid local usernames
1321
-		if ($this->userManager->userExists($token)) {
1322
-			throw new ShareNotFound();
1323
-		}
1324
-		$share = null;
1325
-		try {
1326
-			if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes') {
1327
-				$provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1328
-				$share = $provider->getShareByToken($token);
1329
-			}
1330
-		} catch (ProviderException|ShareNotFound) {
1331
-		}
1332
-
1333
-
1334
-		// If it is not a link share try to fetch a federated share by token
1335
-		if ($share === null) {
1336
-			try {
1337
-				$provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1338
-				$share = $provider->getShareByToken($token);
1339
-			} catch (ProviderException|ShareNotFound) {
1340
-			}
1341
-		}
1342
-
1343
-		// If it is not a link share try to fetch a mail share by token
1344
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1345
-			try {
1346
-				$provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1347
-				$share = $provider->getShareByToken($token);
1348
-			} catch (ProviderException|ShareNotFound) {
1349
-			}
1350
-		}
1351
-
1352
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1353
-			try {
1354
-				$provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1355
-				$share = $provider->getShareByToken($token);
1356
-			} catch (ProviderException|ShareNotFound) {
1357
-			}
1358
-		}
1359
-
1360
-		if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1361
-			try {
1362
-				$provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1363
-				$share = $provider->getShareByToken($token);
1364
-			} catch (ProviderException|ShareNotFound) {
1365
-			}
1366
-		}
1367
-
1368
-		if ($share === null) {
1369
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1370
-		}
1371
-
1372
-		$this->checkShare($share);
1373
-
1374
-		/*
1207
+        $shares2 = [];
1208
+
1209
+        while (true) {
1210
+            $added = 0;
1211
+            foreach ($shares as $share) {
1212
+                $added++;
1213
+                if ($onlyValid) {
1214
+                    try {
1215
+                        $this->checkShare($share, $added);
1216
+                    } catch (ShareNotFound $e) {
1217
+                        // Ignore since this basically means the share is deleted
1218
+                        continue;
1219
+                    }
1220
+                }
1221
+
1222
+                $shares2[] = $share;
1223
+
1224
+                if (count($shares2) === $limit) {
1225
+                    break;
1226
+                }
1227
+            }
1228
+
1229
+            // If we did not fetch more shares than the limit then there are no more shares
1230
+            if (count($shares) < $limit) {
1231
+                break;
1232
+            }
1233
+
1234
+            if (count($shares2) === $limit) {
1235
+                break;
1236
+            }
1237
+
1238
+            // If there was no limit on the select we are done
1239
+            if ($limit === -1) {
1240
+                break;
1241
+            }
1242
+
1243
+            $offset += $added;
1244
+
1245
+            // Fetch again $limit shares
1246
+            if ($path?->getMountPoint() instanceof IShareOwnerlessMount) {
1247
+                // We already fetched all shares, so end here
1248
+                $shares = [];
1249
+            } else {
1250
+                $shares = $provider->getSharesBy($userId, $shareType, $path, $reshares, $limit, $offset);
1251
+            }
1252
+
1253
+            // No more shares means we are done
1254
+            if (empty($shares)) {
1255
+                break;
1256
+            }
1257
+        }
1258
+
1259
+        $shares = $shares2;
1260
+
1261
+        return $shares;
1262
+    }
1263
+
1264
+    #[Override]
1265
+    public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array {
1266
+        try {
1267
+            $provider = $this->factory->getProviderForType($shareType);
1268
+        } catch (ProviderException $e) {
1269
+            return [];
1270
+        }
1271
+
1272
+        $shares = $provider->getSharedWith($userId, $shareType, $node, $limit, $offset);
1273
+
1274
+        // remove all shares which are already expired
1275
+        foreach ($shares as $key => $share) {
1276
+            try {
1277
+                $this->checkShare($share);
1278
+            } catch (ShareNotFound $e) {
1279
+                unset($shares[$key]);
1280
+            }
1281
+        }
1282
+
1283
+        return $shares;
1284
+    }
1285
+
1286
+    #[Override]
1287
+    public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array {
1288
+        $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset);
1289
+
1290
+        // Only get shares deleted shares and where the owner still exists
1291
+        return array_filter($shares, fn (IShare $share): bool => $share->getPermissions() === 0
1292
+            && $this->userManager->userExists($share->getShareOwner()));
1293
+    }
1294
+
1295
+    #[Override]
1296
+    public function getShareById($id, $recipient = null, bool $onlyValid = true): IShare {
1297
+        if ($id === null) {
1298
+            throw new ShareNotFound();
1299
+        }
1300
+
1301
+        [$providerId, $id] = $this->splitFullId($id);
1302
+
1303
+        try {
1304
+            $provider = $this->factory->getProvider($providerId);
1305
+        } catch (ProviderException $e) {
1306
+            throw new ShareNotFound();
1307
+        }
1308
+
1309
+        $share = $provider->getShareById($id, $recipient);
1310
+
1311
+        if ($onlyValid) {
1312
+            $this->checkShare($share);
1313
+        }
1314
+
1315
+        return $share;
1316
+    }
1317
+
1318
+    #[Override]
1319
+    public function getShareByToken(string $token): IShare {
1320
+        // tokens cannot be valid local usernames
1321
+        if ($this->userManager->userExists($token)) {
1322
+            throw new ShareNotFound();
1323
+        }
1324
+        $share = null;
1325
+        try {
1326
+            if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes') {
1327
+                $provider = $this->factory->getProviderForType(IShare::TYPE_LINK);
1328
+                $share = $provider->getShareByToken($token);
1329
+            }
1330
+        } catch (ProviderException|ShareNotFound) {
1331
+        }
1332
+
1333
+
1334
+        // If it is not a link share try to fetch a federated share by token
1335
+        if ($share === null) {
1336
+            try {
1337
+                $provider = $this->factory->getProviderForType(IShare::TYPE_REMOTE);
1338
+                $share = $provider->getShareByToken($token);
1339
+            } catch (ProviderException|ShareNotFound) {
1340
+            }
1341
+        }
1342
+
1343
+        // If it is not a link share try to fetch a mail share by token
1344
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_EMAIL)) {
1345
+            try {
1346
+                $provider = $this->factory->getProviderForType(IShare::TYPE_EMAIL);
1347
+                $share = $provider->getShareByToken($token);
1348
+            } catch (ProviderException|ShareNotFound) {
1349
+            }
1350
+        }
1351
+
1352
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_CIRCLE)) {
1353
+            try {
1354
+                $provider = $this->factory->getProviderForType(IShare::TYPE_CIRCLE);
1355
+                $share = $provider->getShareByToken($token);
1356
+            } catch (ProviderException|ShareNotFound) {
1357
+            }
1358
+        }
1359
+
1360
+        if ($share === null && $this->shareProviderExists(IShare::TYPE_ROOM)) {
1361
+            try {
1362
+                $provider = $this->factory->getProviderForType(IShare::TYPE_ROOM);
1363
+                $share = $provider->getShareByToken($token);
1364
+            } catch (ProviderException|ShareNotFound) {
1365
+            }
1366
+        }
1367
+
1368
+        if ($share === null) {
1369
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1370
+        }
1371
+
1372
+        $this->checkShare($share);
1373
+
1374
+        /*
1375 1375
 		 * Reduce the permissions for link or email shares if public upload is not enabled
1376 1376
 		 */
1377
-		if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1378
-			&& $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1379
-			$share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1380
-		}
1381
-
1382
-		return $share;
1383
-	}
1384
-
1385
-	/**
1386
-	 * Check expire date and disabled owner
1387
-	 *
1388
-	 * @param int &$added If given, will be decremented if the share is deleted
1389
-	 * @throws ShareNotFound
1390
-	 */
1391
-	private function checkShare(IShare $share, int &$added = 1): void {
1392
-		if ($share->isExpired()) {
1393
-			$this->deleteShare($share);
1394
-			// Remove 1 to added, because this share was deleted
1395
-			$added--;
1396
-			throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1397
-		}
1398
-		if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1399
-			$uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1400
-			foreach ($uids as $uid) {
1401
-				$user = $this->userManager->get($uid);
1402
-				if ($user?->isEnabled() === false) {
1403
-					throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1404
-				}
1405
-			}
1406
-		}
1407
-
1408
-		// For link and email shares, verify the share owner can still create such shares
1409
-		if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1410
-			$shareOwner = $this->userManager->get($share->getShareOwner());
1411
-			if ($shareOwner === null) {
1412
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1413
-			}
1414
-			if (!$this->userCanCreateLinkShares($shareOwner)) {
1415
-				throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1416
-			}
1417
-		}
1418
-	}
1419
-
1420
-	#[Override]
1421
-	public function checkPassword(IShare $share, ?string $password): bool {
1422
-
1423
-		// if there is no password on the share object / passsword is null, there is nothing to check
1424
-		if ($password === null || $share->getPassword() === null) {
1425
-			return false;
1426
-		}
1427
-
1428
-		// Makes sure password hasn't expired
1429
-		$expirationTime = $share->getPasswordExpirationTime();
1430
-		if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1431
-			return false;
1432
-		}
1433
-
1434
-		$newHash = '';
1435
-		if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1436
-			return false;
1437
-		}
1438
-
1439
-		if (!empty($newHash)) {
1440
-			$share->setPassword($newHash);
1441
-			$provider = $this->factory->getProviderForType($share->getShareType());
1442
-			$provider->update($share);
1443
-		}
1444
-
1445
-		return true;
1446
-	}
1447
-
1448
-	#[Override]
1449
-	public function userDeleted(string $uid): void {
1450
-		$types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1451
-
1452
-		foreach ($types as $type) {
1453
-			try {
1454
-				$provider = $this->factory->getProviderForType($type);
1455
-			} catch (ProviderException $e) {
1456
-				continue;
1457
-			}
1458
-			$provider->userDeleted($uid, $type);
1459
-		}
1460
-	}
1461
-
1462
-	#[Override]
1463
-	public function groupDeleted(string $gid): void {
1464
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1465
-			try {
1466
-				$provider = $this->factory->getProviderForType($type);
1467
-			} catch (ProviderException $e) {
1468
-				continue;
1469
-			}
1470
-			$provider->groupDeleted($gid);
1471
-		}
1472
-
1473
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1474
-		if ($excludedGroups === '') {
1475
-			return;
1476
-		}
1477
-
1478
-		$excludedGroups = json_decode($excludedGroups, true);
1479
-		if (json_last_error() !== JSON_ERROR_NONE) {
1480
-			return;
1481
-		}
1482
-
1483
-		$excludedGroups = array_diff($excludedGroups, [$gid]);
1484
-		$this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1485
-	}
1486
-
1487
-	#[Override]
1488
-	public function userDeletedFromGroup(string $uid, string $gid): void {
1489
-		foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1490
-			try {
1491
-				$provider = $this->factory->getProviderForType($type);
1492
-			} catch (ProviderException $e) {
1493
-				continue;
1494
-			}
1495
-			$provider->userDeletedFromGroup($uid, $gid);
1496
-		}
1497
-	}
1498
-
1499
-	#[\Override]
1500
-	public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false): array {
1501
-		$owner = $path->getOwner();
1502
-
1503
-		if ($owner === null) {
1504
-			return [];
1505
-		}
1506
-
1507
-		$owner = $owner->getUID();
1508
-
1509
-		if ($currentAccess) {
1510
-			$al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1511
-		} else {
1512
-			$al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1513
-		}
1514
-		if (!$this->userManager->userExists($owner)) {
1515
-			return $al;
1516
-		}
1517
-
1518
-		//Get node for the owner and correct the owner in case of external storage
1519
-		$userFolder = $this->rootFolder->getUserFolder($owner);
1520
-		if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1521
-			$path = $userFolder->getFirstNodeById($path->getId());
1522
-			if ($path === null || $path->getOwner() === null) {
1523
-				return [];
1524
-			}
1525
-			$owner = $path->getOwner()->getUID();
1526
-		}
1527
-
1528
-		$providers = $this->factory->getAllProviders();
1529
-
1530
-		/** @var Node[] $nodes */
1531
-		$nodes = [];
1532
-
1533
-
1534
-		if ($currentAccess) {
1535
-			$ownerPath = $path->getPath();
1536
-			$ownerPath = explode('/', $ownerPath, 4);
1537
-			if (count($ownerPath) < 4) {
1538
-				$ownerPath = '';
1539
-			} else {
1540
-				$ownerPath = $ownerPath[3];
1541
-			}
1542
-			$al['users'][$owner] = [
1543
-				'node_id' => $path->getId(),
1544
-				'node_path' => '/' . $ownerPath,
1545
-			];
1546
-		} else {
1547
-			$al['users'][] = $owner;
1548
-		}
1549
-
1550
-		// Collect all the shares
1551
-		while ($path->getPath() !== $userFolder->getPath()) {
1552
-			$nodes[] = $path;
1553
-			if (!$recursive) {
1554
-				break;
1555
-			}
1556
-			$path = $path->getParent();
1557
-		}
1558
-
1559
-		foreach ($providers as $provider) {
1560
-			$tmp = $provider->getAccessList($nodes, $currentAccess);
1561
-
1562
-			foreach ($tmp as $k => $v) {
1563
-				if (isset($al[$k])) {
1564
-					if (is_array($al[$k])) {
1565
-						if ($currentAccess) {
1566
-							$al[$k] += $v;
1567
-						} else {
1568
-							$al[$k] = array_merge($al[$k], $v);
1569
-							$al[$k] = array_unique($al[$k]);
1570
-							$al[$k] = array_values($al[$k]);
1571
-						}
1572
-					} else {
1573
-						$al[$k] = $al[$k] || $v;
1574
-					}
1575
-				} else {
1576
-					$al[$k] = $v;
1577
-				}
1578
-			}
1579
-		}
1580
-
1581
-		return $al;
1582
-	}
1583
-
1584
-	#[Override]
1585
-	public function newShare(): IShare {
1586
-		return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1587
-	}
1588
-
1589
-	#[Override]
1590
-	public function shareApiEnabled(): bool {
1591
-		return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1592
-	}
1593
-
1594
-	#[Override]
1595
-	public function shareApiAllowLinks(?IUser $user = null): bool {
1596
-		if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1597
-			return false;
1598
-		}
1599
-
1600
-		$user = $user ?? $this->userSession->getUser();
1601
-		if ($user) {
1602
-			$excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1603
-			if ($excludedGroups) {
1604
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1605
-				return !(bool)array_intersect($excludedGroups, $userGroups);
1606
-			}
1607
-		}
1608
-
1609
-		return true;
1610
-	}
1611
-
1612
-	/**
1613
-	 * Check if a specific user can create link shares
1614
-	 *
1615
-	 * @param IUser $user The user to check
1616
-	 * @return bool
1617
-	 */
1618
-	protected function userCanCreateLinkShares(IUser $user): bool {
1619
-		return $this->shareApiAllowLinks($user);
1620
-	}
1621
-
1622
-	#[Override]
1623
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true): bool {
1624
-		$excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1625
-		if ($excludedGroups !== '' && $checkGroupMembership) {
1626
-			$excludedGroups = json_decode($excludedGroups);
1627
-			$user = $this->userSession->getUser();
1628
-			if ($user) {
1629
-				$userGroups = $this->groupManager->getUserGroupIds($user);
1630
-				if ((bool)array_intersect($excludedGroups, $userGroups)) {
1631
-					return false;
1632
-				}
1633
-			}
1634
-		}
1635
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1636
-	}
1637
-
1638
-	#[Override]
1639
-	public function shareApiLinkDefaultExpireDate(): bool {
1640
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1641
-	}
1642
-
1643
-	#[Override]
1644
-	public function shareApiLinkDefaultExpireDateEnforced(): bool {
1645
-		return $this->shareApiLinkDefaultExpireDate()
1646
-			&& $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1647
-	}
1648
-
1649
-	#[Override]
1650
-	public function shareApiLinkDefaultExpireDays(): int {
1651
-		return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1652
-	}
1653
-
1654
-	#[Override]
1655
-	public function shareApiInternalDefaultExpireDate(): bool {
1656
-		return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1657
-	}
1658
-
1659
-	#[Override]
1660
-	public function shareApiRemoteDefaultExpireDate(): bool {
1661
-		return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1662
-	}
1663
-
1664
-	#[Override]
1665
-	public function shareApiInternalDefaultExpireDateEnforced(): bool {
1666
-		return $this->shareApiInternalDefaultExpireDate()
1667
-			&& $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1668
-	}
1669
-
1670
-	#[Override]
1671
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1672
-		return $this->shareApiRemoteDefaultExpireDate()
1673
-			&& $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1674
-	}
1675
-
1676
-	#[Override]
1677
-	public function shareApiInternalDefaultExpireDays(): int {
1678
-		return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1679
-	}
1680
-
1681
-	#[Override]
1682
-	public function shareApiRemoteDefaultExpireDays(): int {
1683
-		return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1684
-	}
1685
-
1686
-	#[Override]
1687
-	public function shareApiLinkAllowPublicUpload(): bool {
1688
-		return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1689
-	}
1690
-
1691
-	#[Override]
1692
-	public function shareWithGroupMembersOnly(): bool {
1693
-		return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1694
-	}
1695
-
1696
-	#[Override]
1697
-	public function shareWithGroupMembersOnlyExcludeGroupsList(): array {
1698
-		if (!$this->shareWithGroupMembersOnly()) {
1699
-			return [];
1700
-		}
1701
-		$excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1702
-		return json_decode($excludeGroups, true) ?? [];
1703
-	}
1704
-
1705
-	#[Override]
1706
-	public function allowGroupSharing(): bool {
1707
-		return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1708
-	}
1709
-
1710
-	#[Override]
1711
-	public function allowEnumeration(): bool {
1712
-		return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1713
-	}
1714
-
1715
-	#[Override]
1716
-	public function limitEnumerationToGroups(): bool {
1717
-		return $this->allowEnumeration()
1718
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1719
-	}
1720
-
1721
-	#[Override]
1722
-	public function limitEnumerationToPhone(): bool {
1723
-		return $this->allowEnumeration()
1724
-			&& $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1725
-	}
1726
-
1727
-	#[Override]
1728
-	public function allowEnumerationFullMatch(): bool {
1729
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1730
-	}
1731
-
1732
-	#[Override]
1733
-	public function matchEmail(): bool {
1734
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1735
-	}
1736
-
1737
-	#[Override]
1738
-	public function matchUserId(): bool {
1739
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_user_id', 'yes') === 'yes';
1740
-	}
1741
-
1742
-	public function matchDisplayName(): bool {
1743
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_displayname', 'yes') === 'yes';
1744
-	}
1745
-
1746
-	#[Override]
1747
-	public function ignoreSecondDisplayName(): bool {
1748
-		return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1749
-	}
1750
-
1751
-	#[Override]
1752
-	public function allowCustomTokens(): bool {
1753
-		return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1754
-	}
1755
-
1756
-	#[Override]
1757
-	public function allowViewWithoutDownload(): bool {
1758
-		return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1759
-	}
1760
-
1761
-	#[Override]
1762
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1763
-		if ($this->allowEnumerationFullMatch()) {
1764
-			return true;
1765
-		}
1766
-
1767
-		if (!$this->allowEnumeration()) {
1768
-			return false;
1769
-		}
1770
-
1771
-		if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1772
-			// Enumeration is enabled and not restricted: OK
1773
-			return true;
1774
-		}
1775
-
1776
-		if (!$currentUser instanceof IUser) {
1777
-			// Enumeration restrictions require an account
1778
-			return false;
1779
-		}
1780
-
1781
-		// Enumeration is limited to phone match
1782
-		if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1783
-			return true;
1784
-		}
1785
-
1786
-		// Enumeration is limited to groups
1787
-		if ($this->limitEnumerationToGroups()) {
1788
-			$currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1789
-			$targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1790
-			if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1791
-				return true;
1792
-			}
1793
-		}
1794
-
1795
-		return false;
1796
-	}
1797
-
1798
-	#[Override]
1799
-	public function sharingDisabledForUser(?string $userId): bool {
1800
-		return $this->shareDisableChecker->sharingDisabledForUser($userId);
1801
-	}
1802
-
1803
-	#[Override]
1804
-	public function outgoingServer2ServerSharesAllowed(): bool {
1805
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1806
-	}
1807
-
1808
-	#[Override]
1809
-	public function outgoingServer2ServerGroupSharesAllowed(): bool {
1810
-		return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1811
-	}
1812
-
1813
-	#[Override]
1814
-	public function shareProviderExists(int $shareType): bool {
1815
-		try {
1816
-			$this->factory->getProviderForType($shareType);
1817
-		} catch (ProviderException $e) {
1818
-			return false;
1819
-		}
1820
-
1821
-		return true;
1822
-	}
1823
-
1824
-	public function registerShareProvider(string $shareProviderClass): void {
1825
-		$this->factory->registerProvider($shareProviderClass);
1826
-	}
1827
-
1828
-	#[Override]
1829
-	public function getAllShares(): iterable {
1830
-		$providers = $this->factory->getAllProviders();
1831
-
1832
-		foreach ($providers as $provider) {
1833
-			yield from $provider->getAllShares();
1834
-		}
1835
-	}
1836
-
1837
-	#[Override]
1838
-	public function generateToken(): string {
1839
-		// Initial token length
1840
-		$tokenLength = Helper::getTokenLength();
1841
-
1842
-		do {
1843
-			$tokenExists = false;
1844
-
1845
-			for ($i = 0; $i <= 2; $i++) {
1846
-				// Generate a new token
1847
-				$token = $this->secureRandom->generate(
1848
-					$tokenLength,
1849
-					ISecureRandom::CHAR_HUMAN_READABLE,
1850
-				);
1851
-
1852
-				try {
1853
-					// Try to fetch a share with the generated token
1854
-					$this->getShareByToken($token);
1855
-					$tokenExists = true; // Token exists, we need to try again
1856
-				} catch (ShareNotFound $e) {
1857
-					// Token is unique, exit the loop
1858
-					$tokenExists = false;
1859
-					break;
1860
-				}
1861
-			}
1862
-
1863
-			// If we've reached the maximum attempts and the token still exists, increase the token length
1864
-			if ($tokenExists) {
1865
-				$tokenLength++;
1866
-
1867
-				// Check if the token length exceeds the maximum allowed length
1868
-				if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
1869
-					throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
1870
-				}
1871
-			}
1872
-		} while ($tokenExists);
1873
-
1874
-		return $token;
1875
-	}
1876
-
1877
-	private function dispatchEvent(Event $event, string $name): void {
1878
-		try {
1879
-			$this->dispatcher->dispatchTyped($event);
1880
-		} catch (\Exception $e) {
1881
-			$this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
1882
-		}
1883
-	}
1884
-
1885
-	public function getUsersForShare(IShare $share): iterable {
1886
-		$provider = $this->factory->getProviderForType($share->getShareType());
1887
-		if ($provider instanceof Share\IShareProviderGetUsers) {
1888
-			return $provider->getUsersForShare($share);
1889
-		} else {
1890
-			return [];
1891
-		}
1892
-	}
1377
+        if (($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL)
1378
+            && $share->getNodeType() === 'folder' && !$this->shareApiLinkAllowPublicUpload()) {
1379
+            $share->setPermissions($share->getPermissions() & ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE));
1380
+        }
1381
+
1382
+        return $share;
1383
+    }
1384
+
1385
+    /**
1386
+     * Check expire date and disabled owner
1387
+     *
1388
+     * @param int &$added If given, will be decremented if the share is deleted
1389
+     * @throws ShareNotFound
1390
+     */
1391
+    private function checkShare(IShare $share, int &$added = 1): void {
1392
+        if ($share->isExpired()) {
1393
+            $this->deleteShare($share);
1394
+            // Remove 1 to added, because this share was deleted
1395
+            $added--;
1396
+            throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1397
+        }
1398
+        if ($this->config->getAppValue('files_sharing', 'hide_disabled_user_shares', 'no') === 'yes') {
1399
+            $uids = array_unique([$share->getShareOwner(),$share->getSharedBy()]);
1400
+            foreach ($uids as $uid) {
1401
+                $user = $this->userManager->get($uid);
1402
+                if ($user?->isEnabled() === false) {
1403
+                    throw new ShareNotFound($this->l->t('The requested share comes from a disabled user'));
1404
+                }
1405
+            }
1406
+        }
1407
+
1408
+        // For link and email shares, verify the share owner can still create such shares
1409
+        if ($share->getShareType() === IShare::TYPE_LINK || $share->getShareType() === IShare::TYPE_EMAIL) {
1410
+            $shareOwner = $this->userManager->get($share->getShareOwner());
1411
+            if ($shareOwner === null) {
1412
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1413
+            }
1414
+            if (!$this->userCanCreateLinkShares($shareOwner)) {
1415
+                throw new ShareNotFound($this->l->t('The requested share does not exist anymore'));
1416
+            }
1417
+        }
1418
+    }
1419
+
1420
+    #[Override]
1421
+    public function checkPassword(IShare $share, ?string $password): bool {
1422
+
1423
+        // if there is no password on the share object / passsword is null, there is nothing to check
1424
+        if ($password === null || $share->getPassword() === null) {
1425
+            return false;
1426
+        }
1427
+
1428
+        // Makes sure password hasn't expired
1429
+        $expirationTime = $share->getPasswordExpirationTime();
1430
+        if ($expirationTime !== null && $expirationTime < new \DateTime()) {
1431
+            return false;
1432
+        }
1433
+
1434
+        $newHash = '';
1435
+        if (!$this->hasher->verify($password, $share->getPassword(), $newHash)) {
1436
+            return false;
1437
+        }
1438
+
1439
+        if (!empty($newHash)) {
1440
+            $share->setPassword($newHash);
1441
+            $provider = $this->factory->getProviderForType($share->getShareType());
1442
+            $provider->update($share);
1443
+        }
1444
+
1445
+        return true;
1446
+    }
1447
+
1448
+    #[Override]
1449
+    public function userDeleted(string $uid): void {
1450
+        $types = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_EMAIL];
1451
+
1452
+        foreach ($types as $type) {
1453
+            try {
1454
+                $provider = $this->factory->getProviderForType($type);
1455
+            } catch (ProviderException $e) {
1456
+                continue;
1457
+            }
1458
+            $provider->userDeleted($uid, $type);
1459
+        }
1460
+    }
1461
+
1462
+    #[Override]
1463
+    public function groupDeleted(string $gid): void {
1464
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1465
+            try {
1466
+                $provider = $this->factory->getProviderForType($type);
1467
+            } catch (ProviderException $e) {
1468
+                continue;
1469
+            }
1470
+            $provider->groupDeleted($gid);
1471
+        }
1472
+
1473
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
1474
+        if ($excludedGroups === '') {
1475
+            return;
1476
+        }
1477
+
1478
+        $excludedGroups = json_decode($excludedGroups, true);
1479
+        if (json_last_error() !== JSON_ERROR_NONE) {
1480
+            return;
1481
+        }
1482
+
1483
+        $excludedGroups = array_diff($excludedGroups, [$gid]);
1484
+        $this->config->setAppValue('core', 'shareapi_exclude_groups_list', json_encode($excludedGroups));
1485
+    }
1486
+
1487
+    #[Override]
1488
+    public function userDeletedFromGroup(string $uid, string $gid): void {
1489
+        foreach ([IShare::TYPE_GROUP, IShare::TYPE_REMOTE_GROUP] as $type) {
1490
+            try {
1491
+                $provider = $this->factory->getProviderForType($type);
1492
+            } catch (ProviderException $e) {
1493
+                continue;
1494
+            }
1495
+            $provider->userDeletedFromGroup($uid, $gid);
1496
+        }
1497
+    }
1498
+
1499
+    #[\Override]
1500
+    public function getAccessList(\OCP\Files\Node $path, $recursive = true, $currentAccess = false): array {
1501
+        $owner = $path->getOwner();
1502
+
1503
+        if ($owner === null) {
1504
+            return [];
1505
+        }
1506
+
1507
+        $owner = $owner->getUID();
1508
+
1509
+        if ($currentAccess) {
1510
+            $al = ['users' => [], 'remote' => [], 'public' => false, 'mail' => []];
1511
+        } else {
1512
+            $al = ['users' => [], 'remote' => false, 'public' => false, 'mail' => []];
1513
+        }
1514
+        if (!$this->userManager->userExists($owner)) {
1515
+            return $al;
1516
+        }
1517
+
1518
+        //Get node for the owner and correct the owner in case of external storage
1519
+        $userFolder = $this->rootFolder->getUserFolder($owner);
1520
+        if ($path->getId() !== $userFolder->getId() && !$userFolder->isSubNode($path)) {
1521
+            $path = $userFolder->getFirstNodeById($path->getId());
1522
+            if ($path === null || $path->getOwner() === null) {
1523
+                return [];
1524
+            }
1525
+            $owner = $path->getOwner()->getUID();
1526
+        }
1527
+
1528
+        $providers = $this->factory->getAllProviders();
1529
+
1530
+        /** @var Node[] $nodes */
1531
+        $nodes = [];
1532
+
1533
+
1534
+        if ($currentAccess) {
1535
+            $ownerPath = $path->getPath();
1536
+            $ownerPath = explode('/', $ownerPath, 4);
1537
+            if (count($ownerPath) < 4) {
1538
+                $ownerPath = '';
1539
+            } else {
1540
+                $ownerPath = $ownerPath[3];
1541
+            }
1542
+            $al['users'][$owner] = [
1543
+                'node_id' => $path->getId(),
1544
+                'node_path' => '/' . $ownerPath,
1545
+            ];
1546
+        } else {
1547
+            $al['users'][] = $owner;
1548
+        }
1549
+
1550
+        // Collect all the shares
1551
+        while ($path->getPath() !== $userFolder->getPath()) {
1552
+            $nodes[] = $path;
1553
+            if (!$recursive) {
1554
+                break;
1555
+            }
1556
+            $path = $path->getParent();
1557
+        }
1558
+
1559
+        foreach ($providers as $provider) {
1560
+            $tmp = $provider->getAccessList($nodes, $currentAccess);
1561
+
1562
+            foreach ($tmp as $k => $v) {
1563
+                if (isset($al[$k])) {
1564
+                    if (is_array($al[$k])) {
1565
+                        if ($currentAccess) {
1566
+                            $al[$k] += $v;
1567
+                        } else {
1568
+                            $al[$k] = array_merge($al[$k], $v);
1569
+                            $al[$k] = array_unique($al[$k]);
1570
+                            $al[$k] = array_values($al[$k]);
1571
+                        }
1572
+                    } else {
1573
+                        $al[$k] = $al[$k] || $v;
1574
+                    }
1575
+                } else {
1576
+                    $al[$k] = $v;
1577
+                }
1578
+            }
1579
+        }
1580
+
1581
+        return $al;
1582
+    }
1583
+
1584
+    #[Override]
1585
+    public function newShare(): IShare {
1586
+        return new \OC\Share20\Share($this->rootFolder, $this->userManager);
1587
+    }
1588
+
1589
+    #[Override]
1590
+    public function shareApiEnabled(): bool {
1591
+        return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes';
1592
+    }
1593
+
1594
+    #[Override]
1595
+    public function shareApiAllowLinks(?IUser $user = null): bool {
1596
+        if ($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
1597
+            return false;
1598
+        }
1599
+
1600
+        $user = $user ?? $this->userSession->getUser();
1601
+        if ($user) {
1602
+            $excludedGroups = json_decode($this->config->getAppValue('core', 'shareapi_allow_links_exclude_groups', '[]'));
1603
+            if ($excludedGroups) {
1604
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1605
+                return !(bool)array_intersect($excludedGroups, $userGroups);
1606
+            }
1607
+        }
1608
+
1609
+        return true;
1610
+    }
1611
+
1612
+    /**
1613
+     * Check if a specific user can create link shares
1614
+     *
1615
+     * @param IUser $user The user to check
1616
+     * @return bool
1617
+     */
1618
+    protected function userCanCreateLinkShares(IUser $user): bool {
1619
+        return $this->shareApiAllowLinks($user);
1620
+    }
1621
+
1622
+    #[Override]
1623
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true): bool {
1624
+        $excludedGroups = $this->config->getAppValue('core', 'shareapi_enforce_links_password_excluded_groups', '');
1625
+        if ($excludedGroups !== '' && $checkGroupMembership) {
1626
+            $excludedGroups = json_decode($excludedGroups);
1627
+            $user = $this->userSession->getUser();
1628
+            if ($user) {
1629
+                $userGroups = $this->groupManager->getUserGroupIds($user);
1630
+                if ((bool)array_intersect($excludedGroups, $userGroups)) {
1631
+                    return false;
1632
+                }
1633
+            }
1634
+        }
1635
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_PASSWORD_ENFORCED);
1636
+    }
1637
+
1638
+    #[Override]
1639
+    public function shareApiLinkDefaultExpireDate(): bool {
1640
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_DEFAULT);
1641
+    }
1642
+
1643
+    #[Override]
1644
+    public function shareApiLinkDefaultExpireDateEnforced(): bool {
1645
+        return $this->shareApiLinkDefaultExpireDate()
1646
+            && $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_LINK_EXPIRE_DATE_ENFORCED);
1647
+    }
1648
+
1649
+    #[Override]
1650
+    public function shareApiLinkDefaultExpireDays(): int {
1651
+        return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7');
1652
+    }
1653
+
1654
+    #[Override]
1655
+    public function shareApiInternalDefaultExpireDate(): bool {
1656
+        return $this->config->getAppValue('core', 'shareapi_default_internal_expire_date', 'no') === 'yes';
1657
+    }
1658
+
1659
+    #[Override]
1660
+    public function shareApiRemoteDefaultExpireDate(): bool {
1661
+        return $this->config->getAppValue('core', 'shareapi_default_remote_expire_date', 'no') === 'yes';
1662
+    }
1663
+
1664
+    #[Override]
1665
+    public function shareApiInternalDefaultExpireDateEnforced(): bool {
1666
+        return $this->shareApiInternalDefaultExpireDate()
1667
+            && $this->config->getAppValue('core', 'shareapi_enforce_internal_expire_date', 'no') === 'yes';
1668
+    }
1669
+
1670
+    #[Override]
1671
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool {
1672
+        return $this->shareApiRemoteDefaultExpireDate()
1673
+            && $this->config->getAppValue('core', 'shareapi_enforce_remote_expire_date', 'no') === 'yes';
1674
+    }
1675
+
1676
+    #[Override]
1677
+    public function shareApiInternalDefaultExpireDays(): int {
1678
+        return (int)$this->config->getAppValue('core', 'shareapi_internal_expire_after_n_days', '7');
1679
+    }
1680
+
1681
+    #[Override]
1682
+    public function shareApiRemoteDefaultExpireDays(): int {
1683
+        return (int)$this->config->getAppValue('core', 'shareapi_remote_expire_after_n_days', '7');
1684
+    }
1685
+
1686
+    #[Override]
1687
+    public function shareApiLinkAllowPublicUpload(): bool {
1688
+        return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes';
1689
+    }
1690
+
1691
+    #[Override]
1692
+    public function shareWithGroupMembersOnly(): bool {
1693
+        return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
1694
+    }
1695
+
1696
+    #[Override]
1697
+    public function shareWithGroupMembersOnlyExcludeGroupsList(): array {
1698
+        if (!$this->shareWithGroupMembersOnly()) {
1699
+            return [];
1700
+        }
1701
+        $excludeGroups = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', '');
1702
+        return json_decode($excludeGroups, true) ?? [];
1703
+    }
1704
+
1705
+    #[Override]
1706
+    public function allowGroupSharing(): bool {
1707
+        return $this->config->getAppValue('core', 'shareapi_allow_group_sharing', 'yes') === 'yes';
1708
+    }
1709
+
1710
+    #[Override]
1711
+    public function allowEnumeration(): bool {
1712
+        return $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
1713
+    }
1714
+
1715
+    #[Override]
1716
+    public function limitEnumerationToGroups(): bool {
1717
+        return $this->allowEnumeration()
1718
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
1719
+    }
1720
+
1721
+    #[Override]
1722
+    public function limitEnumerationToPhone(): bool {
1723
+        return $this->allowEnumeration()
1724
+            && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
1725
+    }
1726
+
1727
+    #[Override]
1728
+    public function allowEnumerationFullMatch(): bool {
1729
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
1730
+    }
1731
+
1732
+    #[Override]
1733
+    public function matchEmail(): bool {
1734
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
1735
+    }
1736
+
1737
+    #[Override]
1738
+    public function matchUserId(): bool {
1739
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_user_id', 'yes') === 'yes';
1740
+    }
1741
+
1742
+    public function matchDisplayName(): bool {
1743
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_displayname', 'yes') === 'yes';
1744
+    }
1745
+
1746
+    #[Override]
1747
+    public function ignoreSecondDisplayName(): bool {
1748
+        return $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_ignore_second_dn', 'no') === 'yes';
1749
+    }
1750
+
1751
+    #[Override]
1752
+    public function allowCustomTokens(): bool {
1753
+        return $this->appConfig->getValueBool('core', ConfigLexicon::SHARE_CUSTOM_TOKEN);
1754
+    }
1755
+
1756
+    #[Override]
1757
+    public function allowViewWithoutDownload(): bool {
1758
+        return $this->appConfig->getValueBool('core', 'shareapi_allow_view_without_download', true);
1759
+    }
1760
+
1761
+    #[Override]
1762
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool {
1763
+        if ($this->allowEnumerationFullMatch()) {
1764
+            return true;
1765
+        }
1766
+
1767
+        if (!$this->allowEnumeration()) {
1768
+            return false;
1769
+        }
1770
+
1771
+        if (!$this->limitEnumerationToPhone() && !$this->limitEnumerationToGroups()) {
1772
+            // Enumeration is enabled and not restricted: OK
1773
+            return true;
1774
+        }
1775
+
1776
+        if (!$currentUser instanceof IUser) {
1777
+            // Enumeration restrictions require an account
1778
+            return false;
1779
+        }
1780
+
1781
+        // Enumeration is limited to phone match
1782
+        if ($this->limitEnumerationToPhone() && $this->knownUserService->isKnownToUser($currentUser->getUID(), $targetUser->getUID())) {
1783
+            return true;
1784
+        }
1785
+
1786
+        // Enumeration is limited to groups
1787
+        if ($this->limitEnumerationToGroups()) {
1788
+            $currentUserGroupIds = $this->groupManager->getUserGroupIds($currentUser);
1789
+            $targetUserGroupIds = $this->groupManager->getUserGroupIds($targetUser);
1790
+            if (!empty(array_intersect($currentUserGroupIds, $targetUserGroupIds))) {
1791
+                return true;
1792
+            }
1793
+        }
1794
+
1795
+        return false;
1796
+    }
1797
+
1798
+    #[Override]
1799
+    public function sharingDisabledForUser(?string $userId): bool {
1800
+        return $this->shareDisableChecker->sharingDisabledForUser($userId);
1801
+    }
1802
+
1803
+    #[Override]
1804
+    public function outgoingServer2ServerSharesAllowed(): bool {
1805
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes';
1806
+    }
1807
+
1808
+    #[Override]
1809
+    public function outgoingServer2ServerGroupSharesAllowed(): bool {
1810
+        return $this->config->getAppValue('files_sharing', 'outgoing_server2server_group_share_enabled', 'no') === 'yes';
1811
+    }
1812
+
1813
+    #[Override]
1814
+    public function shareProviderExists(int $shareType): bool {
1815
+        try {
1816
+            $this->factory->getProviderForType($shareType);
1817
+        } catch (ProviderException $e) {
1818
+            return false;
1819
+        }
1820
+
1821
+        return true;
1822
+    }
1823
+
1824
+    public function registerShareProvider(string $shareProviderClass): void {
1825
+        $this->factory->registerProvider($shareProviderClass);
1826
+    }
1827
+
1828
+    #[Override]
1829
+    public function getAllShares(): iterable {
1830
+        $providers = $this->factory->getAllProviders();
1831
+
1832
+        foreach ($providers as $provider) {
1833
+            yield from $provider->getAllShares();
1834
+        }
1835
+    }
1836
+
1837
+    #[Override]
1838
+    public function generateToken(): string {
1839
+        // Initial token length
1840
+        $tokenLength = Helper::getTokenLength();
1841
+
1842
+        do {
1843
+            $tokenExists = false;
1844
+
1845
+            for ($i = 0; $i <= 2; $i++) {
1846
+                // Generate a new token
1847
+                $token = $this->secureRandom->generate(
1848
+                    $tokenLength,
1849
+                    ISecureRandom::CHAR_HUMAN_READABLE,
1850
+                );
1851
+
1852
+                try {
1853
+                    // Try to fetch a share with the generated token
1854
+                    $this->getShareByToken($token);
1855
+                    $tokenExists = true; // Token exists, we need to try again
1856
+                } catch (ShareNotFound $e) {
1857
+                    // Token is unique, exit the loop
1858
+                    $tokenExists = false;
1859
+                    break;
1860
+                }
1861
+            }
1862
+
1863
+            // If we've reached the maximum attempts and the token still exists, increase the token length
1864
+            if ($tokenExists) {
1865
+                $tokenLength++;
1866
+
1867
+                // Check if the token length exceeds the maximum allowed length
1868
+                if ($tokenLength > \OC\Share\Constants::MAX_TOKEN_LENGTH) {
1869
+                    throw new ShareTokenException('Unable to generate a unique share token. Maximum token length exceeded.');
1870
+                }
1871
+            }
1872
+        } while ($tokenExists);
1873
+
1874
+        return $token;
1875
+    }
1876
+
1877
+    private function dispatchEvent(Event $event, string $name): void {
1878
+        try {
1879
+            $this->dispatcher->dispatchTyped($event);
1880
+        } catch (\Exception $e) {
1881
+            $this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]);
1882
+        }
1883
+    }
1884
+
1885
+    public function getUsersForShare(IShare $share): iterable {
1886
+        $provider = $this->factory->getProviderForType($share->getShareType());
1887
+        if ($provider instanceof Share\IShareProviderGetUsers) {
1888
+            return $provider->getUsersForShare($share);
1889
+        } else {
1890
+            return [];
1891
+        }
1892
+    }
1893 1893
 }
Please login to merge, or discard this patch.
lib/private/Share20/DefaultShareProvider.php 1 patch
Indentation   +1612 added lines, -1612 removed lines patch added patch discarded remove patch
@@ -46,1652 +46,1652 @@
 block discarded – undo
46 46
  * @package OC\Share20
47 47
  */
48 48
 class DefaultShareProvider implements
49
-	IShareProviderWithNotification,
50
-	IShareProviderSupportsAccept,
51
-	IShareProviderSupportsAllSharesInFolder,
52
-	IShareProviderGetUsers {
53
-	public function __construct(
54
-		private IDBConnection $dbConn,
55
-		private IUserManager $userManager,
56
-		private IGroupManager $groupManager,
57
-		private IRootFolder $rootFolder,
58
-		private IMailer $mailer,
59
-		private Defaults $defaults,
60
-		private IFactory $l10nFactory,
61
-		private IURLGenerator $urlGenerator,
62
-		private ITimeFactory $timeFactory,
63
-		private LoggerInterface $logger,
64
-		private IManager $shareManager,
65
-		private IConfig $config,
66
-	) {
67
-	}
68
-
69
-	/**
70
-	 * Return the identifier of this provider.
71
-	 *
72
-	 * @return string Containing only [a-zA-Z0-9]
73
-	 */
74
-	public function identifier() {
75
-		return 'ocinternal';
76
-	}
77
-
78
-	/**
79
-	 * Share a path
80
-	 *
81
-	 * @param \OCP\Share\IShare $share
82
-	 * @return \OCP\Share\IShare The share object
83
-	 * @throws ShareNotFound
84
-	 * @throws \Exception
85
-	 */
86
-	public function create(\OCP\Share\IShare $share) {
87
-		$qb = $this->dbConn->getQueryBuilder();
88
-
89
-		$qb->insert('share');
90
-		$qb->setValue('share_type', $qb->createNamedParameter($share->getShareType()));
91
-
92
-		$expirationDate = $share->getExpirationDate();
93
-		if ($expirationDate !== null) {
94
-			$expirationDate = clone $expirationDate;
95
-			$expirationDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
96
-		}
97
-
98
-		if ($share->getShareType() === IShare::TYPE_USER) {
99
-			//Set the UID of the user we share with
100
-			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
101
-			$qb->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING));
102
-
103
-			//If an expiration date is set store it
104
-			if ($expirationDate !== null) {
105
-				$qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
106
-			}
107
-
108
-			$qb->setValue('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL));
109
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
110
-			//Set the GID of the group we share with
111
-			$qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
112
-
113
-			//If an expiration date is set store it
114
-			if ($expirationDate !== null) {
115
-				$qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
116
-			}
117
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
118
-			//set label for public link
119
-			$qb->setValue('label', $qb->createNamedParameter($share->getLabel()));
120
-			//Set the token of the share
121
-			$qb->setValue('token', $qb->createNamedParameter($share->getToken()));
122
-
123
-			//If a password is set store it
124
-			if ($share->getPassword() !== null) {
125
-				$qb->setValue('password', $qb->createNamedParameter($share->getPassword()));
126
-			}
127
-
128
-			$qb->setValue('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL));
129
-
130
-			//If an expiration date is set store it
131
-			if ($expirationDate !== null) {
132
-				$qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
133
-			}
134
-
135
-			$qb->setValue('parent', $qb->createNamedParameter($share->getParent()));
136
-
137
-			$qb->setValue('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT));
138
-		} else {
139
-			throw new \Exception('invalid share type!');
140
-		}
141
-
142
-		// Set what is shares
143
-		$qb->setValue('item_type', $qb->createParameter('itemType'));
144
-		if ($share->getNode() instanceof \OCP\Files\File) {
145
-			$qb->setParameter('itemType', 'file');
146
-		} else {
147
-			$qb->setParameter('itemType', 'folder');
148
-		}
149
-
150
-		// Set the file id
151
-		$qb->setValue('item_source', $qb->createNamedParameter($share->getNode()->getId()));
152
-		$qb->setValue('file_source', $qb->createNamedParameter($share->getNode()->getId()));
153
-
154
-		// set the permissions
155
-		$qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
156
-
157
-		// set share attributes
158
-		$shareAttributes = $this->formatShareAttributes(
159
-			$share->getAttributes()
160
-		);
161
-		$qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
162
-
163
-		// Set who created this share
164
-		$qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
165
-
166
-		// Set who is the owner of this file/folder (and this the owner of the share)
167
-		$qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()));
168
-
169
-		// Set the file target
170
-		$qb->setValue('file_target', $qb->createNamedParameter($share->getTarget()));
171
-
172
-		if ($share->getNote() !== '') {
173
-			$qb->setValue('note', $qb->createNamedParameter($share->getNote()));
174
-		}
175
-
176
-		// Set the time this share was created
177
-		$shareTime = $this->timeFactory->now();
178
-		$qb->setValue('stime', $qb->createNamedParameter($shareTime->getTimestamp()));
179
-
180
-		// insert the data and fetch the id of the share
181
-		$qb->executeStatement();
182
-
183
-		// Update mandatory data
184
-		$id = $qb->getLastInsertId();
185
-		$share->setId((string)$id);
186
-		$share->setProviderId($this->identifier());
187
-
188
-		$share->setShareTime(\DateTime::createFromImmutable($shareTime));
189
-
190
-		$mailSendValue = $share->getMailSend();
191
-		$share->setMailSend(($mailSendValue === null) ? true : $mailSendValue);
192
-
193
-		return $share;
194
-	}
195
-
196
-	/**
197
-	 * Update a share
198
-	 *
199
-	 * @param \OCP\Share\IShare $share
200
-	 * @return \OCP\Share\IShare The share object
201
-	 * @throws ShareNotFound
202
-	 * @throws \OCP\Files\InvalidPathException
203
-	 * @throws \OCP\Files\NotFoundException
204
-	 */
205
-	public function update(\OCP\Share\IShare $share) {
206
-		$originalShare = $this->getShareById($share->getId());
207
-
208
-		$shareAttributes = $this->formatShareAttributes($share->getAttributes());
209
-
210
-		$expirationDate = $share->getExpirationDate();
211
-		if ($expirationDate !== null) {
212
-			$expirationDate = clone $expirationDate;
213
-			$expirationDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
214
-		}
215
-
216
-		if ($share->getShareType() === IShare::TYPE_USER) {
217
-			/*
49
+    IShareProviderWithNotification,
50
+    IShareProviderSupportsAccept,
51
+    IShareProviderSupportsAllSharesInFolder,
52
+    IShareProviderGetUsers {
53
+    public function __construct(
54
+        private IDBConnection $dbConn,
55
+        private IUserManager $userManager,
56
+        private IGroupManager $groupManager,
57
+        private IRootFolder $rootFolder,
58
+        private IMailer $mailer,
59
+        private Defaults $defaults,
60
+        private IFactory $l10nFactory,
61
+        private IURLGenerator $urlGenerator,
62
+        private ITimeFactory $timeFactory,
63
+        private LoggerInterface $logger,
64
+        private IManager $shareManager,
65
+        private IConfig $config,
66
+    ) {
67
+    }
68
+
69
+    /**
70
+     * Return the identifier of this provider.
71
+     *
72
+     * @return string Containing only [a-zA-Z0-9]
73
+     */
74
+    public function identifier() {
75
+        return 'ocinternal';
76
+    }
77
+
78
+    /**
79
+     * Share a path
80
+     *
81
+     * @param \OCP\Share\IShare $share
82
+     * @return \OCP\Share\IShare The share object
83
+     * @throws ShareNotFound
84
+     * @throws \Exception
85
+     */
86
+    public function create(\OCP\Share\IShare $share) {
87
+        $qb = $this->dbConn->getQueryBuilder();
88
+
89
+        $qb->insert('share');
90
+        $qb->setValue('share_type', $qb->createNamedParameter($share->getShareType()));
91
+
92
+        $expirationDate = $share->getExpirationDate();
93
+        if ($expirationDate !== null) {
94
+            $expirationDate = clone $expirationDate;
95
+            $expirationDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
96
+        }
97
+
98
+        if ($share->getShareType() === IShare::TYPE_USER) {
99
+            //Set the UID of the user we share with
100
+            $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
101
+            $qb->setValue('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING));
102
+
103
+            //If an expiration date is set store it
104
+            if ($expirationDate !== null) {
105
+                $qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
106
+            }
107
+
108
+            $qb->setValue('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL));
109
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
110
+            //Set the GID of the group we share with
111
+            $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()));
112
+
113
+            //If an expiration date is set store it
114
+            if ($expirationDate !== null) {
115
+                $qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
116
+            }
117
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
118
+            //set label for public link
119
+            $qb->setValue('label', $qb->createNamedParameter($share->getLabel()));
120
+            //Set the token of the share
121
+            $qb->setValue('token', $qb->createNamedParameter($share->getToken()));
122
+
123
+            //If a password is set store it
124
+            if ($share->getPassword() !== null) {
125
+                $qb->setValue('password', $qb->createNamedParameter($share->getPassword()));
126
+            }
127
+
128
+            $qb->setValue('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL));
129
+
130
+            //If an expiration date is set store it
131
+            if ($expirationDate !== null) {
132
+                $qb->setValue('expiration', $qb->createNamedParameter($expirationDate, 'datetime'));
133
+            }
134
+
135
+            $qb->setValue('parent', $qb->createNamedParameter($share->getParent()));
136
+
137
+            $qb->setValue('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT));
138
+        } else {
139
+            throw new \Exception('invalid share type!');
140
+        }
141
+
142
+        // Set what is shares
143
+        $qb->setValue('item_type', $qb->createParameter('itemType'));
144
+        if ($share->getNode() instanceof \OCP\Files\File) {
145
+            $qb->setParameter('itemType', 'file');
146
+        } else {
147
+            $qb->setParameter('itemType', 'folder');
148
+        }
149
+
150
+        // Set the file id
151
+        $qb->setValue('item_source', $qb->createNamedParameter($share->getNode()->getId()));
152
+        $qb->setValue('file_source', $qb->createNamedParameter($share->getNode()->getId()));
153
+
154
+        // set the permissions
155
+        $qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
156
+
157
+        // set share attributes
158
+        $shareAttributes = $this->formatShareAttributes(
159
+            $share->getAttributes()
160
+        );
161
+        $qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
162
+
163
+        // Set who created this share
164
+        $qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
165
+
166
+        // Set who is the owner of this file/folder (and this the owner of the share)
167
+        $qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()));
168
+
169
+        // Set the file target
170
+        $qb->setValue('file_target', $qb->createNamedParameter($share->getTarget()));
171
+
172
+        if ($share->getNote() !== '') {
173
+            $qb->setValue('note', $qb->createNamedParameter($share->getNote()));
174
+        }
175
+
176
+        // Set the time this share was created
177
+        $shareTime = $this->timeFactory->now();
178
+        $qb->setValue('stime', $qb->createNamedParameter($shareTime->getTimestamp()));
179
+
180
+        // insert the data and fetch the id of the share
181
+        $qb->executeStatement();
182
+
183
+        // Update mandatory data
184
+        $id = $qb->getLastInsertId();
185
+        $share->setId((string)$id);
186
+        $share->setProviderId($this->identifier());
187
+
188
+        $share->setShareTime(\DateTime::createFromImmutable($shareTime));
189
+
190
+        $mailSendValue = $share->getMailSend();
191
+        $share->setMailSend(($mailSendValue === null) ? true : $mailSendValue);
192
+
193
+        return $share;
194
+    }
195
+
196
+    /**
197
+     * Update a share
198
+     *
199
+     * @param \OCP\Share\IShare $share
200
+     * @return \OCP\Share\IShare The share object
201
+     * @throws ShareNotFound
202
+     * @throws \OCP\Files\InvalidPathException
203
+     * @throws \OCP\Files\NotFoundException
204
+     */
205
+    public function update(\OCP\Share\IShare $share) {
206
+        $originalShare = $this->getShareById($share->getId());
207
+
208
+        $shareAttributes = $this->formatShareAttributes($share->getAttributes());
209
+
210
+        $expirationDate = $share->getExpirationDate();
211
+        if ($expirationDate !== null) {
212
+            $expirationDate = clone $expirationDate;
213
+            $expirationDate->setTimezone(new \DateTimeZone(date_default_timezone_get()));
214
+        }
215
+
216
+        if ($share->getShareType() === IShare::TYPE_USER) {
217
+            /*
218 218
 			 * We allow updating the recipient on user shares.
219 219
 			 */
220
-			$qb = $this->dbConn->getQueryBuilder();
221
-			$qb->update('share')
222
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
223
-				->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
224
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
225
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
226
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
227
-				->set('attributes', $qb->createNamedParameter($shareAttributes))
228
-				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
229
-				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
230
-				->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
231
-				->set('note', $qb->createNamedParameter($share->getNote()))
232
-				->set('accepted', $qb->createNamedParameter($share->getStatus()))
233
-				->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL))
234
-				->executeStatement();
235
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
236
-			$qb = $this->dbConn->getQueryBuilder();
237
-			$qb->update('share')
238
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
239
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
240
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
241
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
242
-				->set('attributes', $qb->createNamedParameter($shareAttributes))
243
-				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
244
-				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
245
-				->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
246
-				->set('note', $qb->createNamedParameter($share->getNote()))
247
-				->executeStatement();
248
-
249
-			/*
220
+            $qb = $this->dbConn->getQueryBuilder();
221
+            $qb->update('share')
222
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
223
+                ->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
224
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
225
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
226
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
227
+                ->set('attributes', $qb->createNamedParameter($shareAttributes))
228
+                ->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
229
+                ->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
230
+                ->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
231
+                ->set('note', $qb->createNamedParameter($share->getNote()))
232
+                ->set('accepted', $qb->createNamedParameter($share->getStatus()))
233
+                ->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL))
234
+                ->executeStatement();
235
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
236
+            $qb = $this->dbConn->getQueryBuilder();
237
+            $qb->update('share')
238
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
239
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
240
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
241
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
242
+                ->set('attributes', $qb->createNamedParameter($shareAttributes))
243
+                ->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
244
+                ->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
245
+                ->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
246
+                ->set('note', $qb->createNamedParameter($share->getNote()))
247
+                ->executeStatement();
248
+
249
+            /*
250 250
 			 * Update all user defined group shares
251 251
 			 */
252
-			$qb = $this->dbConn->getQueryBuilder();
253
-			$qb->update('share')
254
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
255
-				->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
256
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
257
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
258
-				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
259
-				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
260
-				->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
261
-				->set('note', $qb->createNamedParameter($share->getNote()))
262
-				->executeStatement();
263
-
264
-			/*
252
+            $qb = $this->dbConn->getQueryBuilder();
253
+            $qb->update('share')
254
+                ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
255
+                ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
256
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
257
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
258
+                ->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
259
+                ->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
260
+                ->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
261
+                ->set('note', $qb->createNamedParameter($share->getNote()))
262
+                ->executeStatement();
263
+
264
+            /*
265 265
 			 * Now update the permissions for all children that have not set it to 0
266 266
 			 */
267
-			$qb = $this->dbConn->getQueryBuilder();
268
-			$qb->update('share')
269
-				->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
270
-				->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0)))
271
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
272
-				->set('attributes', $qb->createNamedParameter($shareAttributes))
273
-				->executeStatement();
274
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
275
-			$qb = $this->dbConn->getQueryBuilder();
276
-			$qb->update('share')
277
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
278
-				->set('password', $qb->createNamedParameter($share->getPassword()))
279
-				->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
280
-				->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
281
-				->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
282
-				->set('permissions', $qb->createNamedParameter($share->getPermissions()))
283
-				->set('attributes', $qb->createNamedParameter($shareAttributes))
284
-				->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
285
-				->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
286
-				->set('token', $qb->createNamedParameter($share->getToken()))
287
-				->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
288
-				->set('note', $qb->createNamedParameter($share->getNote()))
289
-				->set('label', $qb->createNamedParameter($share->getLabel()))
290
-				->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT))
291
-				->executeStatement();
292
-		}
293
-
294
-		if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
295
-			$this->propagateNote($share);
296
-		}
297
-
298
-
299
-		return $share;
300
-	}
301
-
302
-	/**
303
-	 * Accept a share.
304
-	 *
305
-	 * @param IShare $share
306
-	 * @param string $recipient
307
-	 * @return IShare The share object
308
-	 * @since 9.0.0
309
-	 */
310
-	public function acceptShare(IShare $share, string $recipient): IShare {
311
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
312
-			$group = $this->groupManager->get($share->getSharedWith());
313
-			$user = $this->userManager->get($recipient);
314
-
315
-			if (is_null($group)) {
316
-				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
317
-			}
318
-
319
-			if (!$group->inGroup($user)) {
320
-				throw new ProviderException('Recipient not in receiving group');
321
-			}
322
-
323
-			// Try to fetch user specific share
324
-			$qb = $this->dbConn->getQueryBuilder();
325
-			$stmt = $qb->select('*')
326
-				->from('share')
327
-				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
328
-				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
329
-				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
330
-				->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
331
-				->executeQuery();
332
-
333
-			$data = $stmt->fetch();
334
-			$stmt->closeCursor();
335
-
336
-			/*
267
+            $qb = $this->dbConn->getQueryBuilder();
268
+            $qb->update('share')
269
+                ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
270
+                ->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0)))
271
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
272
+                ->set('attributes', $qb->createNamedParameter($shareAttributes))
273
+                ->executeStatement();
274
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
275
+            $qb = $this->dbConn->getQueryBuilder();
276
+            $qb->update('share')
277
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
278
+                ->set('password', $qb->createNamedParameter($share->getPassword()))
279
+                ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
280
+                ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
281
+                ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
282
+                ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
283
+                ->set('attributes', $qb->createNamedParameter($shareAttributes))
284
+                ->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
285
+                ->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
286
+                ->set('token', $qb->createNamedParameter($share->getToken()))
287
+                ->set('expiration', $qb->createNamedParameter($expirationDate, IQueryBuilder::PARAM_DATETIME_MUTABLE))
288
+                ->set('note', $qb->createNamedParameter($share->getNote()))
289
+                ->set('label', $qb->createNamedParameter($share->getLabel()))
290
+                ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0, IQueryBuilder::PARAM_INT))
291
+                ->executeStatement();
292
+        }
293
+
294
+        if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
295
+            $this->propagateNote($share);
296
+        }
297
+
298
+
299
+        return $share;
300
+    }
301
+
302
+    /**
303
+     * Accept a share.
304
+     *
305
+     * @param IShare $share
306
+     * @param string $recipient
307
+     * @return IShare The share object
308
+     * @since 9.0.0
309
+     */
310
+    public function acceptShare(IShare $share, string $recipient): IShare {
311
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
312
+            $group = $this->groupManager->get($share->getSharedWith());
313
+            $user = $this->userManager->get($recipient);
314
+
315
+            if (is_null($group)) {
316
+                throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
317
+            }
318
+
319
+            if (!$group->inGroup($user)) {
320
+                throw new ProviderException('Recipient not in receiving group');
321
+            }
322
+
323
+            // Try to fetch user specific share
324
+            $qb = $this->dbConn->getQueryBuilder();
325
+            $stmt = $qb->select('*')
326
+                ->from('share')
327
+                ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
328
+                ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
329
+                ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
330
+                ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
331
+                ->executeQuery();
332
+
333
+            $data = $stmt->fetch();
334
+            $stmt->closeCursor();
335
+
336
+            /*
337 337
 			 * Check if there already is a user specific group share.
338 338
 			 * If there is update it (if required).
339 339
 			 */
340
-			if ($data === false) {
341
-				$id = $this->createUserSpecificGroupShare($share, $recipient);
342
-			} else {
343
-				$id = $data['id'];
344
-			}
345
-		} elseif ($share->getShareType() === IShare::TYPE_USER) {
346
-			if ($share->getSharedWith() !== $recipient) {
347
-				throw new ProviderException('Recipient does not match');
348
-			}
349
-
350
-			$id = $share->getId();
351
-		} else {
352
-			throw new ProviderException('Invalid shareType');
353
-		}
354
-
355
-		$qb = $this->dbConn->getQueryBuilder();
356
-		$qb->update('share')
357
-			->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED))
358
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
359
-			->executeStatement();
360
-
361
-		return $share;
362
-	}
363
-
364
-	public function getChildren(IShare $parent): array {
365
-		$children = [];
366
-
367
-		$qb = $this->dbConn->getQueryBuilder();
368
-		$qb->select('*')
369
-			->from('share')
370
-			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
371
-			->andWhere(
372
-				$qb->expr()->in(
373
-					'share_type',
374
-					$qb->createNamedParameter([
375
-						IShare::TYPE_USER,
376
-						IShare::TYPE_GROUP,
377
-						IShare::TYPE_LINK,
378
-					], IQueryBuilder::PARAM_INT_ARRAY)
379
-				)
380
-			)
381
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
382
-			->orderBy('id');
383
-
384
-		$cursor = $qb->executeQuery();
385
-		while ($data = $cursor->fetch()) {
386
-			$children[] = $this->createShare($data);
387
-		}
388
-		$cursor->closeCursor();
389
-
390
-		return $children;
391
-	}
392
-
393
-	/**
394
-	 * Delete a share
395
-	 *
396
-	 * @param \OCP\Share\IShare $share
397
-	 */
398
-	public function delete(\OCP\Share\IShare $share) {
399
-		$qb = $this->dbConn->getQueryBuilder();
400
-		$qb->delete('share')
401
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
402
-
403
-		/*
340
+            if ($data === false) {
341
+                $id = $this->createUserSpecificGroupShare($share, $recipient);
342
+            } else {
343
+                $id = $data['id'];
344
+            }
345
+        } elseif ($share->getShareType() === IShare::TYPE_USER) {
346
+            if ($share->getSharedWith() !== $recipient) {
347
+                throw new ProviderException('Recipient does not match');
348
+            }
349
+
350
+            $id = $share->getId();
351
+        } else {
352
+            throw new ProviderException('Invalid shareType');
353
+        }
354
+
355
+        $qb = $this->dbConn->getQueryBuilder();
356
+        $qb->update('share')
357
+            ->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED))
358
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
359
+            ->executeStatement();
360
+
361
+        return $share;
362
+    }
363
+
364
+    public function getChildren(IShare $parent): array {
365
+        $children = [];
366
+
367
+        $qb = $this->dbConn->getQueryBuilder();
368
+        $qb->select('*')
369
+            ->from('share')
370
+            ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
371
+            ->andWhere(
372
+                $qb->expr()->in(
373
+                    'share_type',
374
+                    $qb->createNamedParameter([
375
+                        IShare::TYPE_USER,
376
+                        IShare::TYPE_GROUP,
377
+                        IShare::TYPE_LINK,
378
+                    ], IQueryBuilder::PARAM_INT_ARRAY)
379
+                )
380
+            )
381
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
382
+            ->orderBy('id');
383
+
384
+        $cursor = $qb->executeQuery();
385
+        while ($data = $cursor->fetch()) {
386
+            $children[] = $this->createShare($data);
387
+        }
388
+        $cursor->closeCursor();
389
+
390
+        return $children;
391
+    }
392
+
393
+    /**
394
+     * Delete a share
395
+     *
396
+     * @param \OCP\Share\IShare $share
397
+     */
398
+    public function delete(\OCP\Share\IShare $share) {
399
+        $qb = $this->dbConn->getQueryBuilder();
400
+        $qb->delete('share')
401
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())));
402
+
403
+        /*
404 404
 		 * If the share is a group share delete all possible
405 405
 		 * user defined groups shares.
406 406
 		 */
407
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
408
-			$qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
409
-		}
410
-
411
-		$qb->executeStatement();
412
-	}
413
-
414
-	/**
415
-	 * Unshare a share from the recipient. If this is a group share
416
-	 * this means we need a special entry in the share db.
417
-	 *
418
-	 * @param IShare $share
419
-	 * @param string $recipient UserId of recipient
420
-	 * @throws BackendError
421
-	 * @throws ProviderException
422
-	 */
423
-	public function deleteFromSelf(IShare $share, $recipient) {
424
-		if ($share->getShareType() === IShare::TYPE_GROUP) {
425
-			$group = $this->groupManager->get($share->getSharedWith());
426
-			$user = $this->userManager->get($recipient);
427
-
428
-			if (is_null($group)) {
429
-				throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
430
-			}
431
-
432
-			if (!$group->inGroup($user)) {
433
-				// nothing left to do
434
-				return;
435
-			}
436
-
437
-			// Try to fetch user specific share
438
-			$qb = $this->dbConn->getQueryBuilder();
439
-			$stmt = $qb->select('*')
440
-				->from('share')
441
-				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
442
-				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
443
-				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
444
-				->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
445
-				->executeQuery();
446
-
447
-			$data = $stmt->fetch();
448
-
449
-			/*
407
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
408
+            $qb->orWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
409
+        }
410
+
411
+        $qb->executeStatement();
412
+    }
413
+
414
+    /**
415
+     * Unshare a share from the recipient. If this is a group share
416
+     * this means we need a special entry in the share db.
417
+     *
418
+     * @param IShare $share
419
+     * @param string $recipient UserId of recipient
420
+     * @throws BackendError
421
+     * @throws ProviderException
422
+     */
423
+    public function deleteFromSelf(IShare $share, $recipient) {
424
+        if ($share->getShareType() === IShare::TYPE_GROUP) {
425
+            $group = $this->groupManager->get($share->getSharedWith());
426
+            $user = $this->userManager->get($recipient);
427
+
428
+            if (is_null($group)) {
429
+                throw new ProviderException('Group "' . $share->getSharedWith() . '" does not exist');
430
+            }
431
+
432
+            if (!$group->inGroup($user)) {
433
+                // nothing left to do
434
+                return;
435
+            }
436
+
437
+            // Try to fetch user specific share
438
+            $qb = $this->dbConn->getQueryBuilder();
439
+            $stmt = $qb->select('*')
440
+                ->from('share')
441
+                ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
442
+                ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
443
+                ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
444
+                ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
445
+                ->executeQuery();
446
+
447
+            $data = $stmt->fetch();
448
+
449
+            /*
450 450
 			 * Check if there already is a user specific group share.
451 451
 			 * If there is update it (if required).
452 452
 			 */
453
-			if ($data === false) {
454
-				$id = $this->createUserSpecificGroupShare($share, $recipient);
455
-				$permissions = $share->getPermissions();
456
-			} else {
457
-				$permissions = $data['permissions'];
458
-				$id = $data['id'];
459
-			}
460
-
461
-			if ($permissions !== 0) {
462
-				// Update existing usergroup share
463
-				$qb = $this->dbConn->getQueryBuilder();
464
-				$qb->update('share')
465
-					->set('permissions', $qb->createNamedParameter(0))
466
-					->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
467
-					->executeStatement();
468
-			}
469
-		} elseif ($share->getShareType() === IShare::TYPE_USER) {
470
-			if ($share->getSharedWith() !== $recipient) {
471
-				throw new ProviderException('Recipient does not match');
472
-			}
473
-
474
-			// We can just delete user and link shares
475
-			$this->delete($share);
476
-		} else {
477
-			throw new ProviderException('Invalid shareType');
478
-		}
479
-	}
480
-
481
-	protected function createUserSpecificGroupShare(IShare $share, string $recipient): int {
482
-		$type = $share->getNodeType();
483
-
484
-		$shareFolder = $this->config->getSystemValue('share_folder', '/');
485
-		$allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
486
-		if ($allowCustomShareFolder) {
487
-			$shareFolder = $this->config->getUserValue($recipient, Application::APP_ID, 'share_folder', $shareFolder);
488
-		}
489
-
490
-		$target = $shareFolder . '/' . $share->getNode()->getName();
491
-		$target = \OC\Files\Filesystem::normalizePath($target);
492
-
493
-		$qb = $this->dbConn->getQueryBuilder();
494
-		$qb->insert('share')
495
-			->values([
496
-				'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
497
-				'share_with' => $qb->createNamedParameter($recipient),
498
-				'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
499
-				'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
500
-				'parent' => $qb->createNamedParameter($share->getId()),
501
-				'item_type' => $qb->createNamedParameter($type),
502
-				'item_source' => $qb->createNamedParameter($share->getNodeId()),
503
-				'file_source' => $qb->createNamedParameter($share->getNodeId()),
504
-				'file_target' => $qb->createNamedParameter($target),
505
-				'permissions' => $qb->createNamedParameter($share->getPermissions()),
506
-				'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
507
-			])->executeStatement();
508
-
509
-		return $qb->getLastInsertId();
510
-	}
511
-
512
-	/**
513
-	 * @inheritdoc
514
-	 *
515
-	 * For now this only works for group shares
516
-	 * If this gets implemented for normal shares we have to extend it
517
-	 */
518
-	public function restore(IShare $share, string $recipient): IShare {
519
-		$qb = $this->dbConn->getQueryBuilder();
520
-		$qb->select('permissions')
521
-			->from('share')
522
-			->where(
523
-				$qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))
524
-			);
525
-		$cursor = $qb->executeQuery();
526
-		$data = $cursor->fetch();
527
-		$cursor->closeCursor();
528
-
529
-		$originalPermission = $data['permissions'];
530
-
531
-		$qb = $this->dbConn->getQueryBuilder();
532
-		$qb->update('share')
533
-			->set('permissions', $qb->createNamedParameter($originalPermission))
534
-			->where(
535
-				$qb->expr()->eq('parent', $qb->createNamedParameter($share->getParent()))
536
-			)->andWhere(
537
-				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))
538
-			)->andWhere(
539
-				$qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))
540
-			);
541
-
542
-		$qb->executeStatement();
543
-
544
-		return $this->getShareById($share->getId(), $recipient);
545
-	}
546
-
547
-	/**
548
-	 * @inheritdoc
549
-	 */
550
-	public function move(\OCP\Share\IShare $share, $recipient) {
551
-		if ($share->getShareType() === IShare::TYPE_USER) {
552
-			// Just update the target
553
-			$qb = $this->dbConn->getQueryBuilder();
554
-			$qb->update('share')
555
-				->set('file_target', $qb->createNamedParameter($share->getTarget()))
556
-				->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
557
-				->executeStatement();
558
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
559
-			// Check if there is a usergroup share
560
-			$qb = $this->dbConn->getQueryBuilder();
561
-			$stmt = $qb->select('id')
562
-				->from('share')
563
-				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
564
-				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
565
-				->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
566
-				->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
567
-				->setMaxResults(1)
568
-				->executeQuery();
569
-
570
-			$data = $stmt->fetch();
571
-			$stmt->closeCursor();
572
-
573
-			$shareAttributes = $this->formatShareAttributes(
574
-				$share->getAttributes()
575
-			);
576
-
577
-			if ($data === false) {
578
-				// No usergroup share yet. Create one.
579
-				$qb = $this->dbConn->getQueryBuilder();
580
-				$qb->insert('share')
581
-					->values([
582
-						'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
583
-						'share_with' => $qb->createNamedParameter($recipient),
584
-						'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
585
-						'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
586
-						'parent' => $qb->createNamedParameter($share->getId()),
587
-						'item_type' => $qb->createNamedParameter($share->getNodeType()),
588
-						'item_source' => $qb->createNamedParameter($share->getNodeId()),
589
-						'file_source' => $qb->createNamedParameter($share->getNodeId()),
590
-						'file_target' => $qb->createNamedParameter($share->getTarget()),
591
-						'permissions' => $qb->createNamedParameter($share->getPermissions()),
592
-						'attributes' => $qb->createNamedParameter($shareAttributes),
593
-						'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
594
-					])->executeStatement();
595
-			} else {
596
-				// Already a usergroup share. Update it.
597
-				$qb = $this->dbConn->getQueryBuilder();
598
-				$qb->update('share')
599
-					->set('file_target', $qb->createNamedParameter($share->getTarget()))
600
-					->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
601
-					->executeStatement();
602
-			}
603
-		}
604
-
605
-		return $share;
606
-	}
607
-
608
-	public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true) {
609
-		if (!$shallow) {
610
-			throw new \Exception('non-shallow getSharesInFolder is no longer supported');
611
-		}
612
-
613
-		return $this->getSharesInFolderInternal($userId, $node, $reshares);
614
-	}
615
-
616
-	public function getAllSharesInFolder(Folder $node): array {
617
-		return $this->getSharesInFolderInternal(null, $node, null);
618
-	}
619
-
620
-	/**
621
-	 * @return array<int, list<IShare>>
622
-	 */
623
-	private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array {
624
-		$qb = $this->dbConn->getQueryBuilder();
625
-		$qb->select('s.*',
626
-			'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
627
-			'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
628
-			'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum')
629
-			->from('share', 's')
630
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
631
-
632
-		$qb->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)));
633
-
634
-		if ($userId !== null) {
635
-			/**
636
-			 * Reshares for this user are shares where they are the owner.
637
-			 */
638
-			if ($reshares !== true) {
639
-				$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
640
-			} else {
641
-				$qb->andWhere(
642
-					$qb->expr()->orX(
643
-						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
644
-						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
645
-					)
646
-				);
647
-			}
648
-		}
649
-
650
-		// todo? maybe get these from the oc_mounts table
651
-		$childMountNodes = array_filter($node->getDirectoryListing(), function (Node $node): bool {
652
-			return $node->getInternalPath() === '';
653
-		});
654
-		$childMountRootIds = array_map(function (Node $node): int {
655
-			return $node->getId();
656
-		}, $childMountNodes);
657
-
658
-		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
659
-		$qb->andWhere(
660
-			$qb->expr()->orX(
661
-				$qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())),
662
-				$qb->expr()->in('f.fileid', $qb->createParameter('chunk'))
663
-			)
664
-		);
665
-
666
-		$qb->orderBy('id');
667
-
668
-		$shares = [];
669
-
670
-		$chunks = array_chunk($childMountRootIds, 1000);
671
-
672
-		// Force the request to be run when there is 0 mount.
673
-		if (count($chunks) === 0) {
674
-			$chunks = [[]];
675
-		}
676
-
677
-		foreach ($chunks as $chunk) {
678
-			$qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
679
-			$cursor = $qb->executeQuery();
680
-			while ($data = $cursor->fetch()) {
681
-				$shares[$data['fileid']][] = $this->createShare($data);
682
-			}
683
-			$cursor->closeCursor();
684
-		}
685
-
686
-		return $shares;
687
-	}
688
-
689
-	/**
690
-	 * @inheritdoc
691
-	 */
692
-	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
693
-		$qb = $this->dbConn->getQueryBuilder();
694
-		$qb->select('*')
695
-			->from('share')
696
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
697
-
698
-		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
699
-
700
-		/**
701
-		 * Reshares for this user are shares where they are the owner.
702
-		 */
703
-		if ($reshares === false) {
704
-			$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
705
-		} else {
706
-			if ($node === null) {
707
-				$qb->andWhere(
708
-					$qb->expr()->orX(
709
-						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
710
-						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
711
-					)
712
-				);
713
-			}
714
-		}
715
-
716
-		if ($node !== null) {
717
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
718
-		}
719
-
720
-		if ($limit !== -1) {
721
-			$qb->setMaxResults($limit);
722
-		}
723
-
724
-		$qb->setFirstResult($offset);
725
-		$qb->orderBy('id');
726
-
727
-		$cursor = $qb->executeQuery();
728
-		$shares = [];
729
-		while ($data = $cursor->fetch()) {
730
-			$shares[] = $this->createShare($data);
731
-		}
732
-		$cursor->closeCursor();
733
-
734
-		return $shares;
735
-	}
736
-
737
-	/**
738
-	 * @inheritdoc
739
-	 */
740
-	public function getShareById($id, $recipientId = null) {
741
-		$qb = $this->dbConn->getQueryBuilder();
742
-
743
-		$qb->select('*')
744
-			->from('share')
745
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
746
-			->andWhere(
747
-				$qb->expr()->in(
748
-					'share_type',
749
-					$qb->createNamedParameter([
750
-						IShare::TYPE_USER,
751
-						IShare::TYPE_GROUP,
752
-						IShare::TYPE_LINK,
753
-					], IQueryBuilder::PARAM_INT_ARRAY)
754
-				)
755
-			)
756
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
757
-
758
-		$cursor = $qb->executeQuery();
759
-		$data = $cursor->fetch();
760
-		$cursor->closeCursor();
761
-
762
-		if ($data === false) {
763
-			throw new ShareNotFound();
764
-		}
765
-
766
-		try {
767
-			$share = $this->createShare($data);
768
-		} catch (InvalidShare $e) {
769
-			throw new ShareNotFound();
770
-		}
771
-
772
-		// If the recipient is set for a group share resolve to that user
773
-		if ($recipientId !== null && $share->getShareType() === IShare::TYPE_GROUP) {
774
-			$share = $this->resolveGroupShares([(int)$share->getId() => $share], $recipientId)[0];
775
-		}
776
-
777
-		return $share;
778
-	}
779
-
780
-	/**
781
-	 * Get shares for a given path
782
-	 *
783
-	 * @param \OCP\Files\Node $path
784
-	 * @return \OCP\Share\IShare[]
785
-	 */
786
-	public function getSharesByPath(Node $path) {
787
-		$qb = $this->dbConn->getQueryBuilder();
788
-
789
-		$cursor = $qb->select('*')
790
-			->from('share')
791
-			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
792
-			->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)))
793
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
794
-			->orderBy('id', 'ASC')
795
-			->executeQuery();
796
-
797
-		$shares = [];
798
-		while ($data = $cursor->fetch()) {
799
-			$shares[] = $this->createShare($data);
800
-		}
801
-		$cursor->closeCursor();
802
-
803
-		return $shares;
804
-	}
805
-
806
-	/**
807
-	 * Returns whether the given database result can be interpreted as
808
-	 * a share with accessible file (not trashed, not deleted)
809
-	 */
810
-	private function isAccessibleResult($data) {
811
-		// exclude shares leading to deleted file entries
812
-		if ($data['fileid'] === null || $data['path'] === null) {
813
-			return false;
814
-		}
815
-
816
-		// exclude shares leading to trashbin on home storages
817
-		$pathSections = explode('/', $data['path'], 2);
818
-		// FIXME: would not detect rare md5'd home storage case properly
819
-		if ($pathSections[0] !== 'files'
820
-			&& (str_starts_with($data['storage_string_id'], 'home::') || str_starts_with($data['storage_string_id'], 'object::user'))) {
821
-			return false;
822
-		} elseif ($pathSections[0] === '__groupfolders'
823
-			&& str_starts_with($pathSections[1], 'trash/')
824
-		) {
825
-			// exclude shares leading to trashbin on group folders storages
826
-			return false;
827
-		}
828
-		return true;
829
-	}
830
-
831
-	/**
832
-	 * @inheritdoc
833
-	 */
834
-	public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
835
-		/** @var Share[] $shares */
836
-		$shares = [];
837
-
838
-		if ($shareType === IShare::TYPE_USER) {
839
-			//Get shares directly with this user
840
-			$qb = $this->dbConn->getQueryBuilder();
841
-			$qb->select('s.*',
842
-				'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
843
-				'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
844
-				'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
845
-			)
846
-				->selectAlias('st.id', 'storage_string_id')
847
-				->from('share', 's')
848
-				->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
849
-				->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'));
850
-
851
-			// Order by id
852
-			$qb->orderBy('s.id');
853
-
854
-			// Set limit and offset
855
-			if ($limit !== -1) {
856
-				$qb->setMaxResults($limit);
857
-			}
858
-			$qb->setFirstResult($offset);
859
-
860
-			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)))
861
-				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
862
-				->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
863
-
864
-			// Filter by node if provided
865
-			if ($node !== null) {
866
-				$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
867
-			}
868
-
869
-			$cursor = $qb->executeQuery();
870
-
871
-			while ($data = $cursor->fetch()) {
872
-				if ($data['fileid'] && $data['path'] === null) {
873
-					$data['path'] = (string)$data['path'];
874
-					$data['name'] = (string)$data['name'];
875
-					$data['checksum'] = (string)$data['checksum'];
876
-				}
877
-				if ($this->isAccessibleResult($data)) {
878
-					$shares[] = $this->createShare($data);
879
-				}
880
-			}
881
-			$cursor->closeCursor();
882
-		} elseif ($shareType === IShare::TYPE_GROUP) {
883
-			$user = new LazyUser($userId, $this->userManager);
884
-			$allGroups = $this->groupManager->getUserGroupIds($user);
885
-
886
-			/** @var Share[] $shares2 */
887
-			$shares2 = [];
888
-
889
-			$start = 0;
890
-			while (true) {
891
-				$groups = array_slice($allGroups, $start, 1000);
892
-				$start += 1000;
893
-
894
-				if ($groups === []) {
895
-					break;
896
-				}
897
-
898
-				$qb = $this->dbConn->getQueryBuilder();
899
-				$qb->select('s.*',
900
-					'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
901
-					'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
902
-					'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
903
-				)
904
-					->selectAlias('st.id', 'storage_string_id')
905
-					->from('share', 's')
906
-					->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
907
-					->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
908
-					->orderBy('s.id')
909
-					->setFirstResult(0);
910
-
911
-				if ($limit !== -1) {
912
-					$qb->setMaxResults($limit - count($shares));
913
-				}
914
-
915
-				// Filter by node if provided
916
-				if ($node !== null) {
917
-					$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
918
-				}
919
-
920
-				$groups = array_filter($groups);
921
-
922
-				$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
923
-					->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
924
-						$groups,
925
-						IQueryBuilder::PARAM_STR_ARRAY
926
-					)))
927
-					->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
928
-
929
-				$cursor = $qb->executeQuery();
930
-				while ($data = $cursor->fetch()) {
931
-					if ($offset > 0) {
932
-						$offset--;
933
-						continue;
934
-					}
935
-
936
-					if ($this->isAccessibleResult($data)) {
937
-						$share = $this->createShare($data);
938
-						$shares2[$share->getId()] = $share;
939
-					}
940
-				}
941
-				$cursor->closeCursor();
942
-			}
943
-
944
-			/*
453
+            if ($data === false) {
454
+                $id = $this->createUserSpecificGroupShare($share, $recipient);
455
+                $permissions = $share->getPermissions();
456
+            } else {
457
+                $permissions = $data['permissions'];
458
+                $id = $data['id'];
459
+            }
460
+
461
+            if ($permissions !== 0) {
462
+                // Update existing usergroup share
463
+                $qb = $this->dbConn->getQueryBuilder();
464
+                $qb->update('share')
465
+                    ->set('permissions', $qb->createNamedParameter(0))
466
+                    ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
467
+                    ->executeStatement();
468
+            }
469
+        } elseif ($share->getShareType() === IShare::TYPE_USER) {
470
+            if ($share->getSharedWith() !== $recipient) {
471
+                throw new ProviderException('Recipient does not match');
472
+            }
473
+
474
+            // We can just delete user and link shares
475
+            $this->delete($share);
476
+        } else {
477
+            throw new ProviderException('Invalid shareType');
478
+        }
479
+    }
480
+
481
+    protected function createUserSpecificGroupShare(IShare $share, string $recipient): int {
482
+        $type = $share->getNodeType();
483
+
484
+        $shareFolder = $this->config->getSystemValue('share_folder', '/');
485
+        $allowCustomShareFolder = $this->config->getSystemValueBool('sharing.allow_custom_share_folder', true);
486
+        if ($allowCustomShareFolder) {
487
+            $shareFolder = $this->config->getUserValue($recipient, Application::APP_ID, 'share_folder', $shareFolder);
488
+        }
489
+
490
+        $target = $shareFolder . '/' . $share->getNode()->getName();
491
+        $target = \OC\Files\Filesystem::normalizePath($target);
492
+
493
+        $qb = $this->dbConn->getQueryBuilder();
494
+        $qb->insert('share')
495
+            ->values([
496
+                'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
497
+                'share_with' => $qb->createNamedParameter($recipient),
498
+                'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
499
+                'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
500
+                'parent' => $qb->createNamedParameter($share->getId()),
501
+                'item_type' => $qb->createNamedParameter($type),
502
+                'item_source' => $qb->createNamedParameter($share->getNodeId()),
503
+                'file_source' => $qb->createNamedParameter($share->getNodeId()),
504
+                'file_target' => $qb->createNamedParameter($target),
505
+                'permissions' => $qb->createNamedParameter($share->getPermissions()),
506
+                'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
507
+            ])->executeStatement();
508
+
509
+        return $qb->getLastInsertId();
510
+    }
511
+
512
+    /**
513
+     * @inheritdoc
514
+     *
515
+     * For now this only works for group shares
516
+     * If this gets implemented for normal shares we have to extend it
517
+     */
518
+    public function restore(IShare $share, string $recipient): IShare {
519
+        $qb = $this->dbConn->getQueryBuilder();
520
+        $qb->select('permissions')
521
+            ->from('share')
522
+            ->where(
523
+                $qb->expr()->eq('id', $qb->createNamedParameter($share->getId()))
524
+            );
525
+        $cursor = $qb->executeQuery();
526
+        $data = $cursor->fetch();
527
+        $cursor->closeCursor();
528
+
529
+        $originalPermission = $data['permissions'];
530
+
531
+        $qb = $this->dbConn->getQueryBuilder();
532
+        $qb->update('share')
533
+            ->set('permissions', $qb->createNamedParameter($originalPermission))
534
+            ->where(
535
+                $qb->expr()->eq('parent', $qb->createNamedParameter($share->getParent()))
536
+            )->andWhere(
537
+                $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP))
538
+            )->andWhere(
539
+                $qb->expr()->eq('share_with', $qb->createNamedParameter($recipient))
540
+            );
541
+
542
+        $qb->executeStatement();
543
+
544
+        return $this->getShareById($share->getId(), $recipient);
545
+    }
546
+
547
+    /**
548
+     * @inheritdoc
549
+     */
550
+    public function move(\OCP\Share\IShare $share, $recipient) {
551
+        if ($share->getShareType() === IShare::TYPE_USER) {
552
+            // Just update the target
553
+            $qb = $this->dbConn->getQueryBuilder();
554
+            $qb->update('share')
555
+                ->set('file_target', $qb->createNamedParameter($share->getTarget()))
556
+                ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
557
+                ->executeStatement();
558
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
559
+            // Check if there is a usergroup share
560
+            $qb = $this->dbConn->getQueryBuilder();
561
+            $stmt = $qb->select('id')
562
+                ->from('share')
563
+                ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
564
+                ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)))
565
+                ->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
566
+                ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
567
+                ->setMaxResults(1)
568
+                ->executeQuery();
569
+
570
+            $data = $stmt->fetch();
571
+            $stmt->closeCursor();
572
+
573
+            $shareAttributes = $this->formatShareAttributes(
574
+                $share->getAttributes()
575
+            );
576
+
577
+            if ($data === false) {
578
+                // No usergroup share yet. Create one.
579
+                $qb = $this->dbConn->getQueryBuilder();
580
+                $qb->insert('share')
581
+                    ->values([
582
+                        'share_type' => $qb->createNamedParameter(IShare::TYPE_USERGROUP),
583
+                        'share_with' => $qb->createNamedParameter($recipient),
584
+                        'uid_owner' => $qb->createNamedParameter($share->getShareOwner()),
585
+                        'uid_initiator' => $qb->createNamedParameter($share->getSharedBy()),
586
+                        'parent' => $qb->createNamedParameter($share->getId()),
587
+                        'item_type' => $qb->createNamedParameter($share->getNodeType()),
588
+                        'item_source' => $qb->createNamedParameter($share->getNodeId()),
589
+                        'file_source' => $qb->createNamedParameter($share->getNodeId()),
590
+                        'file_target' => $qb->createNamedParameter($share->getTarget()),
591
+                        'permissions' => $qb->createNamedParameter($share->getPermissions()),
592
+                        'attributes' => $qb->createNamedParameter($shareAttributes),
593
+                        'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
594
+                    ])->executeStatement();
595
+            } else {
596
+                // Already a usergroup share. Update it.
597
+                $qb = $this->dbConn->getQueryBuilder();
598
+                $qb->update('share')
599
+                    ->set('file_target', $qb->createNamedParameter($share->getTarget()))
600
+                    ->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
601
+                    ->executeStatement();
602
+            }
603
+        }
604
+
605
+        return $share;
606
+    }
607
+
608
+    public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true) {
609
+        if (!$shallow) {
610
+            throw new \Exception('non-shallow getSharesInFolder is no longer supported');
611
+        }
612
+
613
+        return $this->getSharesInFolderInternal($userId, $node, $reshares);
614
+    }
615
+
616
+    public function getAllSharesInFolder(Folder $node): array {
617
+        return $this->getSharesInFolderInternal(null, $node, null);
618
+    }
619
+
620
+    /**
621
+     * @return array<int, list<IShare>>
622
+     */
623
+    private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array {
624
+        $qb = $this->dbConn->getQueryBuilder();
625
+        $qb->select('s.*',
626
+            'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
627
+            'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
628
+            'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum')
629
+            ->from('share', 's')
630
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
631
+
632
+        $qb->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)));
633
+
634
+        if ($userId !== null) {
635
+            /**
636
+             * Reshares for this user are shares where they are the owner.
637
+             */
638
+            if ($reshares !== true) {
639
+                $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
640
+            } else {
641
+                $qb->andWhere(
642
+                    $qb->expr()->orX(
643
+                        $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
644
+                        $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
645
+                    )
646
+                );
647
+            }
648
+        }
649
+
650
+        // todo? maybe get these from the oc_mounts table
651
+        $childMountNodes = array_filter($node->getDirectoryListing(), function (Node $node): bool {
652
+            return $node->getInternalPath() === '';
653
+        });
654
+        $childMountRootIds = array_map(function (Node $node): int {
655
+            return $node->getId();
656
+        }, $childMountNodes);
657
+
658
+        $qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
659
+        $qb->andWhere(
660
+            $qb->expr()->orX(
661
+                $qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())),
662
+                $qb->expr()->in('f.fileid', $qb->createParameter('chunk'))
663
+            )
664
+        );
665
+
666
+        $qb->orderBy('id');
667
+
668
+        $shares = [];
669
+
670
+        $chunks = array_chunk($childMountRootIds, 1000);
671
+
672
+        // Force the request to be run when there is 0 mount.
673
+        if (count($chunks) === 0) {
674
+            $chunks = [[]];
675
+        }
676
+
677
+        foreach ($chunks as $chunk) {
678
+            $qb->setParameter('chunk', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
679
+            $cursor = $qb->executeQuery();
680
+            while ($data = $cursor->fetch()) {
681
+                $shares[$data['fileid']][] = $this->createShare($data);
682
+            }
683
+            $cursor->closeCursor();
684
+        }
685
+
686
+        return $shares;
687
+    }
688
+
689
+    /**
690
+     * @inheritdoc
691
+     */
692
+    public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset) {
693
+        $qb = $this->dbConn->getQueryBuilder();
694
+        $qb->select('*')
695
+            ->from('share')
696
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
697
+
698
+        $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter($shareType)));
699
+
700
+        /**
701
+         * Reshares for this user are shares where they are the owner.
702
+         */
703
+        if ($reshares === false) {
704
+            $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
705
+        } else {
706
+            if ($node === null) {
707
+                $qb->andWhere(
708
+                    $qb->expr()->orX(
709
+                        $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
710
+                        $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
711
+                    )
712
+                );
713
+            }
714
+        }
715
+
716
+        if ($node !== null) {
717
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
718
+        }
719
+
720
+        if ($limit !== -1) {
721
+            $qb->setMaxResults($limit);
722
+        }
723
+
724
+        $qb->setFirstResult($offset);
725
+        $qb->orderBy('id');
726
+
727
+        $cursor = $qb->executeQuery();
728
+        $shares = [];
729
+        while ($data = $cursor->fetch()) {
730
+            $shares[] = $this->createShare($data);
731
+        }
732
+        $cursor->closeCursor();
733
+
734
+        return $shares;
735
+    }
736
+
737
+    /**
738
+     * @inheritdoc
739
+     */
740
+    public function getShareById($id, $recipientId = null) {
741
+        $qb = $this->dbConn->getQueryBuilder();
742
+
743
+        $qb->select('*')
744
+            ->from('share')
745
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
746
+            ->andWhere(
747
+                $qb->expr()->in(
748
+                    'share_type',
749
+                    $qb->createNamedParameter([
750
+                        IShare::TYPE_USER,
751
+                        IShare::TYPE_GROUP,
752
+                        IShare::TYPE_LINK,
753
+                    ], IQueryBuilder::PARAM_INT_ARRAY)
754
+                )
755
+            )
756
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
757
+
758
+        $cursor = $qb->executeQuery();
759
+        $data = $cursor->fetch();
760
+        $cursor->closeCursor();
761
+
762
+        if ($data === false) {
763
+            throw new ShareNotFound();
764
+        }
765
+
766
+        try {
767
+            $share = $this->createShare($data);
768
+        } catch (InvalidShare $e) {
769
+            throw new ShareNotFound();
770
+        }
771
+
772
+        // If the recipient is set for a group share resolve to that user
773
+        if ($recipientId !== null && $share->getShareType() === IShare::TYPE_GROUP) {
774
+            $share = $this->resolveGroupShares([(int)$share->getId() => $share], $recipientId)[0];
775
+        }
776
+
777
+        return $share;
778
+    }
779
+
780
+    /**
781
+     * Get shares for a given path
782
+     *
783
+     * @param \OCP\Files\Node $path
784
+     * @return \OCP\Share\IShare[]
785
+     */
786
+    public function getSharesByPath(Node $path) {
787
+        $qb = $this->dbConn->getQueryBuilder();
788
+
789
+        $cursor = $qb->select('*')
790
+            ->from('share')
791
+            ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
792
+            ->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)))
793
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
794
+            ->orderBy('id', 'ASC')
795
+            ->executeQuery();
796
+
797
+        $shares = [];
798
+        while ($data = $cursor->fetch()) {
799
+            $shares[] = $this->createShare($data);
800
+        }
801
+        $cursor->closeCursor();
802
+
803
+        return $shares;
804
+    }
805
+
806
+    /**
807
+     * Returns whether the given database result can be interpreted as
808
+     * a share with accessible file (not trashed, not deleted)
809
+     */
810
+    private function isAccessibleResult($data) {
811
+        // exclude shares leading to deleted file entries
812
+        if ($data['fileid'] === null || $data['path'] === null) {
813
+            return false;
814
+        }
815
+
816
+        // exclude shares leading to trashbin on home storages
817
+        $pathSections = explode('/', $data['path'], 2);
818
+        // FIXME: would not detect rare md5'd home storage case properly
819
+        if ($pathSections[0] !== 'files'
820
+            && (str_starts_with($data['storage_string_id'], 'home::') || str_starts_with($data['storage_string_id'], 'object::user'))) {
821
+            return false;
822
+        } elseif ($pathSections[0] === '__groupfolders'
823
+            && str_starts_with($pathSections[1], 'trash/')
824
+        ) {
825
+            // exclude shares leading to trashbin on group folders storages
826
+            return false;
827
+        }
828
+        return true;
829
+    }
830
+
831
+    /**
832
+     * @inheritdoc
833
+     */
834
+    public function getSharedWith($userId, $shareType, $node, $limit, $offset) {
835
+        /** @var Share[] $shares */
836
+        $shares = [];
837
+
838
+        if ($shareType === IShare::TYPE_USER) {
839
+            //Get shares directly with this user
840
+            $qb = $this->dbConn->getQueryBuilder();
841
+            $qb->select('s.*',
842
+                'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
843
+                'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
844
+                'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
845
+            )
846
+                ->selectAlias('st.id', 'storage_string_id')
847
+                ->from('share', 's')
848
+                ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
849
+                ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'));
850
+
851
+            // Order by id
852
+            $qb->orderBy('s.id');
853
+
854
+            // Set limit and offset
855
+            if ($limit !== -1) {
856
+                $qb->setMaxResults($limit);
857
+            }
858
+            $qb->setFirstResult($offset);
859
+
860
+            $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)))
861
+                ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
862
+                ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
863
+
864
+            // Filter by node if provided
865
+            if ($node !== null) {
866
+                $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
867
+            }
868
+
869
+            $cursor = $qb->executeQuery();
870
+
871
+            while ($data = $cursor->fetch()) {
872
+                if ($data['fileid'] && $data['path'] === null) {
873
+                    $data['path'] = (string)$data['path'];
874
+                    $data['name'] = (string)$data['name'];
875
+                    $data['checksum'] = (string)$data['checksum'];
876
+                }
877
+                if ($this->isAccessibleResult($data)) {
878
+                    $shares[] = $this->createShare($data);
879
+                }
880
+            }
881
+            $cursor->closeCursor();
882
+        } elseif ($shareType === IShare::TYPE_GROUP) {
883
+            $user = new LazyUser($userId, $this->userManager);
884
+            $allGroups = $this->groupManager->getUserGroupIds($user);
885
+
886
+            /** @var Share[] $shares2 */
887
+            $shares2 = [];
888
+
889
+            $start = 0;
890
+            while (true) {
891
+                $groups = array_slice($allGroups, $start, 1000);
892
+                $start += 1000;
893
+
894
+                if ($groups === []) {
895
+                    break;
896
+                }
897
+
898
+                $qb = $this->dbConn->getQueryBuilder();
899
+                $qb->select('s.*',
900
+                    'f.fileid', 'f.path', 'f.permissions AS f_permissions', 'f.storage', 'f.path_hash',
901
+                    'f.parent AS f_parent', 'f.name', 'f.mimetype', 'f.mimepart', 'f.size', 'f.mtime', 'f.storage_mtime',
902
+                    'f.encrypted', 'f.unencrypted_size', 'f.etag', 'f.checksum'
903
+                )
904
+                    ->selectAlias('st.id', 'storage_string_id')
905
+                    ->from('share', 's')
906
+                    ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
907
+                    ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
908
+                    ->orderBy('s.id')
909
+                    ->setFirstResult(0);
910
+
911
+                if ($limit !== -1) {
912
+                    $qb->setMaxResults($limit - count($shares));
913
+                }
914
+
915
+                // Filter by node if provided
916
+                if ($node !== null) {
917
+                    $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
918
+                }
919
+
920
+                $groups = array_filter($groups);
921
+
922
+                $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
923
+                    ->andWhere($qb->expr()->in('share_with', $qb->createNamedParameter(
924
+                        $groups,
925
+                        IQueryBuilder::PARAM_STR_ARRAY
926
+                    )))
927
+                    ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
928
+
929
+                $cursor = $qb->executeQuery();
930
+                while ($data = $cursor->fetch()) {
931
+                    if ($offset > 0) {
932
+                        $offset--;
933
+                        continue;
934
+                    }
935
+
936
+                    if ($this->isAccessibleResult($data)) {
937
+                        $share = $this->createShare($data);
938
+                        $shares2[$share->getId()] = $share;
939
+                    }
940
+                }
941
+                $cursor->closeCursor();
942
+            }
943
+
944
+            /*
945 945
 			 * Resolve all group shares to user specific shares
946 946
 			 */
947
-			$shares = $this->resolveGroupShares($shares2, $userId);
948
-		} else {
949
-			throw new BackendError('Invalid backend');
950
-		}
951
-
952
-
953
-		return $shares;
954
-	}
955
-
956
-	/**
957
-	 * Get a share by token
958
-	 *
959
-	 * @param string $token
960
-	 * @return \OCP\Share\IShare
961
-	 * @throws ShareNotFound
962
-	 */
963
-	public function getShareByToken($token) {
964
-		$qb = $this->dbConn->getQueryBuilder();
965
-
966
-		$cursor = $qb->select('*')
967
-			->from('share')
968
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)))
969
-			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
970
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
971
-			->executeQuery();
972
-
973
-		$data = $cursor->fetch();
974
-
975
-		if ($data === false) {
976
-			throw new ShareNotFound();
977
-		}
978
-
979
-		try {
980
-			$share = $this->createShare($data);
981
-		} catch (InvalidShare $e) {
982
-			throw new ShareNotFound();
983
-		}
984
-
985
-		return $share;
986
-	}
987
-
988
-	/**
989
-	 * Create a share object from a database row
990
-	 *
991
-	 * @param mixed[] $data
992
-	 * @return \OCP\Share\IShare
993
-	 * @throws InvalidShare
994
-	 */
995
-	private function createShare($data) {
996
-		$share = new Share($this->rootFolder, $this->userManager);
997
-		$share->setId($data['id'])
998
-			->setShareType((int)$data['share_type'])
999
-			->setPermissions((int)$data['permissions'])
1000
-			->setTarget($data['file_target'])
1001
-			->setNote((string)$data['note'])
1002
-			->setMailSend((bool)$data['mail_send'])
1003
-			->setStatus((int)$data['accepted'])
1004
-			->setLabel($data['label'] ?? '');
1005
-
1006
-		$shareTime = new \DateTime();
1007
-		$shareTime->setTimestamp((int)$data['stime']);
1008
-		$share->setShareTime($shareTime);
1009
-
1010
-		if ($share->getShareType() === IShare::TYPE_USER) {
1011
-			$share->setSharedWith($data['share_with']);
1012
-			$displayName = $this->userManager->getDisplayName($data['share_with']);
1013
-			if ($displayName !== null) {
1014
-				$share->setSharedWithDisplayName($displayName);
1015
-			}
1016
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1017
-			$share->setSharedWith($data['share_with']);
1018
-			$group = $this->groupManager->get($data['share_with']);
1019
-			if ($group !== null) {
1020
-				$share->setSharedWithDisplayName($group->getDisplayName());
1021
-			}
1022
-		} elseif ($share->getShareType() === IShare::TYPE_LINK) {
1023
-			$share->setPassword($data['password']);
1024
-			$share->setSendPasswordByTalk((bool)$data['password_by_talk']);
1025
-			$share->setToken($data['token']);
1026
-		}
1027
-
1028
-		$share = $this->updateShareAttributes($share, $data['attributes']);
1029
-
1030
-		$share->setSharedBy($data['uid_initiator']);
1031
-		$share->setShareOwner($data['uid_owner']);
1032
-
1033
-		$share->setNodeId((int)$data['file_source']);
1034
-		$share->setNodeType($data['item_type']);
1035
-
1036
-		if ($data['expiration'] !== null) {
1037
-			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
1038
-			$share->setExpirationDate($expiration);
1039
-		}
1040
-
1041
-		if (isset($data['f_permissions'])) {
1042
-			$entryData = $data;
1043
-			$entryData['permissions'] = $entryData['f_permissions'];
1044
-			$entryData['parent'] = $entryData['f_parent'];
1045
-			$share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData,
1046
-				\OC::$server->getMimeTypeLoader()));
1047
-		}
1048
-
1049
-		$share->setProviderId($this->identifier());
1050
-		$share->setHideDownload((int)$data['hide_download'] === 1);
1051
-		$share->setReminderSent((bool)$data['reminder_sent']);
1052
-
1053
-		return $share;
1054
-	}
1055
-
1056
-	/**
1057
-	 * Update the data from group shares with any per-user modifications
1058
-	 *
1059
-	 * @param array<int, Share> $shareMap shares indexed by share id
1060
-	 * @param $userId
1061
-	 * @return Share[] The updates shares if no update is found for a share return the original
1062
-	 */
1063
-	private function resolveGroupShares($shareMap, $userId) {
1064
-		$qb = $this->dbConn->getQueryBuilder();
1065
-		$query = $qb->select('*')
1066
-			->from('share')
1067
-			->where($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
1068
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1069
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1070
-
1071
-		// this is called with either all group shares or one group share.
1072
-		// for all shares it's easier to just only search by share_with,
1073
-		// for a single share it's efficient to filter by parent
1074
-		if (count($shareMap) === 1) {
1075
-			$share = reset($shareMap);
1076
-			$query->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
1077
-		}
1078
-
1079
-		$stmt = $query->executeQuery();
1080
-
1081
-		while ($data = $stmt->fetch()) {
1082
-			if (array_key_exists($data['parent'], $shareMap)) {
1083
-				$shareMap[$data['parent']]->setPermissions((int)$data['permissions']);
1084
-				$shareMap[$data['parent']]->setStatus((int)$data['accepted']);
1085
-				$shareMap[$data['parent']]->setTarget($data['file_target']);
1086
-				$shareMap[$data['parent']]->setParent($data['parent']);
1087
-			}
1088
-		}
1089
-
1090
-		return array_values($shareMap);
1091
-	}
1092
-
1093
-	/**
1094
-	 * A user is deleted from the system
1095
-	 * So clean up the relevant shares.
1096
-	 *
1097
-	 * @param string $uid
1098
-	 * @param int $shareType
1099
-	 */
1100
-	public function userDeleted($uid, $shareType) {
1101
-		$qb = $this->dbConn->getQueryBuilder();
1102
-
1103
-		$qb->delete('share');
1104
-
1105
-		if ($shareType === IShare::TYPE_USER) {
1106
-			/*
947
+            $shares = $this->resolveGroupShares($shares2, $userId);
948
+        } else {
949
+            throw new BackendError('Invalid backend');
950
+        }
951
+
952
+
953
+        return $shares;
954
+    }
955
+
956
+    /**
957
+     * Get a share by token
958
+     *
959
+     * @param string $token
960
+     * @return \OCP\Share\IShare
961
+     * @throws ShareNotFound
962
+     */
963
+    public function getShareByToken($token) {
964
+        $qb = $this->dbConn->getQueryBuilder();
965
+
966
+        $cursor = $qb->select('*')
967
+            ->from('share')
968
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)))
969
+            ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
970
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
971
+            ->executeQuery();
972
+
973
+        $data = $cursor->fetch();
974
+
975
+        if ($data === false) {
976
+            throw new ShareNotFound();
977
+        }
978
+
979
+        try {
980
+            $share = $this->createShare($data);
981
+        } catch (InvalidShare $e) {
982
+            throw new ShareNotFound();
983
+        }
984
+
985
+        return $share;
986
+    }
987
+
988
+    /**
989
+     * Create a share object from a database row
990
+     *
991
+     * @param mixed[] $data
992
+     * @return \OCP\Share\IShare
993
+     * @throws InvalidShare
994
+     */
995
+    private function createShare($data) {
996
+        $share = new Share($this->rootFolder, $this->userManager);
997
+        $share->setId($data['id'])
998
+            ->setShareType((int)$data['share_type'])
999
+            ->setPermissions((int)$data['permissions'])
1000
+            ->setTarget($data['file_target'])
1001
+            ->setNote((string)$data['note'])
1002
+            ->setMailSend((bool)$data['mail_send'])
1003
+            ->setStatus((int)$data['accepted'])
1004
+            ->setLabel($data['label'] ?? '');
1005
+
1006
+        $shareTime = new \DateTime();
1007
+        $shareTime->setTimestamp((int)$data['stime']);
1008
+        $share->setShareTime($shareTime);
1009
+
1010
+        if ($share->getShareType() === IShare::TYPE_USER) {
1011
+            $share->setSharedWith($data['share_with']);
1012
+            $displayName = $this->userManager->getDisplayName($data['share_with']);
1013
+            if ($displayName !== null) {
1014
+                $share->setSharedWithDisplayName($displayName);
1015
+            }
1016
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1017
+            $share->setSharedWith($data['share_with']);
1018
+            $group = $this->groupManager->get($data['share_with']);
1019
+            if ($group !== null) {
1020
+                $share->setSharedWithDisplayName($group->getDisplayName());
1021
+            }
1022
+        } elseif ($share->getShareType() === IShare::TYPE_LINK) {
1023
+            $share->setPassword($data['password']);
1024
+            $share->setSendPasswordByTalk((bool)$data['password_by_talk']);
1025
+            $share->setToken($data['token']);
1026
+        }
1027
+
1028
+        $share = $this->updateShareAttributes($share, $data['attributes']);
1029
+
1030
+        $share->setSharedBy($data['uid_initiator']);
1031
+        $share->setShareOwner($data['uid_owner']);
1032
+
1033
+        $share->setNodeId((int)$data['file_source']);
1034
+        $share->setNodeType($data['item_type']);
1035
+
1036
+        if ($data['expiration'] !== null) {
1037
+            $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
1038
+            $share->setExpirationDate($expiration);
1039
+        }
1040
+
1041
+        if (isset($data['f_permissions'])) {
1042
+            $entryData = $data;
1043
+            $entryData['permissions'] = $entryData['f_permissions'];
1044
+            $entryData['parent'] = $entryData['f_parent'];
1045
+            $share->setNodeCacheEntry(Cache::cacheEntryFromData($entryData,
1046
+                \OC::$server->getMimeTypeLoader()));
1047
+        }
1048
+
1049
+        $share->setProviderId($this->identifier());
1050
+        $share->setHideDownload((int)$data['hide_download'] === 1);
1051
+        $share->setReminderSent((bool)$data['reminder_sent']);
1052
+
1053
+        return $share;
1054
+    }
1055
+
1056
+    /**
1057
+     * Update the data from group shares with any per-user modifications
1058
+     *
1059
+     * @param array<int, Share> $shareMap shares indexed by share id
1060
+     * @param $userId
1061
+     * @return Share[] The updates shares if no update is found for a share return the original
1062
+     */
1063
+    private function resolveGroupShares($shareMap, $userId) {
1064
+        $qb = $this->dbConn->getQueryBuilder();
1065
+        $query = $qb->select('*')
1066
+            ->from('share')
1067
+            ->where($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)))
1068
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1069
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1070
+
1071
+        // this is called with either all group shares or one group share.
1072
+        // for all shares it's easier to just only search by share_with,
1073
+        // for a single share it's efficient to filter by parent
1074
+        if (count($shareMap) === 1) {
1075
+            $share = reset($shareMap);
1076
+            $query->andWhere($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())));
1077
+        }
1078
+
1079
+        $stmt = $query->executeQuery();
1080
+
1081
+        while ($data = $stmt->fetch()) {
1082
+            if (array_key_exists($data['parent'], $shareMap)) {
1083
+                $shareMap[$data['parent']]->setPermissions((int)$data['permissions']);
1084
+                $shareMap[$data['parent']]->setStatus((int)$data['accepted']);
1085
+                $shareMap[$data['parent']]->setTarget($data['file_target']);
1086
+                $shareMap[$data['parent']]->setParent($data['parent']);
1087
+            }
1088
+        }
1089
+
1090
+        return array_values($shareMap);
1091
+    }
1092
+
1093
+    /**
1094
+     * A user is deleted from the system
1095
+     * So clean up the relevant shares.
1096
+     *
1097
+     * @param string $uid
1098
+     * @param int $shareType
1099
+     */
1100
+    public function userDeleted($uid, $shareType) {
1101
+        $qb = $this->dbConn->getQueryBuilder();
1102
+
1103
+        $qb->delete('share');
1104
+
1105
+        if ($shareType === IShare::TYPE_USER) {
1106
+            /*
1107 1107
 			 * Delete all user shares that are owned by this user
1108 1108
 			 * or that are received by this user
1109 1109
 			 */
1110 1110
 
1111
-			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)));
1111
+            $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)));
1112 1112
 
1113
-			$qb->andWhere(
1114
-				$qb->expr()->orX(
1115
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1116
-					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1117
-				)
1118
-			);
1119
-		} elseif ($shareType === IShare::TYPE_GROUP) {
1120
-			/*
1113
+            $qb->andWhere(
1114
+                $qb->expr()->orX(
1115
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1116
+                    $qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1117
+                )
1118
+            );
1119
+        } elseif ($shareType === IShare::TYPE_GROUP) {
1120
+            /*
1121 1121
 			 * Delete all group shares that are owned by this user
1122 1122
 			 * Or special user group shares that are received by this user
1123 1123
 			 */
1124
-			$qb->where(
1125
-				$qb->expr()->andX(
1126
-					$qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_GROUP, IShare::TYPE_USERGROUP], IQueryBuilder::PARAM_INT_ARRAY)),
1127
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))
1128
-				)
1129
-			);
1130
-
1131
-			$qb->orWhere(
1132
-				$qb->expr()->andX(
1133
-					$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)),
1134
-					$qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1135
-				)
1136
-			);
1137
-		} elseif ($shareType === IShare::TYPE_LINK) {
1138
-			/*
1124
+            $qb->where(
1125
+                $qb->expr()->andX(
1126
+                    $qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_GROUP, IShare::TYPE_USERGROUP], IQueryBuilder::PARAM_INT_ARRAY)),
1127
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid))
1128
+                )
1129
+            );
1130
+
1131
+            $qb->orWhere(
1132
+                $qb->expr()->andX(
1133
+                    $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)),
1134
+                    $qb->expr()->eq('share_with', $qb->createNamedParameter($uid))
1135
+                )
1136
+            );
1137
+        } elseif ($shareType === IShare::TYPE_LINK) {
1138
+            /*
1139 1139
 			 * Delete all link shares owned by this user.
1140 1140
 			 * And all link shares initiated by this user (until #22327 is in)
1141 1141
 			 */
1142
-			$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)));
1143
-
1144
-			$qb->andWhere(
1145
-				$qb->expr()->orX(
1146
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1147
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($uid))
1148
-				)
1149
-			);
1150
-		} else {
1151
-			$e = new \InvalidArgumentException('Default share provider tried to delete all shares for type: ' . $shareType);
1152
-			$this->logger->error($e->getMessage(), ['exception' => $e]);
1153
-			return;
1154
-		}
1155
-
1156
-		$qb->executeStatement();
1157
-	}
1158
-
1159
-	/**
1160
-	 * Delete all shares received by this group. As well as any custom group
1161
-	 * shares for group members.
1162
-	 *
1163
-	 * @param string $gid
1164
-	 */
1165
-	public function groupDeleted($gid) {
1166
-		/*
1142
+            $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_LINK)));
1143
+
1144
+            $qb->andWhere(
1145
+                $qb->expr()->orX(
1146
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)),
1147
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($uid))
1148
+                )
1149
+            );
1150
+        } else {
1151
+            $e = new \InvalidArgumentException('Default share provider tried to delete all shares for type: ' . $shareType);
1152
+            $this->logger->error($e->getMessage(), ['exception' => $e]);
1153
+            return;
1154
+        }
1155
+
1156
+        $qb->executeStatement();
1157
+    }
1158
+
1159
+    /**
1160
+     * Delete all shares received by this group. As well as any custom group
1161
+     * shares for group members.
1162
+     *
1163
+     * @param string $gid
1164
+     */
1165
+    public function groupDeleted($gid) {
1166
+        /*
1167 1167
 		 * First delete all custom group shares for group members
1168 1168
 		 */
1169
-		$qb = $this->dbConn->getQueryBuilder();
1170
-		$qb->select('id')
1171
-			->from('share')
1172
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1173
-			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1174
-
1175
-		$cursor = $qb->executeQuery();
1176
-		$ids = [];
1177
-		while ($row = $cursor->fetch()) {
1178
-			$ids[] = (int)$row['id'];
1179
-		}
1180
-		$cursor->closeCursor();
1181
-
1182
-		if (!empty($ids)) {
1183
-			$chunks = array_chunk($ids, 100);
1184
-
1185
-			$qb = $this->dbConn->getQueryBuilder();
1186
-			$qb->delete('share')
1187
-				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1188
-				->andWhere($qb->expr()->in('parent', $qb->createParameter('parents')));
1189
-
1190
-			foreach ($chunks as $chunk) {
1191
-				$qb->setParameter('parents', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
1192
-				$qb->executeStatement();
1193
-			}
1194
-		}
1195
-
1196
-		/*
1169
+        $qb = $this->dbConn->getQueryBuilder();
1170
+        $qb->select('id')
1171
+            ->from('share')
1172
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1173
+            ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1174
+
1175
+        $cursor = $qb->executeQuery();
1176
+        $ids = [];
1177
+        while ($row = $cursor->fetch()) {
1178
+            $ids[] = (int)$row['id'];
1179
+        }
1180
+        $cursor->closeCursor();
1181
+
1182
+        if (!empty($ids)) {
1183
+            $chunks = array_chunk($ids, 100);
1184
+
1185
+            $qb = $this->dbConn->getQueryBuilder();
1186
+            $qb->delete('share')
1187
+                ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1188
+                ->andWhere($qb->expr()->in('parent', $qb->createParameter('parents')));
1189
+
1190
+            foreach ($chunks as $chunk) {
1191
+                $qb->setParameter('parents', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
1192
+                $qb->executeStatement();
1193
+            }
1194
+        }
1195
+
1196
+        /*
1197 1197
 		 * Now delete all the group shares
1198 1198
 		 */
1199
-		$qb = $this->dbConn->getQueryBuilder();
1200
-		$qb->delete('share')
1201
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1202
-			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1203
-		$qb->executeStatement();
1204
-	}
1205
-
1206
-	/**
1207
-	 * Delete custom group shares to this group for this user
1208
-	 *
1209
-	 * @param string $uid
1210
-	 * @param string $gid
1211
-	 * @return void
1212
-	 */
1213
-	public function userDeletedFromGroup($uid, $gid) {
1214
-		/*
1199
+        $qb = $this->dbConn->getQueryBuilder();
1200
+        $qb->delete('share')
1201
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1202
+            ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1203
+        $qb->executeStatement();
1204
+    }
1205
+
1206
+    /**
1207
+     * Delete custom group shares to this group for this user
1208
+     *
1209
+     * @param string $uid
1210
+     * @param string $gid
1211
+     * @return void
1212
+     */
1213
+    public function userDeletedFromGroup($uid, $gid) {
1214
+        /*
1215 1215
 		 * Get all group shares
1216 1216
 		 */
1217
-		$qb = $this->dbConn->getQueryBuilder();
1218
-		$qb->select('id')
1219
-			->from('share')
1220
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1221
-			->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1222
-
1223
-		$cursor = $qb->executeQuery();
1224
-		$ids = [];
1225
-		while ($row = $cursor->fetch()) {
1226
-			$ids[] = (int)$row['id'];
1227
-		}
1228
-		$cursor->closeCursor();
1229
-
1230
-		if (!empty($ids)) {
1231
-			$chunks = array_chunk($ids, 100);
1232
-
1233
-			/*
1217
+        $qb = $this->dbConn->getQueryBuilder();
1218
+        $qb->select('id')
1219
+            ->from('share')
1220
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
1221
+            ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($gid)));
1222
+
1223
+        $cursor = $qb->executeQuery();
1224
+        $ids = [];
1225
+        while ($row = $cursor->fetch()) {
1226
+            $ids[] = (int)$row['id'];
1227
+        }
1228
+        $cursor->closeCursor();
1229
+
1230
+        if (!empty($ids)) {
1231
+            $chunks = array_chunk($ids, 100);
1232
+
1233
+            /*
1234 1234
 			 * Delete all special shares with this user for the found group shares
1235 1235
 			 */
1236
-			$qb = $this->dbConn->getQueryBuilder();
1237
-			$qb->delete('share')
1238
-				->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1239
-				->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid)))
1240
-				->andWhere($qb->expr()->in('parent', $qb->createParameter('parents')));
1241
-
1242
-			foreach ($chunks as $chunk) {
1243
-				$qb->setParameter('parents', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
1244
-				$qb->executeStatement();
1245
-			}
1246
-		}
1247
-
1248
-		if ($this->shareManager->shareWithGroupMembersOnly()) {
1249
-			$user = $this->userManager->get($uid);
1250
-			if ($user === null) {
1251
-				return;
1252
-			}
1253
-			$userGroups = $this->groupManager->getUserGroupIds($user);
1254
-			$userGroups = array_diff($userGroups, $this->shareManager->shareWithGroupMembersOnlyExcludeGroupsList());
1255
-
1256
-			// Delete user shares received by the user from users in the group.
1257
-			$userReceivedShares = $this->shareManager->getSharedWith($uid, IShare::TYPE_USER, null, -1);
1258
-			foreach ($userReceivedShares as $share) {
1259
-				$owner = $this->userManager->get($share->getSharedBy());
1260
-				if ($owner === null) {
1261
-					continue;
1262
-				}
1263
-				$ownerGroups = $this->groupManager->getUserGroupIds($owner);
1264
-				$mutualGroups = array_intersect($userGroups, $ownerGroups);
1265
-
1266
-				if (count($mutualGroups) === 0) {
1267
-					$this->shareManager->deleteShare($share);
1268
-				}
1269
-			}
1270
-
1271
-			// Delete user shares from the user to users in the group.
1272
-			$userEmittedShares = $this->shareManager->getSharesBy($uid, IShare::TYPE_USER, null, true, -1);
1273
-			foreach ($userEmittedShares as $share) {
1274
-				$recipient = $this->userManager->get($share->getSharedWith());
1275
-				if ($recipient === null) {
1276
-					continue;
1277
-				}
1278
-				$recipientGroups = $this->groupManager->getUserGroupIds($recipient);
1279
-				$mutualGroups = array_intersect($userGroups, $recipientGroups);
1280
-
1281
-				if (count($mutualGroups) === 0) {
1282
-					$this->shareManager->deleteShare($share);
1283
-				}
1284
-			}
1285
-		}
1286
-	}
1287
-
1288
-	/**
1289
-	 * @inheritdoc
1290
-	 */
1291
-	public function getAccessList($nodes, $currentAccess) {
1292
-		$ids = [];
1293
-		foreach ($nodes as $node) {
1294
-			$ids[] = $node->getId();
1295
-		}
1296
-
1297
-		$qb = $this->dbConn->getQueryBuilder();
1298
-
1299
-		$shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK];
1300
-
1301
-		if ($currentAccess) {
1302
-			$shareTypes[] = IShare::TYPE_USERGROUP;
1303
-		}
1304
-
1305
-		$qb->select('id', 'parent', 'share_type', 'share_with', 'file_source', 'file_target', 'permissions')
1306
-			->from('share')
1307
-			->where(
1308
-				$qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY))
1309
-			)
1310
-			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1311
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1312
-
1313
-		// Ensure accepted is true for user and usergroup type
1314
-		$qb->andWhere(
1315
-			$qb->expr()->orX(
1316
-				$qb->expr()->andX(
1317
-					$qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)),
1318
-					$qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)),
1319
-				),
1320
-				$qb->expr()->eq('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED, IQueryBuilder::PARAM_INT)),
1321
-			),
1322
-		);
1323
-
1324
-		$cursor = $qb->executeQuery();
1325
-
1326
-		$users = [];
1327
-		$link = false;
1328
-		while ($row = $cursor->fetch()) {
1329
-			$type = (int)$row['share_type'];
1330
-			if ($type === IShare::TYPE_USER) {
1331
-				$uid = $row['share_with'];
1332
-				$users[$uid] = $users[$uid] ?? [];
1333
-				$users[$uid][$row['id']] = $row;
1334
-			} elseif ($type === IShare::TYPE_GROUP) {
1335
-				$gid = $row['share_with'];
1336
-				$group = $this->groupManager->get($gid);
1337
-
1338
-				if ($group === null) {
1339
-					continue;
1340
-				}
1341
-
1342
-				$userList = $group->getUsers();
1343
-				foreach ($userList as $user) {
1344
-					$uid = $user->getUID();
1345
-					$users[$uid] = $users[$uid] ?? [];
1346
-					$users[$uid][$row['id']] = $row;
1347
-				}
1348
-			} elseif ($type === IShare::TYPE_LINK) {
1349
-				$link = true;
1350
-			} elseif ($type === IShare::TYPE_USERGROUP && $currentAccess === true) {
1351
-				$uid = $row['share_with'];
1352
-				$users[$uid] = $users[$uid] ?? [];
1353
-				$users[$uid][$row['id']] = $row;
1354
-			}
1355
-		}
1356
-		$cursor->closeCursor();
1357
-
1358
-		if ($currentAccess === true) {
1359
-			$users = array_map([$this, 'filterSharesOfUser'], $users);
1360
-			$users = array_filter($users);
1361
-		} else {
1362
-			$users = array_keys($users);
1363
-		}
1364
-
1365
-		return ['users' => $users, 'public' => $link];
1366
-	}
1367
-
1368
-	/**
1369
-	 * For each user the path with the fewest slashes is returned
1370
-	 * @param array $shares
1371
-	 * @return array
1372
-	 */
1373
-	protected function filterSharesOfUser(array $shares) {
1374
-		// Group shares when the user has a share exception
1375
-		foreach ($shares as $id => $share) {
1376
-			$type = (int)$share['share_type'];
1377
-			$permissions = (int)$share['permissions'];
1378
-
1379
-			if ($type === IShare::TYPE_USERGROUP) {
1380
-				unset($shares[$share['parent']]);
1381
-
1382
-				if ($permissions === 0) {
1383
-					unset($shares[$id]);
1384
-				}
1385
-			}
1386
-		}
1387
-
1388
-		$best = [];
1389
-		$bestDepth = 0;
1390
-		foreach ($shares as $id => $share) {
1391
-			$depth = substr_count(($share['file_target'] ?? ''), '/');
1392
-			if (empty($best) || $depth < $bestDepth) {
1393
-				$bestDepth = $depth;
1394
-				$best = [
1395
-					'node_id' => $share['file_source'],
1396
-					'node_path' => $share['file_target'],
1397
-				];
1398
-			}
1399
-		}
1400
-
1401
-		return $best;
1402
-	}
1403
-
1404
-	/**
1405
-	 * propagate notes to the recipients
1406
-	 *
1407
-	 * @param IShare $share
1408
-	 * @throws \OCP\Files\NotFoundException
1409
-	 */
1410
-	private function propagateNote(IShare $share) {
1411
-		if ($share->getShareType() === IShare::TYPE_USER) {
1412
-			$user = $this->userManager->get($share->getSharedWith());
1413
-			$this->sendNote([$user], $share);
1414
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1415
-			$group = $this->groupManager->get($share->getSharedWith());
1416
-			$groupMembers = $group->getUsers();
1417
-			$this->sendNote($groupMembers, $share);
1418
-		}
1419
-	}
1420
-
1421
-	public function sendMailNotification(IShare $share): bool {
1422
-		try {
1423
-			// Check user
1424
-			$user = $this->userManager->get($share->getSharedWith());
1425
-			if ($user === null) {
1426
-				$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
1427
-				return false;
1428
-			}
1429
-
1430
-			// Handle user shares
1431
-			if ($share->getShareType() === IShare::TYPE_USER) {
1432
-				// Check email address
1433
-				$emailAddress = $user->getEMailAddress();
1434
-				if ($emailAddress === null || $emailAddress === '') {
1435
-					$this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
1436
-					return false;
1437
-				}
1438
-
1439
-				$userLang = $this->l10nFactory->getUserLanguage($user);
1440
-				$l = $this->l10nFactory->get('lib', $userLang);
1441
-				$this->sendUserShareMail(
1442
-					$l,
1443
-					$share->getNode()->getName(),
1444
-					$this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]),
1445
-					$share->getSharedBy(),
1446
-					$emailAddress,
1447
-					$share->getExpirationDate(),
1448
-					$share->getNote()
1449
-				);
1450
-				$this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId() . '.', ['app' => 'share']);
1451
-				return true;
1452
-			}
1453
-		} catch (\Exception $e) {
1454
-			$this->logger->error('Share notification mail could not be sent.', ['exception' => $e]);
1455
-		}
1456
-
1457
-		return false;
1458
-	}
1459
-
1460
-	/**
1461
-	 * Send mail notifications for the user share type
1462
-	 *
1463
-	 * @param IL10N $l Language of the recipient
1464
-	 * @param string $filename file/folder name
1465
-	 * @param string $link link to the file/folder
1466
-	 * @param string $initiator user ID of share sender
1467
-	 * @param string $shareWith email address of share receiver
1468
-	 * @param \DateTime|null $expiration
1469
-	 * @param string $note
1470
-	 * @throws \Exception
1471
-	 */
1472
-	protected function sendUserShareMail(
1473
-		IL10N $l,
1474
-		$filename,
1475
-		$link,
1476
-		$initiator,
1477
-		$shareWith,
1478
-		?\DateTime $expiration = null,
1479
-		$note = '') {
1480
-		$initiatorUser = $this->userManager->get($initiator);
1481
-		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
1482
-
1483
-		$message = $this->mailer->createMessage();
1484
-
1485
-		$emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
1486
-			'filename' => $filename,
1487
-			'link' => $link,
1488
-			'initiator' => $initiatorDisplayName,
1489
-			'expiration' => $expiration,
1490
-			'shareWith' => $shareWith,
1491
-		]);
1492
-
1493
-		$emailTemplate->setSubject($l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]));
1494
-		$emailTemplate->addHeader();
1495
-		$emailTemplate->addHeading($l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false);
1496
-
1497
-		if ($note !== '') {
1498
-			$emailTemplate->addBodyText(htmlspecialchars($note), $note);
1499
-		}
1500
-
1501
-		$emailTemplate->addBodyButton(
1502
-			$l->t('Open %s', [$filename]),
1503
-			$link
1504
-		);
1505
-
1506
-		$message->setTo([$shareWith]);
1507
-
1508
-		// The "From" contains the sharers name
1509
-		$instanceName = $this->defaults->getName();
1510
-		$senderName = $l->t(
1511
-			'%1$s via %2$s',
1512
-			[
1513
-				$initiatorDisplayName,
1514
-				$instanceName,
1515
-			]
1516
-		);
1517
-		$message->setFrom([\OCP\Util::getDefaultEmailAddress('noreply') => $senderName]);
1518
-
1519
-		// The "Reply-To" is set to the sharer if an mail address is configured
1520
-		// also the default footer contains a "Do not reply" which needs to be adjusted.
1521
-		if ($initiatorUser) {
1522
-			$initiatorEmail = $initiatorUser->getEMailAddress();
1523
-			if ($initiatorEmail !== null) {
1524
-				$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
1525
-				$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
1526
-			} else {
1527
-				$emailTemplate->addFooter();
1528
-			}
1529
-		} else {
1530
-			$emailTemplate->addFooter();
1531
-		}
1532
-
1533
-		$message->useTemplate($emailTemplate);
1534
-		$failedRecipients = $this->mailer->send($message);
1535
-		if (!empty($failedRecipients)) {
1536
-			$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
1537
-			return;
1538
-		}
1539
-	}
1540
-
1541
-	/**
1542
-	 * send note by mail
1543
-	 *
1544
-	 * @param array $recipients
1545
-	 * @param IShare $share
1546
-	 * @throws \OCP\Files\NotFoundException
1547
-	 */
1548
-	private function sendNote(array $recipients, IShare $share) {
1549
-		$toListByLanguage = [];
1550
-
1551
-		foreach ($recipients as $recipient) {
1552
-			/** @var IUser $recipient */
1553
-			$email = $recipient->getEMailAddress();
1554
-			if ($email) {
1555
-				$language = $this->l10nFactory->getUserLanguage($recipient);
1556
-				if (!isset($toListByLanguage[$language])) {
1557
-					$toListByLanguage[$language] = [];
1558
-				}
1559
-				$toListByLanguage[$language][$email] = $recipient->getDisplayName();
1560
-			}
1561
-		}
1562
-
1563
-		if (empty($toListByLanguage)) {
1564
-			return;
1565
-		}
1566
-
1567
-		foreach ($toListByLanguage as $l10n => $toList) {
1568
-			$filename = $share->getNode()->getName();
1569
-			$initiator = $share->getSharedBy();
1570
-			$note = $share->getNote();
1571
-
1572
-			$l = $this->l10nFactory->get('lib', $l10n);
1573
-
1574
-			$initiatorUser = $this->userManager->get($initiator);
1575
-			$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
1576
-			$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
1577
-			$plainHeading = $l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]);
1578
-			$htmlHeading = $l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]);
1579
-			$message = $this->mailer->createMessage();
1580
-
1581
-			$emailTemplate = $this->mailer->createEMailTemplate('defaultShareProvider.sendNote');
1582
-
1583
-			$emailTemplate->setSubject($l->t('%s added a note to a file shared with you', [$initiatorDisplayName]));
1584
-			$emailTemplate->addHeader();
1585
-			$emailTemplate->addHeading($htmlHeading, $plainHeading);
1586
-			$emailTemplate->addBodyText(htmlspecialchars($note), $note);
1587
-
1588
-			$link = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
1589
-			$emailTemplate->addBodyButton(
1590
-				$l->t('Open %s', [$filename]),
1591
-				$link
1592
-			);
1593
-
1594
-
1595
-			// The "From" contains the sharers name
1596
-			$instanceName = $this->defaults->getName();
1597
-			$senderName = $l->t(
1598
-				'%1$s via %2$s',
1599
-				[
1600
-					$initiatorDisplayName,
1601
-					$instanceName
1602
-				]
1603
-			);
1604
-			$message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
1605
-			if ($initiatorEmailAddress !== null) {
1606
-				$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
1607
-				$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
1608
-			} else {
1609
-				$emailTemplate->addFooter();
1610
-			}
1611
-
1612
-			if (count($toList) === 1) {
1613
-				$message->setTo($toList);
1614
-			} else {
1615
-				$message->setTo([]);
1616
-				$message->setBcc($toList);
1617
-			}
1618
-			$message->useTemplate($emailTemplate);
1619
-			$this->mailer->send($message);
1620
-		}
1621
-	}
1622
-
1623
-	public function getAllShares(): iterable {
1624
-		$qb = $this->dbConn->getQueryBuilder();
1625
-
1626
-		$qb->select('*')
1627
-			->from('share')
1628
-			->where($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)));
1629
-
1630
-		$cursor = $qb->executeQuery();
1631
-		while ($data = $cursor->fetch()) {
1632
-			try {
1633
-				$share = $this->createShare($data);
1634
-			} catch (InvalidShare $e) {
1635
-				continue;
1636
-			}
1637
-
1638
-			yield $share;
1639
-		}
1640
-		$cursor->closeCursor();
1641
-	}
1642
-
1643
-	/**
1644
-	 * Load from database format (JSON string) to IAttributes
1645
-	 *
1646
-	 * @return IShare the modified share
1647
-	 */
1648
-	protected function updateShareAttributes(IShare $share, ?string $data): IShare {
1649
-		if ($data !== null && $data !== '') {
1650
-			$attributes = new ShareAttributes();
1651
-			$compressedAttributes = \json_decode($data, true);
1652
-			if ($compressedAttributes === false || $compressedAttributes === null) {
1653
-				return $share;
1654
-			}
1655
-			foreach ($compressedAttributes as $compressedAttribute) {
1656
-				$attributes->setAttribute(
1657
-					$compressedAttribute[0],
1658
-					$compressedAttribute[1],
1659
-					$compressedAttribute[2]
1660
-				);
1661
-			}
1662
-			$share->setAttributes($attributes);
1663
-		}
1664
-
1665
-		return $share;
1666
-	}
1667
-
1668
-	/**
1669
-	 * Format IAttributes to database format (JSON string)
1670
-	 */
1671
-	protected function formatShareAttributes(?IAttributes $attributes): ?string {
1672
-		if ($attributes === null || empty($attributes->toArray())) {
1673
-			return null;
1674
-		}
1675
-
1676
-		$compressedAttributes = [];
1677
-		foreach ($attributes->toArray() as $attribute) {
1678
-			$compressedAttributes[] = [
1679
-				0 => $attribute['scope'],
1680
-				1 => $attribute['key'],
1681
-				2 => $attribute['value']
1682
-			];
1683
-		}
1684
-		return \json_encode($compressedAttributes);
1685
-	}
1686
-
1687
-	public function getUsersForShare(IShare $share): iterable {
1688
-		if ($share->getShareType() === IShare::TYPE_USER) {
1689
-			return [new LazyUser($share->getSharedWith(), $this->userManager)];
1690
-		} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1691
-			$group = $this->groupManager->get($share->getSharedWith());
1692
-			return $group->getUsers();
1693
-		} else {
1694
-			return [];
1695
-		}
1696
-	}
1236
+            $qb = $this->dbConn->getQueryBuilder();
1237
+            $qb->delete('share')
1238
+                ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)))
1239
+                ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($uid)))
1240
+                ->andWhere($qb->expr()->in('parent', $qb->createParameter('parents')));
1241
+
1242
+            foreach ($chunks as $chunk) {
1243
+                $qb->setParameter('parents', $chunk, IQueryBuilder::PARAM_INT_ARRAY);
1244
+                $qb->executeStatement();
1245
+            }
1246
+        }
1247
+
1248
+        if ($this->shareManager->shareWithGroupMembersOnly()) {
1249
+            $user = $this->userManager->get($uid);
1250
+            if ($user === null) {
1251
+                return;
1252
+            }
1253
+            $userGroups = $this->groupManager->getUserGroupIds($user);
1254
+            $userGroups = array_diff($userGroups, $this->shareManager->shareWithGroupMembersOnlyExcludeGroupsList());
1255
+
1256
+            // Delete user shares received by the user from users in the group.
1257
+            $userReceivedShares = $this->shareManager->getSharedWith($uid, IShare::TYPE_USER, null, -1);
1258
+            foreach ($userReceivedShares as $share) {
1259
+                $owner = $this->userManager->get($share->getSharedBy());
1260
+                if ($owner === null) {
1261
+                    continue;
1262
+                }
1263
+                $ownerGroups = $this->groupManager->getUserGroupIds($owner);
1264
+                $mutualGroups = array_intersect($userGroups, $ownerGroups);
1265
+
1266
+                if (count($mutualGroups) === 0) {
1267
+                    $this->shareManager->deleteShare($share);
1268
+                }
1269
+            }
1270
+
1271
+            // Delete user shares from the user to users in the group.
1272
+            $userEmittedShares = $this->shareManager->getSharesBy($uid, IShare::TYPE_USER, null, true, -1);
1273
+            foreach ($userEmittedShares as $share) {
1274
+                $recipient = $this->userManager->get($share->getSharedWith());
1275
+                if ($recipient === null) {
1276
+                    continue;
1277
+                }
1278
+                $recipientGroups = $this->groupManager->getUserGroupIds($recipient);
1279
+                $mutualGroups = array_intersect($userGroups, $recipientGroups);
1280
+
1281
+                if (count($mutualGroups) === 0) {
1282
+                    $this->shareManager->deleteShare($share);
1283
+                }
1284
+            }
1285
+        }
1286
+    }
1287
+
1288
+    /**
1289
+     * @inheritdoc
1290
+     */
1291
+    public function getAccessList($nodes, $currentAccess) {
1292
+        $ids = [];
1293
+        foreach ($nodes as $node) {
1294
+            $ids[] = $node->getId();
1295
+        }
1296
+
1297
+        $qb = $this->dbConn->getQueryBuilder();
1298
+
1299
+        $shareTypes = [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK];
1300
+
1301
+        if ($currentAccess) {
1302
+            $shareTypes[] = IShare::TYPE_USERGROUP;
1303
+        }
1304
+
1305
+        $qb->select('id', 'parent', 'share_type', 'share_with', 'file_source', 'file_target', 'permissions')
1306
+            ->from('share')
1307
+            ->where(
1308
+                $qb->expr()->in('share_type', $qb->createNamedParameter($shareTypes, IQueryBuilder::PARAM_INT_ARRAY))
1309
+            )
1310
+            ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1311
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1312
+
1313
+        // Ensure accepted is true for user and usergroup type
1314
+        $qb->andWhere(
1315
+            $qb->expr()->orX(
1316
+                $qb->expr()->andX(
1317
+                    $qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)),
1318
+                    $qb->expr()->neq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP)),
1319
+                ),
1320
+                $qb->expr()->eq('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED, IQueryBuilder::PARAM_INT)),
1321
+            ),
1322
+        );
1323
+
1324
+        $cursor = $qb->executeQuery();
1325
+
1326
+        $users = [];
1327
+        $link = false;
1328
+        while ($row = $cursor->fetch()) {
1329
+            $type = (int)$row['share_type'];
1330
+            if ($type === IShare::TYPE_USER) {
1331
+                $uid = $row['share_with'];
1332
+                $users[$uid] = $users[$uid] ?? [];
1333
+                $users[$uid][$row['id']] = $row;
1334
+            } elseif ($type === IShare::TYPE_GROUP) {
1335
+                $gid = $row['share_with'];
1336
+                $group = $this->groupManager->get($gid);
1337
+
1338
+                if ($group === null) {
1339
+                    continue;
1340
+                }
1341
+
1342
+                $userList = $group->getUsers();
1343
+                foreach ($userList as $user) {
1344
+                    $uid = $user->getUID();
1345
+                    $users[$uid] = $users[$uid] ?? [];
1346
+                    $users[$uid][$row['id']] = $row;
1347
+                }
1348
+            } elseif ($type === IShare::TYPE_LINK) {
1349
+                $link = true;
1350
+            } elseif ($type === IShare::TYPE_USERGROUP && $currentAccess === true) {
1351
+                $uid = $row['share_with'];
1352
+                $users[$uid] = $users[$uid] ?? [];
1353
+                $users[$uid][$row['id']] = $row;
1354
+            }
1355
+        }
1356
+        $cursor->closeCursor();
1357
+
1358
+        if ($currentAccess === true) {
1359
+            $users = array_map([$this, 'filterSharesOfUser'], $users);
1360
+            $users = array_filter($users);
1361
+        } else {
1362
+            $users = array_keys($users);
1363
+        }
1364
+
1365
+        return ['users' => $users, 'public' => $link];
1366
+    }
1367
+
1368
+    /**
1369
+     * For each user the path with the fewest slashes is returned
1370
+     * @param array $shares
1371
+     * @return array
1372
+     */
1373
+    protected function filterSharesOfUser(array $shares) {
1374
+        // Group shares when the user has a share exception
1375
+        foreach ($shares as $id => $share) {
1376
+            $type = (int)$share['share_type'];
1377
+            $permissions = (int)$share['permissions'];
1378
+
1379
+            if ($type === IShare::TYPE_USERGROUP) {
1380
+                unset($shares[$share['parent']]);
1381
+
1382
+                if ($permissions === 0) {
1383
+                    unset($shares[$id]);
1384
+                }
1385
+            }
1386
+        }
1387
+
1388
+        $best = [];
1389
+        $bestDepth = 0;
1390
+        foreach ($shares as $id => $share) {
1391
+            $depth = substr_count(($share['file_target'] ?? ''), '/');
1392
+            if (empty($best) || $depth < $bestDepth) {
1393
+                $bestDepth = $depth;
1394
+                $best = [
1395
+                    'node_id' => $share['file_source'],
1396
+                    'node_path' => $share['file_target'],
1397
+                ];
1398
+            }
1399
+        }
1400
+
1401
+        return $best;
1402
+    }
1403
+
1404
+    /**
1405
+     * propagate notes to the recipients
1406
+     *
1407
+     * @param IShare $share
1408
+     * @throws \OCP\Files\NotFoundException
1409
+     */
1410
+    private function propagateNote(IShare $share) {
1411
+        if ($share->getShareType() === IShare::TYPE_USER) {
1412
+            $user = $this->userManager->get($share->getSharedWith());
1413
+            $this->sendNote([$user], $share);
1414
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1415
+            $group = $this->groupManager->get($share->getSharedWith());
1416
+            $groupMembers = $group->getUsers();
1417
+            $this->sendNote($groupMembers, $share);
1418
+        }
1419
+    }
1420
+
1421
+    public function sendMailNotification(IShare $share): bool {
1422
+        try {
1423
+            // Check user
1424
+            $user = $this->userManager->get($share->getSharedWith());
1425
+            if ($user === null) {
1426
+                $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because user could not be found.', ['app' => 'share']);
1427
+                return false;
1428
+            }
1429
+
1430
+            // Handle user shares
1431
+            if ($share->getShareType() === IShare::TYPE_USER) {
1432
+                // Check email address
1433
+                $emailAddress = $user->getEMailAddress();
1434
+                if ($emailAddress === null || $emailAddress === '') {
1435
+                    $this->logger->debug('Share notification not sent to ' . $share->getSharedWith() . ' because email address is not set.', ['app' => 'share']);
1436
+                    return false;
1437
+                }
1438
+
1439
+                $userLang = $this->l10nFactory->getUserLanguage($user);
1440
+                $l = $this->l10nFactory->get('lib', $userLang);
1441
+                $this->sendUserShareMail(
1442
+                    $l,
1443
+                    $share->getNode()->getName(),
1444
+                    $this->urlGenerator->linkToRouteAbsolute('files_sharing.Accept.accept', ['shareId' => $share->getFullId()]),
1445
+                    $share->getSharedBy(),
1446
+                    $emailAddress,
1447
+                    $share->getExpirationDate(),
1448
+                    $share->getNote()
1449
+                );
1450
+                $this->logger->debug('Sent share notification to ' . $emailAddress . ' for share with ID ' . $share->getId() . '.', ['app' => 'share']);
1451
+                return true;
1452
+            }
1453
+        } catch (\Exception $e) {
1454
+            $this->logger->error('Share notification mail could not be sent.', ['exception' => $e]);
1455
+        }
1456
+
1457
+        return false;
1458
+    }
1459
+
1460
+    /**
1461
+     * Send mail notifications for the user share type
1462
+     *
1463
+     * @param IL10N $l Language of the recipient
1464
+     * @param string $filename file/folder name
1465
+     * @param string $link link to the file/folder
1466
+     * @param string $initiator user ID of share sender
1467
+     * @param string $shareWith email address of share receiver
1468
+     * @param \DateTime|null $expiration
1469
+     * @param string $note
1470
+     * @throws \Exception
1471
+     */
1472
+    protected function sendUserShareMail(
1473
+        IL10N $l,
1474
+        $filename,
1475
+        $link,
1476
+        $initiator,
1477
+        $shareWith,
1478
+        ?\DateTime $expiration = null,
1479
+        $note = '') {
1480
+        $initiatorUser = $this->userManager->get($initiator);
1481
+        $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
1482
+
1483
+        $message = $this->mailer->createMessage();
1484
+
1485
+        $emailTemplate = $this->mailer->createEMailTemplate('files_sharing.RecipientNotification', [
1486
+            'filename' => $filename,
1487
+            'link' => $link,
1488
+            'initiator' => $initiatorDisplayName,
1489
+            'expiration' => $expiration,
1490
+            'shareWith' => $shareWith,
1491
+        ]);
1492
+
1493
+        $emailTemplate->setSubject($l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]));
1494
+        $emailTemplate->addHeader();
1495
+        $emailTemplate->addHeading($l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false);
1496
+
1497
+        if ($note !== '') {
1498
+            $emailTemplate->addBodyText(htmlspecialchars($note), $note);
1499
+        }
1500
+
1501
+        $emailTemplate->addBodyButton(
1502
+            $l->t('Open %s', [$filename]),
1503
+            $link
1504
+        );
1505
+
1506
+        $message->setTo([$shareWith]);
1507
+
1508
+        // The "From" contains the sharers name
1509
+        $instanceName = $this->defaults->getName();
1510
+        $senderName = $l->t(
1511
+            '%1$s via %2$s',
1512
+            [
1513
+                $initiatorDisplayName,
1514
+                $instanceName,
1515
+            ]
1516
+        );
1517
+        $message->setFrom([\OCP\Util::getDefaultEmailAddress('noreply') => $senderName]);
1518
+
1519
+        // The "Reply-To" is set to the sharer if an mail address is configured
1520
+        // also the default footer contains a "Do not reply" which needs to be adjusted.
1521
+        if ($initiatorUser) {
1522
+            $initiatorEmail = $initiatorUser->getEMailAddress();
1523
+            if ($initiatorEmail !== null) {
1524
+                $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
1525
+                $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
1526
+            } else {
1527
+                $emailTemplate->addFooter();
1528
+            }
1529
+        } else {
1530
+            $emailTemplate->addFooter();
1531
+        }
1532
+
1533
+        $message->useTemplate($emailTemplate);
1534
+        $failedRecipients = $this->mailer->send($message);
1535
+        if (!empty($failedRecipients)) {
1536
+            $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
1537
+            return;
1538
+        }
1539
+    }
1540
+
1541
+    /**
1542
+     * send note by mail
1543
+     *
1544
+     * @param array $recipients
1545
+     * @param IShare $share
1546
+     * @throws \OCP\Files\NotFoundException
1547
+     */
1548
+    private function sendNote(array $recipients, IShare $share) {
1549
+        $toListByLanguage = [];
1550
+
1551
+        foreach ($recipients as $recipient) {
1552
+            /** @var IUser $recipient */
1553
+            $email = $recipient->getEMailAddress();
1554
+            if ($email) {
1555
+                $language = $this->l10nFactory->getUserLanguage($recipient);
1556
+                if (!isset($toListByLanguage[$language])) {
1557
+                    $toListByLanguage[$language] = [];
1558
+                }
1559
+                $toListByLanguage[$language][$email] = $recipient->getDisplayName();
1560
+            }
1561
+        }
1562
+
1563
+        if (empty($toListByLanguage)) {
1564
+            return;
1565
+        }
1566
+
1567
+        foreach ($toListByLanguage as $l10n => $toList) {
1568
+            $filename = $share->getNode()->getName();
1569
+            $initiator = $share->getSharedBy();
1570
+            $note = $share->getNote();
1571
+
1572
+            $l = $this->l10nFactory->get('lib', $l10n);
1573
+
1574
+            $initiatorUser = $this->userManager->get($initiator);
1575
+            $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
1576
+            $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
1577
+            $plainHeading = $l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]);
1578
+            $htmlHeading = $l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]);
1579
+            $message = $this->mailer->createMessage();
1580
+
1581
+            $emailTemplate = $this->mailer->createEMailTemplate('defaultShareProvider.sendNote');
1582
+
1583
+            $emailTemplate->setSubject($l->t('%s added a note to a file shared with you', [$initiatorDisplayName]));
1584
+            $emailTemplate->addHeader();
1585
+            $emailTemplate->addHeading($htmlHeading, $plainHeading);
1586
+            $emailTemplate->addBodyText(htmlspecialchars($note), $note);
1587
+
1588
+            $link = $this->urlGenerator->linkToRouteAbsolute('files.viewcontroller.showFile', ['fileid' => $share->getNode()->getId()]);
1589
+            $emailTemplate->addBodyButton(
1590
+                $l->t('Open %s', [$filename]),
1591
+                $link
1592
+            );
1593
+
1594
+
1595
+            // The "From" contains the sharers name
1596
+            $instanceName = $this->defaults->getName();
1597
+            $senderName = $l->t(
1598
+                '%1$s via %2$s',
1599
+                [
1600
+                    $initiatorDisplayName,
1601
+                    $instanceName
1602
+                ]
1603
+            );
1604
+            $message->setFrom([\OCP\Util::getDefaultEmailAddress($instanceName) => $senderName]);
1605
+            if ($initiatorEmailAddress !== null) {
1606
+                $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
1607
+                $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
1608
+            } else {
1609
+                $emailTemplate->addFooter();
1610
+            }
1611
+
1612
+            if (count($toList) === 1) {
1613
+                $message->setTo($toList);
1614
+            } else {
1615
+                $message->setTo([]);
1616
+                $message->setBcc($toList);
1617
+            }
1618
+            $message->useTemplate($emailTemplate);
1619
+            $this->mailer->send($message);
1620
+        }
1621
+    }
1622
+
1623
+    public function getAllShares(): iterable {
1624
+        $qb = $this->dbConn->getQueryBuilder();
1625
+
1626
+        $qb->select('*')
1627
+            ->from('share')
1628
+            ->where($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK], IQueryBuilder::PARAM_INT_ARRAY)));
1629
+
1630
+        $cursor = $qb->executeQuery();
1631
+        while ($data = $cursor->fetch()) {
1632
+            try {
1633
+                $share = $this->createShare($data);
1634
+            } catch (InvalidShare $e) {
1635
+                continue;
1636
+            }
1637
+
1638
+            yield $share;
1639
+        }
1640
+        $cursor->closeCursor();
1641
+    }
1642
+
1643
+    /**
1644
+     * Load from database format (JSON string) to IAttributes
1645
+     *
1646
+     * @return IShare the modified share
1647
+     */
1648
+    protected function updateShareAttributes(IShare $share, ?string $data): IShare {
1649
+        if ($data !== null && $data !== '') {
1650
+            $attributes = new ShareAttributes();
1651
+            $compressedAttributes = \json_decode($data, true);
1652
+            if ($compressedAttributes === false || $compressedAttributes === null) {
1653
+                return $share;
1654
+            }
1655
+            foreach ($compressedAttributes as $compressedAttribute) {
1656
+                $attributes->setAttribute(
1657
+                    $compressedAttribute[0],
1658
+                    $compressedAttribute[1],
1659
+                    $compressedAttribute[2]
1660
+                );
1661
+            }
1662
+            $share->setAttributes($attributes);
1663
+        }
1664
+
1665
+        return $share;
1666
+    }
1667
+
1668
+    /**
1669
+     * Format IAttributes to database format (JSON string)
1670
+     */
1671
+    protected function formatShareAttributes(?IAttributes $attributes): ?string {
1672
+        if ($attributes === null || empty($attributes->toArray())) {
1673
+            return null;
1674
+        }
1675
+
1676
+        $compressedAttributes = [];
1677
+        foreach ($attributes->toArray() as $attribute) {
1678
+            $compressedAttributes[] = [
1679
+                0 => $attribute['scope'],
1680
+                1 => $attribute['key'],
1681
+                2 => $attribute['value']
1682
+            ];
1683
+        }
1684
+        return \json_encode($compressedAttributes);
1685
+    }
1686
+
1687
+    public function getUsersForShare(IShare $share): iterable {
1688
+        if ($share->getShareType() === IShare::TYPE_USER) {
1689
+            return [new LazyUser($share->getSharedWith(), $this->userManager)];
1690
+        } elseif ($share->getShareType() === IShare::TYPE_GROUP) {
1691
+            $group = $this->groupManager->get($share->getSharedWith());
1692
+            return $group->getUsers();
1693
+        } else {
1694
+            return [];
1695
+        }
1696
+    }
1697 1697
 }
Please login to merge, or discard this patch.
lib/public/Share/IManager.php 1 patch
Indentation   +504 added lines, -504 removed lines patch added patch discarded remove patch
@@ -23,508 +23,508 @@
 block discarded – undo
23 23
  */
24 24
 #[Consumable(since: '9.0.0')]
25 25
 interface IManager {
26
-	/**
27
-	 * Create a Share
28
-	 *
29
-	 * @throws \Exception
30
-	 * @since 9.0.0
31
-	 */
32
-	public function createShare(IShare $share): IShare;
33
-
34
-	/**
35
-	 * Update a share.
36
-	 * The target of the share can't be changed this way: use moveShare
37
-	 * The share can't be removed this way (permission 0): use deleteShare
38
-	 * The state can't be changed this way: use acceptShare
39
-	 *
40
-	 * @param bool $onlyValid Only updates valid shares, invalid shares will be deleted automatically and are not updated
41
-	 * @throws \InvalidArgumentException
42
-	 * @since 9.0.0
43
-	 */
44
-	public function updateShare(IShare $share, bool $onlyValid = true): IShare;
45
-
46
-	/**
47
-	 * Accept a share.
48
-	 *
49
-	 * @throws \InvalidArgumentException
50
-	 * @since 18.0.0
51
-	 */
52
-	public function acceptShare(IShare $share, string $recipientId): IShare;
53
-
54
-	/**
55
-	 * Delete a share
56
-	 *
57
-	 * @throws ShareNotFound
58
-	 * @throws \InvalidArgumentException
59
-	 * @since 9.0.0
60
-	 */
61
-	public function deleteShare(IShare $share): void;
62
-
63
-	/**
64
-	 * Unshare a file as the recipient.
65
-	 * This can be different from a regular delete for example when one of
66
-	 * the users in a groups deletes that share. But the provider should
67
-	 * handle this.
68
-	 *
69
-	 * @since 9.0.0
70
-	 */
71
-	public function deleteFromSelf(IShare $share, string $recipientId): void;
72
-
73
-	/**
74
-	 * Restore the share when it has been deleted
75
-	 * Certain share types can be restored when they have been deleted
76
-	 * but the provider should properly handle this\
77
-	 *
78
-	 * @param IShare $share The share to restore
79
-	 * @param string $recipientId The user to restore the share for
80
-	 * @return IShare The restored share object
81
-	 * @throws GenericShareException In case restoring the share failed
82
-	 *
83
-	 * @since 14.0.0
84
-	 */
85
-	public function restoreShare(IShare $share, string $recipientId): IShare;
86
-
87
-	/**
88
-	 * Move the share as a recipient of the share.
89
-	 * This is updating the share target. So where the recipient has the share mounted.
90
-	 *
91
-	 * @throws \InvalidArgumentException If $share is a link share or the $recipient does not match
92
-	 * @since 9.0.0
93
-	 */
94
-	public function moveShare(IShare $share, string $recipientId): IShare;
95
-
96
-	/**
97
-	 * Get all shares shared by (initiated) by the provided user in a folder.
98
-	 *
99
-	 * @param string $userId
100
-	 * @param Folder $node
101
-	 * @param bool $reshares
102
-	 * @param bool $shallow Whether the method should stop at the first level, or look into sub-folders.
103
-	 * @return array<int, list<IShare>> [$fileId => IShare[], ...]
104
-	 * @since 11.0.0
105
-	 */
106
-	public function getSharesInFolder(string $userId, Folder $node, bool $reshares = false, bool $shallow = true): array;
107
-
108
-	/**
109
-	 * Get shares shared by (initiated) by the provided user.
110
-	 *
111
-	 * @param string $userId
112
-	 * @param IShare::TYPE_* $shareType
113
-	 * @param Node|null $path
114
-	 * @param bool $reshares
115
-	 * @param int $limit The maximum number of returned results, -1 for all results
116
-	 * @param int $offset
117
-	 * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
118
-	 * @return IShare[]
119
-	 * @since 9.0.0
120
-	 */
121
-	public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array;
122
-
123
-	/**
124
-	 * Get shares shared with $user.
125
-	 * Filter by $node if provided
126
-	 *
127
-	 * @param string $userId
128
-	 * @param IShare::TYPE_* $shareType
129
-	 * @param Node|null $node
130
-	 * @param int $limit The maximum number of shares returned, -1 for all
131
-	 * @param int $offset
132
-	 * @return IShare[]
133
-	 * @since 9.0.0
134
-	 */
135
-	public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
136
-
137
-	/**
138
-	 * Get deleted shares shared with $user.
139
-	 * Filter by $node if provided
140
-	 *
141
-	 * @param IShare::TYPE_* $shareType
142
-	 * @param int $limit The maximum number of shares returned, -1 for all
143
-	 * @return IShare[]
144
-	 * @since 14.0.0
145
-	 */
146
-	public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
147
-
148
-	/**
149
-	 * Retrieve a share by the share id.
150
-	 * If the recipient is set make sure to retrieve the file for that user.
151
-	 * This makes sure that if a user has moved/deleted a group share this
152
-	 * is reflected.
153
-	 *
154
-	 * @param string|null $recipient userID of the recipient
155
-	 * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
156
-	 * @throws ShareNotFound
157
-	 * @since 9.0.0
158
-	 */
159
-	public function getShareById(string $id, ?string $recipient = null, bool $onlyValid = true): IShare;
160
-
161
-	/**
162
-	 * Get the share by token possible with password
163
-	 *
164
-	 * @throws ShareNotFound
165
-	 * @since 9.0.0
166
-	 */
167
-	public function getShareByToken(string $token): IShare;
168
-
169
-	/**
170
-	 * Verify the password of a public share
171
-	 *
172
-	 * @since 9.0.0
173
-	 */
174
-	public function checkPassword(IShare $share, ?string $password): bool;
175
-
176
-	/**
177
-	 * The user with UID is deleted.
178
-	 * All share providers have to cleanup the shares with this user as well
179
-	 * as shares owned by this user.
180
-	 * Shares only initiated by this user are fine.
181
-	 *
182
-	 * @since 9.1.0
183
-	 */
184
-	public function userDeleted(string $uid): void;
185
-
186
-	/**
187
-	 * The group with $gid is deleted
188
-	 * We need to clear up all shares to this group
189
-	 *
190
-	 * @since 9.1.0
191
-	 */
192
-	public function groupDeleted(string $gid): void;
193
-
194
-	/**
195
-	 * The user $uid is deleted from the group $gid
196
-	 * All user specific group shares have to be removed
197
-	 *
198
-	 * @since 9.1.0
199
-	 */
200
-	public function userDeletedFromGroup(string $uid, string $gid): void;
201
-
202
-	/**
203
-	 * Get access list to a path. This means
204
-	 * all the users that can access a given path.
205
-	 *
206
-	 * Consider:
207
-	 * -root
208
-	 * |-folder1 (23)
209
-	 *  |-folder2 (32)
210
-	 *   |-fileA (42)
211
-	 *
212
-	 * fileA is shared with user1 and user1@server1 and email1@maildomain1
213
-	 * folder2 is shared with group2 (user4 is a member of group2)
214
-	 * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
215
-	 *                        and email2@maildomain2
216
-	 *
217
-	 * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
218
-	 * [
219
-	 *  users  => [
220
-	 *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
221
-	 *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
222
-	 *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
223
-	 *  ],
224
-	 *  remote => [
225
-	 *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
226
-	 *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
227
-	 *  ],
228
-	 *  public => bool
229
-	 *  mail => [
230
-	 *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
231
-	 *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
232
-	 *  ]
233
-	 *
234
-	 * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
235
-	 * [
236
-	 *  users  => ['user1', 'user2', 'user4'],
237
-	 *  remote => bool,
238
-	 *  public => bool
239
-	 *  mail => ['email1@maildomain1', 'email2@maildomain2']
240
-	 * ]
241
-	 *
242
-	 * This is required for encryption/activity
243
-	 *
244
-	 * @param bool $recursive Should we check all parent folders as well
245
-	 * @param bool $currentAccess Should the user have currently access to the file
246
-	 * @return ($currentAccess is true
247
-	 * 		? array{
248
-	 *     		users?: array<string, array{node_id: int, node_path: string}>,
249
-	 *     		remote?: array<string, array{node_id: int, node_path: string}>,
250
-	 *     		public?: bool,
251
-	 *     		mail?: array<string, array{node_id: int, node_path: string}>
252
-	 *     	}
253
-	 *      : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
254
-	 * @since 12.0.0
255
-	 */
256
-	public function getAccessList(Node $path, bool $recursive = true, bool $currentAccess = false): array;
257
-
258
-	/**
259
-	 * Instantiates a new share object. This is to be passed to
260
-	 * createShare.
261
-	 *
262
-	 * @since 9.0.0
263
-	 */
264
-	public function newShare(): IShare;
265
-
266
-	/**
267
-	 * Is the share API enabled
268
-	 *
269
-	 * @since 9.0.0
270
-	 */
271
-	public function shareApiEnabled(): bool;
272
-
273
-	/**
274
-	 * Is public link sharing enabled
275
-	 *
276
-	 * @param ?IUser $user User to check against group exclusions, defaults to current session user
277
-	 * @return bool
278
-	 * @since 9.0.0
279
-	 * @since 33.0.0 Added optional $user parameter
280
-	 */
281
-	public function shareApiAllowLinks(): bool;
282
-
283
-	/**
284
-	 * Is password on public link required
285
-	 *
286
-	 * @param bool $checkGroupMembership Check group membership exclusion
287
-	 * @since 9.0.0
288
-	 * @since 24.0.0 Added optional $checkGroupMembership parameter
289
-	 */
290
-	public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true): bool;
291
-
292
-	/**
293
-	 * Is default expire date enabled
294
-	 *
295
-	 * @since 9.0.0
296
-	 */
297
-	public function shareApiLinkDefaultExpireDate(): bool;
298
-
299
-	/**
300
-	 * Is default expire date enforced
301
-	 *`
302
-	 * @since 9.0.0
303
-	 */
304
-	public function shareApiLinkDefaultExpireDateEnforced(): bool;
305
-
306
-	/**
307
-	 * Number of default expire days
308
-	 *
309
-	 * @since 9.0.0
310
-	 */
311
-	public function shareApiLinkDefaultExpireDays(): int;
312
-
313
-	/**
314
-	 * Is default internal expire date enabled
315
-	 *
316
-	 * @since 22.0.0
317
-	 */
318
-	public function shareApiInternalDefaultExpireDate(): bool;
319
-
320
-	/**
321
-	 * Is default remote expire date enabled
322
-	 *
323
-	 * @since 22.0.0
324
-	 */
325
-	public function shareApiRemoteDefaultExpireDate(): bool;
326
-
327
-	/**
328
-	 * Is default expire date enforced
329
-	 *
330
-	 * @since 22.0.0
331
-	 */
332
-	public function shareApiInternalDefaultExpireDateEnforced(): bool;
333
-
334
-	/**
335
-	 * Is default expire date enforced for remote shares
336
-	 *
337
-	 * @since 22.0.0
338
-	 */
339
-	public function shareApiRemoteDefaultExpireDateEnforced(): bool;
340
-
341
-	/**
342
-	 * Number of default expire days
343
-	 *
344
-	 * @since 22.0.0
345
-	 */
346
-	public function shareApiInternalDefaultExpireDays(): int;
347
-
348
-	/**
349
-	 * Number of default expire days for remote shares
350
-	 *
351
-	 * @since 22.0.0
352
-	 */
353
-	public function shareApiRemoteDefaultExpireDays(): int;
354
-
355
-	/**
356
-	 * Allow public upload on link shares
357
-	 *
358
-	 * @since 9.0.0
359
-	 */
360
-	public function shareApiLinkAllowPublicUpload(): bool;
361
-
362
-	/**
363
-	 * Check if user can only share with group members.
364
-	 *
365
-	 * @since 9.0.0
366
-	 */
367
-	public function shareWithGroupMembersOnly(): bool;
368
-
369
-	/**
370
-	 * If shareWithGroupMembersOnly is enabled, return an optional
371
-	 * list of groups that must be excluded from the principle of
372
-	 * belonging to the same group.
373
-	 * @return array
374
-	 * @since 27.0.0
375
-	 */
376
-	public function shareWithGroupMembersOnlyExcludeGroupsList(): array;
377
-
378
-	/**
379
-	 * Check if users can share with groups
380
-	 *
381
-	 * @since 9.0.1
382
-	 */
383
-	public function allowGroupSharing(): bool;
384
-
385
-	/**
386
-	 * Check if user enumeration is allowed
387
-	 *
388
-	 * @since 19.0.0
389
-	 */
390
-	public function allowEnumeration(): bool;
391
-
392
-	/**
393
-	 * Check if user enumeration is limited to the users groups
394
-	 *
395
-	 * @since 19.0.0
396
-	 */
397
-	public function limitEnumerationToGroups(): bool;
398
-
399
-	/**
400
-	 * Check if user enumeration is limited to the phonebook matches
401
-	 *
402
-	 * @since 21.0.1
403
-	 */
404
-	public function limitEnumerationToPhone(): bool;
405
-
406
-	/**
407
-	 * Check if user enumeration is allowed to return also on full match
408
-	 * and ignore limitations to phonebook or groups.
409
-	 *
410
-	 * @since 21.0.1
411
-	 */
412
-	public function allowEnumerationFullMatch(): bool;
413
-
414
-	/**
415
-	 * When `allowEnumerationFullMatch` is enabled and `matchEmail` is set,
416
-	 * then also return results for full email matches.
417
-	 *
418
-	 * @since 25.0.0
419
-	 */
420
-	public function matchEmail(): bool;
421
-
422
-	/**
423
-	 * When `allowEnumerationFullMatch` is enabled and `matchUserId` is set,
424
-	 * then also return results for full user id matches.
425
-	 *
426
-	 * @since 33.0.0
427
-	 */
428
-	public function matchUserId(): bool;
429
-
430
-	/**
431
-	 * When `allowEnumerationFullMatch` is enabled and `matchDisplayName` is set,
432
-	 * then also return results for full display name matches.
433
-	 *
434
-	 * @since 33.0.0
435
-	 */
436
-	public function matchDisplayName(): bool;
437
-
438
-	/**
439
-	 * When `allowEnumerationFullMatch` is enabled and `ignoreSecondDisplayName` is set,
440
-	 * then the search should ignore matches on the second displayname and only use the first.
441
-	 *
442
-	 * @since 25.0.0
443
-	 */
444
-	public function ignoreSecondDisplayName(): bool;
445
-
446
-
447
-	/**
448
-	 * Check if custom tokens are allowed
449
-	 *
450
-	 * @since 31.0.0
451
-	 */
452
-	public function allowCustomTokens(): bool;
453
-
454
-	/**
455
-	 * Check if the current user can view the share
456
-	 * even if the download is disabled.
457
-	 *
458
-	 * @since 32.0.0
459
-	 */
460
-	public function allowViewWithoutDownload(): bool;
461
-
462
-	/**
463
-	 * Check if the current user can enumerate the target user
464
-	 *
465
-	 * @since 23.0.0
466
-	 */
467
-	public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool;
468
-
469
-	/**
470
-	 * Check if sharing is disabled for the given user
471
-	 *
472
-	 * @since 9.0.0
473
-	 */
474
-	public function sharingDisabledForUser(?string $userId): bool;
475
-
476
-	/**
477
-	 * Check if outgoing server2server shares are allowed
478
-	 * @since 9.0.0
479
-	 */
480
-	public function outgoingServer2ServerSharesAllowed(): bool;
481
-
482
-	/**
483
-	 * Check if outgoing server2server shares are allowed
484
-	 * @since 14.0.0
485
-	 */
486
-	public function outgoingServer2ServerGroupSharesAllowed(): bool;
487
-
488
-
489
-	/**
490
-	 * Check if a given share provider exists
491
-	 * @param IShare::TYPE_* $shareType
492
-	 * @since 11.0.0
493
-	 */
494
-	public function shareProviderExists(int $shareType): bool;
495
-
496
-	/**
497
-	 * @param string $shareProviderClass
498
-	 * @since 21.0.0
499
-	 */
500
-	public function registerShareProvider(string $shareProviderClass): void;
501
-
502
-	/**
503
-	 * @Internal
504
-	 *
505
-	 * Get all the shares as iterable to reduce memory overhead
506
-	 * Note, since this opens up database cursors the iterable should
507
-	 * be fully iterated.
508
-	 *
509
-	 * @return iterable<IShare>
510
-	 * @since 18.0.0
511
-	 */
512
-	public function getAllShares(): iterable;
513
-
514
-	/**
515
-	 * Generate a unique share token
516
-	 *
517
-	 * @throws ShareTokenException Failed to generate a unique token
518
-	 * @since 31.0.0
519
-	 */
520
-	public function generateToken(): string;
521
-
522
-	/**
523
-	 * Get all users with access to a share
524
-	 *
525
-	 * @param IShare $share
526
-	 * @return iterable<IUser>
527
-	 * @since 33.0.0
528
-	 */
529
-	public function getUsersForShare(IShare $share): iterable;
26
+    /**
27
+     * Create a Share
28
+     *
29
+     * @throws \Exception
30
+     * @since 9.0.0
31
+     */
32
+    public function createShare(IShare $share): IShare;
33
+
34
+    /**
35
+     * Update a share.
36
+     * The target of the share can't be changed this way: use moveShare
37
+     * The share can't be removed this way (permission 0): use deleteShare
38
+     * The state can't be changed this way: use acceptShare
39
+     *
40
+     * @param bool $onlyValid Only updates valid shares, invalid shares will be deleted automatically and are not updated
41
+     * @throws \InvalidArgumentException
42
+     * @since 9.0.0
43
+     */
44
+    public function updateShare(IShare $share, bool $onlyValid = true): IShare;
45
+
46
+    /**
47
+     * Accept a share.
48
+     *
49
+     * @throws \InvalidArgumentException
50
+     * @since 18.0.0
51
+     */
52
+    public function acceptShare(IShare $share, string $recipientId): IShare;
53
+
54
+    /**
55
+     * Delete a share
56
+     *
57
+     * @throws ShareNotFound
58
+     * @throws \InvalidArgumentException
59
+     * @since 9.0.0
60
+     */
61
+    public function deleteShare(IShare $share): void;
62
+
63
+    /**
64
+     * Unshare a file as the recipient.
65
+     * This can be different from a regular delete for example when one of
66
+     * the users in a groups deletes that share. But the provider should
67
+     * handle this.
68
+     *
69
+     * @since 9.0.0
70
+     */
71
+    public function deleteFromSelf(IShare $share, string $recipientId): void;
72
+
73
+    /**
74
+     * Restore the share when it has been deleted
75
+     * Certain share types can be restored when they have been deleted
76
+     * but the provider should properly handle this\
77
+     *
78
+     * @param IShare $share The share to restore
79
+     * @param string $recipientId The user to restore the share for
80
+     * @return IShare The restored share object
81
+     * @throws GenericShareException In case restoring the share failed
82
+     *
83
+     * @since 14.0.0
84
+     */
85
+    public function restoreShare(IShare $share, string $recipientId): IShare;
86
+
87
+    /**
88
+     * Move the share as a recipient of the share.
89
+     * This is updating the share target. So where the recipient has the share mounted.
90
+     *
91
+     * @throws \InvalidArgumentException If $share is a link share or the $recipient does not match
92
+     * @since 9.0.0
93
+     */
94
+    public function moveShare(IShare $share, string $recipientId): IShare;
95
+
96
+    /**
97
+     * Get all shares shared by (initiated) by the provided user in a folder.
98
+     *
99
+     * @param string $userId
100
+     * @param Folder $node
101
+     * @param bool $reshares
102
+     * @param bool $shallow Whether the method should stop at the first level, or look into sub-folders.
103
+     * @return array<int, list<IShare>> [$fileId => IShare[], ...]
104
+     * @since 11.0.0
105
+     */
106
+    public function getSharesInFolder(string $userId, Folder $node, bool $reshares = false, bool $shallow = true): array;
107
+
108
+    /**
109
+     * Get shares shared by (initiated) by the provided user.
110
+     *
111
+     * @param string $userId
112
+     * @param IShare::TYPE_* $shareType
113
+     * @param Node|null $path
114
+     * @param bool $reshares
115
+     * @param int $limit The maximum number of returned results, -1 for all results
116
+     * @param int $offset
117
+     * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
118
+     * @return IShare[]
119
+     * @since 9.0.0
120
+     */
121
+    public function getSharesBy(string $userId, int $shareType, ?Node $path = null, bool $reshares = false, int $limit = 50, int $offset = 0, bool $onlyValid = true): array;
122
+
123
+    /**
124
+     * Get shares shared with $user.
125
+     * Filter by $node if provided
126
+     *
127
+     * @param string $userId
128
+     * @param IShare::TYPE_* $shareType
129
+     * @param Node|null $node
130
+     * @param int $limit The maximum number of shares returned, -1 for all
131
+     * @param int $offset
132
+     * @return IShare[]
133
+     * @since 9.0.0
134
+     */
135
+    public function getSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
136
+
137
+    /**
138
+     * Get deleted shares shared with $user.
139
+     * Filter by $node if provided
140
+     *
141
+     * @param IShare::TYPE_* $shareType
142
+     * @param int $limit The maximum number of shares returned, -1 for all
143
+     * @return IShare[]
144
+     * @since 14.0.0
145
+     */
146
+    public function getDeletedSharedWith(string $userId, int $shareType, ?Node $node = null, int $limit = 50, int $offset = 0): array;
147
+
148
+    /**
149
+     * Retrieve a share by the share id.
150
+     * If the recipient is set make sure to retrieve the file for that user.
151
+     * This makes sure that if a user has moved/deleted a group share this
152
+     * is reflected.
153
+     *
154
+     * @param string|null $recipient userID of the recipient
155
+     * @param bool $onlyValid Only returns valid shares, invalid shares will be deleted automatically and are not returned
156
+     * @throws ShareNotFound
157
+     * @since 9.0.0
158
+     */
159
+    public function getShareById(string $id, ?string $recipient = null, bool $onlyValid = true): IShare;
160
+
161
+    /**
162
+     * Get the share by token possible with password
163
+     *
164
+     * @throws ShareNotFound
165
+     * @since 9.0.0
166
+     */
167
+    public function getShareByToken(string $token): IShare;
168
+
169
+    /**
170
+     * Verify the password of a public share
171
+     *
172
+     * @since 9.0.0
173
+     */
174
+    public function checkPassword(IShare $share, ?string $password): bool;
175
+
176
+    /**
177
+     * The user with UID is deleted.
178
+     * All share providers have to cleanup the shares with this user as well
179
+     * as shares owned by this user.
180
+     * Shares only initiated by this user are fine.
181
+     *
182
+     * @since 9.1.0
183
+     */
184
+    public function userDeleted(string $uid): void;
185
+
186
+    /**
187
+     * The group with $gid is deleted
188
+     * We need to clear up all shares to this group
189
+     *
190
+     * @since 9.1.0
191
+     */
192
+    public function groupDeleted(string $gid): void;
193
+
194
+    /**
195
+     * The user $uid is deleted from the group $gid
196
+     * All user specific group shares have to be removed
197
+     *
198
+     * @since 9.1.0
199
+     */
200
+    public function userDeletedFromGroup(string $uid, string $gid): void;
201
+
202
+    /**
203
+     * Get access list to a path. This means
204
+     * all the users that can access a given path.
205
+     *
206
+     * Consider:
207
+     * -root
208
+     * |-folder1 (23)
209
+     *  |-folder2 (32)
210
+     *   |-fileA (42)
211
+     *
212
+     * fileA is shared with user1 and user1@server1 and email1@maildomain1
213
+     * folder2 is shared with group2 (user4 is a member of group2)
214
+     * folder1 is shared with user2 (renamed to "folder (1)") and user2@server2
215
+     *                        and email2@maildomain2
216
+     *
217
+     * Then the access list to '/folder1/folder2/fileA' with $currentAccess is:
218
+     * [
219
+     *  users  => [
220
+     *      'user1' => ['node_id' => 42, 'node_path' => '/fileA'],
221
+     *      'user4' => ['node_id' => 32, 'node_path' => '/folder2'],
222
+     *      'user2' => ['node_id' => 23, 'node_path' => '/folder (1)'],
223
+     *  ],
224
+     *  remote => [
225
+     *      'user1@server1' => ['node_id' => 42, 'token' => 'SeCr3t'],
226
+     *      'user2@server2' => ['node_id' => 23, 'token' => 'FooBaR'],
227
+     *  ],
228
+     *  public => bool
229
+     *  mail => [
230
+     *      'email1@maildomain1' => ['node_id' => 42, 'token' => 'aBcDeFg'],
231
+     *      'email2@maildomain2' => ['node_id' => 23, 'token' => 'hIjKlMn'],
232
+     *  ]
233
+     *
234
+     * The access list to '/folder1/folder2/fileA' **without** $currentAccess is:
235
+     * [
236
+     *  users  => ['user1', 'user2', 'user4'],
237
+     *  remote => bool,
238
+     *  public => bool
239
+     *  mail => ['email1@maildomain1', 'email2@maildomain2']
240
+     * ]
241
+     *
242
+     * This is required for encryption/activity
243
+     *
244
+     * @param bool $recursive Should we check all parent folders as well
245
+     * @param bool $currentAccess Should the user have currently access to the file
246
+     * @return ($currentAccess is true
247
+     * 		? array{
248
+     *     		users?: array<string, array{node_id: int, node_path: string}>,
249
+     *     		remote?: array<string, array{node_id: int, node_path: string}>,
250
+     *     		public?: bool,
251
+     *     		mail?: array<string, array{node_id: int, node_path: string}>
252
+     *     	}
253
+     *      : array{users?: list<string>, remote?: bool, public?: bool, mail?: list<string>})
254
+     * @since 12.0.0
255
+     */
256
+    public function getAccessList(Node $path, bool $recursive = true, bool $currentAccess = false): array;
257
+
258
+    /**
259
+     * Instantiates a new share object. This is to be passed to
260
+     * createShare.
261
+     *
262
+     * @since 9.0.0
263
+     */
264
+    public function newShare(): IShare;
265
+
266
+    /**
267
+     * Is the share API enabled
268
+     *
269
+     * @since 9.0.0
270
+     */
271
+    public function shareApiEnabled(): bool;
272
+
273
+    /**
274
+     * Is public link sharing enabled
275
+     *
276
+     * @param ?IUser $user User to check against group exclusions, defaults to current session user
277
+     * @return bool
278
+     * @since 9.0.0
279
+     * @since 33.0.0 Added optional $user parameter
280
+     */
281
+    public function shareApiAllowLinks(): bool;
282
+
283
+    /**
284
+     * Is password on public link required
285
+     *
286
+     * @param bool $checkGroupMembership Check group membership exclusion
287
+     * @since 9.0.0
288
+     * @since 24.0.0 Added optional $checkGroupMembership parameter
289
+     */
290
+    public function shareApiLinkEnforcePassword(bool $checkGroupMembership = true): bool;
291
+
292
+    /**
293
+     * Is default expire date enabled
294
+     *
295
+     * @since 9.0.0
296
+     */
297
+    public function shareApiLinkDefaultExpireDate(): bool;
298
+
299
+    /**
300
+     * Is default expire date enforced
301
+     *`
302
+     * @since 9.0.0
303
+     */
304
+    public function shareApiLinkDefaultExpireDateEnforced(): bool;
305
+
306
+    /**
307
+     * Number of default expire days
308
+     *
309
+     * @since 9.0.0
310
+     */
311
+    public function shareApiLinkDefaultExpireDays(): int;
312
+
313
+    /**
314
+     * Is default internal expire date enabled
315
+     *
316
+     * @since 22.0.0
317
+     */
318
+    public function shareApiInternalDefaultExpireDate(): bool;
319
+
320
+    /**
321
+     * Is default remote expire date enabled
322
+     *
323
+     * @since 22.0.0
324
+     */
325
+    public function shareApiRemoteDefaultExpireDate(): bool;
326
+
327
+    /**
328
+     * Is default expire date enforced
329
+     *
330
+     * @since 22.0.0
331
+     */
332
+    public function shareApiInternalDefaultExpireDateEnforced(): bool;
333
+
334
+    /**
335
+     * Is default expire date enforced for remote shares
336
+     *
337
+     * @since 22.0.0
338
+     */
339
+    public function shareApiRemoteDefaultExpireDateEnforced(): bool;
340
+
341
+    /**
342
+     * Number of default expire days
343
+     *
344
+     * @since 22.0.0
345
+     */
346
+    public function shareApiInternalDefaultExpireDays(): int;
347
+
348
+    /**
349
+     * Number of default expire days for remote shares
350
+     *
351
+     * @since 22.0.0
352
+     */
353
+    public function shareApiRemoteDefaultExpireDays(): int;
354
+
355
+    /**
356
+     * Allow public upload on link shares
357
+     *
358
+     * @since 9.0.0
359
+     */
360
+    public function shareApiLinkAllowPublicUpload(): bool;
361
+
362
+    /**
363
+     * Check if user can only share with group members.
364
+     *
365
+     * @since 9.0.0
366
+     */
367
+    public function shareWithGroupMembersOnly(): bool;
368
+
369
+    /**
370
+     * If shareWithGroupMembersOnly is enabled, return an optional
371
+     * list of groups that must be excluded from the principle of
372
+     * belonging to the same group.
373
+     * @return array
374
+     * @since 27.0.0
375
+     */
376
+    public function shareWithGroupMembersOnlyExcludeGroupsList(): array;
377
+
378
+    /**
379
+     * Check if users can share with groups
380
+     *
381
+     * @since 9.0.1
382
+     */
383
+    public function allowGroupSharing(): bool;
384
+
385
+    /**
386
+     * Check if user enumeration is allowed
387
+     *
388
+     * @since 19.0.0
389
+     */
390
+    public function allowEnumeration(): bool;
391
+
392
+    /**
393
+     * Check if user enumeration is limited to the users groups
394
+     *
395
+     * @since 19.0.0
396
+     */
397
+    public function limitEnumerationToGroups(): bool;
398
+
399
+    /**
400
+     * Check if user enumeration is limited to the phonebook matches
401
+     *
402
+     * @since 21.0.1
403
+     */
404
+    public function limitEnumerationToPhone(): bool;
405
+
406
+    /**
407
+     * Check if user enumeration is allowed to return also on full match
408
+     * and ignore limitations to phonebook or groups.
409
+     *
410
+     * @since 21.0.1
411
+     */
412
+    public function allowEnumerationFullMatch(): bool;
413
+
414
+    /**
415
+     * When `allowEnumerationFullMatch` is enabled and `matchEmail` is set,
416
+     * then also return results for full email matches.
417
+     *
418
+     * @since 25.0.0
419
+     */
420
+    public function matchEmail(): bool;
421
+
422
+    /**
423
+     * When `allowEnumerationFullMatch` is enabled and `matchUserId` is set,
424
+     * then also return results for full user id matches.
425
+     *
426
+     * @since 33.0.0
427
+     */
428
+    public function matchUserId(): bool;
429
+
430
+    /**
431
+     * When `allowEnumerationFullMatch` is enabled and `matchDisplayName` is set,
432
+     * then also return results for full display name matches.
433
+     *
434
+     * @since 33.0.0
435
+     */
436
+    public function matchDisplayName(): bool;
437
+
438
+    /**
439
+     * When `allowEnumerationFullMatch` is enabled and `ignoreSecondDisplayName` is set,
440
+     * then the search should ignore matches on the second displayname and only use the first.
441
+     *
442
+     * @since 25.0.0
443
+     */
444
+    public function ignoreSecondDisplayName(): bool;
445
+
446
+
447
+    /**
448
+     * Check if custom tokens are allowed
449
+     *
450
+     * @since 31.0.0
451
+     */
452
+    public function allowCustomTokens(): bool;
453
+
454
+    /**
455
+     * Check if the current user can view the share
456
+     * even if the download is disabled.
457
+     *
458
+     * @since 32.0.0
459
+     */
460
+    public function allowViewWithoutDownload(): bool;
461
+
462
+    /**
463
+     * Check if the current user can enumerate the target user
464
+     *
465
+     * @since 23.0.0
466
+     */
467
+    public function currentUserCanEnumerateTargetUser(?IUser $currentUser, IUser $targetUser): bool;
468
+
469
+    /**
470
+     * Check if sharing is disabled for the given user
471
+     *
472
+     * @since 9.0.0
473
+     */
474
+    public function sharingDisabledForUser(?string $userId): bool;
475
+
476
+    /**
477
+     * Check if outgoing server2server shares are allowed
478
+     * @since 9.0.0
479
+     */
480
+    public function outgoingServer2ServerSharesAllowed(): bool;
481
+
482
+    /**
483
+     * Check if outgoing server2server shares are allowed
484
+     * @since 14.0.0
485
+     */
486
+    public function outgoingServer2ServerGroupSharesAllowed(): bool;
487
+
488
+
489
+    /**
490
+     * Check if a given share provider exists
491
+     * @param IShare::TYPE_* $shareType
492
+     * @since 11.0.0
493
+     */
494
+    public function shareProviderExists(int $shareType): bool;
495
+
496
+    /**
497
+     * @param string $shareProviderClass
498
+     * @since 21.0.0
499
+     */
500
+    public function registerShareProvider(string $shareProviderClass): void;
501
+
502
+    /**
503
+     * @Internal
504
+     *
505
+     * Get all the shares as iterable to reduce memory overhead
506
+     * Note, since this opens up database cursors the iterable should
507
+     * be fully iterated.
508
+     *
509
+     * @return iterable<IShare>
510
+     * @since 18.0.0
511
+     */
512
+    public function getAllShares(): iterable;
513
+
514
+    /**
515
+     * Generate a unique share token
516
+     *
517
+     * @throws ShareTokenException Failed to generate a unique token
518
+     * @since 31.0.0
519
+     */
520
+    public function generateToken(): string;
521
+
522
+    /**
523
+     * Get all users with access to a share
524
+     *
525
+     * @param IShare $share
526
+     * @return iterable<IUser>
527
+     * @since 33.0.0
528
+     */
529
+    public function getUsersForShare(IShare $share): iterable;
530 530
 }
Please login to merge, or discard this patch.
lib/public/Share/IShareProviderGetUsers.php 1 patch
Indentation   +8 added lines, -8 removed lines patch added patch discarded remove patch
@@ -17,12 +17,12 @@
 block discarded – undo
17 17
  * @since 33.0.0
18 18
  */
19 19
 interface IShareProviderGetUsers extends IShareProvider {
20
-	/**
21
-	 * Get all users with access to a share
22
-	 *
23
-	 * @param IShare $share
24
-	 * @return iterable<IUser>
25
-	 * @since 33.0.0
26
-	 */
27
-	public function getUsersForShare(IShare $share): iterable;
20
+    /**
21
+     * Get all users with access to a share
22
+     *
23
+     * @param IShare $share
24
+     * @return iterable<IUser>
25
+     * @since 33.0.0
26
+     */
27
+    public function getUsersForShare(IShare $share): iterable;
28 28
 }
Please login to merge, or discard this patch.