Completed
Push — master ( fb0b56...db3e84 )
by Daniel
27:54
created
lib/public/Util.php 1 patch
Indentation   +637 added lines, -637 removed lines patch added patch discarded remove patch
@@ -25,641 +25,641 @@
 block discarded – undo
25 25
  * @since 4.0.0
26 26
  */
27 27
 class Util {
28
-	private static ?IManager $shareManager = null;
29
-
30
-	private static array $scriptsInit = [];
31
-	private static array $scripts = [];
32
-	private static array $scriptDeps = [];
33
-
34
-	/**
35
-	 * get the current installed version of Nextcloud
36
-	 * @return array
37
-	 * @since 4.0.0
38
-	 * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion
39
-	 */
40
-	public static function getVersion() {
41
-		return Server::get(ServerVersion::class)->getVersion();
42
-	}
43
-
44
-	/**
45
-	 * @since 17.0.0
46
-	 */
47
-	public static function hasExtendedSupport(): bool {
48
-		try {
49
-			/** @var \OCP\Support\Subscription\IRegistry */
50
-			$subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
51
-			return $subscriptionRegistry->delegateHasExtendedSupport();
52
-		} catch (ContainerExceptionInterface $e) {
53
-		}
54
-		return \OCP\Server::get(IConfig::class)->getSystemValueBool('extendedSupport', false);
55
-	}
56
-
57
-	/**
58
-	 * Set current update channel
59
-	 * @param string $channel
60
-	 * @since 8.1.0
61
-	 */
62
-	public static function setChannel($channel) {
63
-		\OCP\Server::get(IConfig::class)->setSystemValue('updater.release.channel', $channel);
64
-	}
65
-
66
-	/**
67
-	 * Get current update channel
68
-	 * @return string
69
-	 * @since 8.1.0
70
-	 * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel
71
-	 */
72
-	public static function getChannel() {
73
-		return \OCP\Server::get(ServerVersion::class)->getChannel();
74
-	}
75
-
76
-	/**
77
-	 * check if sharing is disabled for the current user
78
-	 *
79
-	 * @return boolean
80
-	 * @since 7.0.0
81
-	 * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser
82
-	 */
83
-	public static function isSharingDisabledForUser() {
84
-		if (self::$shareManager === null) {
85
-			self::$shareManager = Server::get(IManager::class);
86
-		}
87
-
88
-		$user = Server::get(\OCP\IUserSession::class)->getUser();
89
-
90
-		return self::$shareManager->sharingDisabledForUser($user?->getUID());
91
-	}
92
-
93
-	/**
94
-	 * get l10n object
95
-	 * @since 6.0.0 - parameter $language was added in 8.0.0
96
-	 */
97
-	public static function getL10N(string $application, ?string $language = null): IL10N {
98
-		return Server::get(\OCP\L10N\IFactory::class)->get($application, $language);
99
-	}
100
-
101
-	/**
102
-	 * Add a css file
103
-	 *
104
-	 * @param string $application application id
105
-	 * @param ?string $file filename
106
-	 * @param bool $prepend prepend the style to the beginning of the list
107
-	 * @since 4.0.0
108
-	 */
109
-	public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void {
110
-		\OC_Util::addStyle($application, $file, $prepend);
111
-	}
112
-
113
-	/**
114
-	 * Add a standalone init js file that is loaded for initialization
115
-	 *
116
-	 * Be careful loading scripts using this method as they are loaded early
117
-	 * and block the initial page rendering. They should not have dependencies
118
-	 * on any other scripts than core-common and core-main.
119
-	 *
120
-	 * @since 28.0.0
121
-	 */
122
-	public static function addInitScript(string $application, string $file): void {
123
-		if (!empty($application)) {
124
-			$path = "$application/js/$file";
125
-		} else {
126
-			$path = "js/$file";
127
-		}
128
-
129
-		// We need to handle the translation BEFORE the init script
130
-		// is loaded, as the init script might use translations
131
-		if ($application !== 'core' && !str_contains($file, 'l10n')) {
132
-			self::addTranslations($application, null, true);
133
-		}
134
-
135
-		self::$scriptsInit[] = $path;
136
-	}
137
-
138
-	/**
139
-	 * add a javascript file
140
-	 *
141
-	 * @param string $application
142
-	 * @param string|null $file
143
-	 * @param string $afterAppId
144
-	 * @param bool $prepend
145
-	 * @since 4.0.0
146
-	 */
147
-	public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void {
148
-		if (!empty($application)) {
149
-			$path = "$application/js/$file";
150
-		} else {
151
-			$path = "js/$file";
152
-		}
153
-
154
-		// Inject js translations if we load a script for
155
-		// a specific app that is not core, as those js files
156
-		// need separate handling
157
-		if ($application !== 'core'
158
-			&& $file !== null
159
-			&& !str_contains($file, 'l10n')) {
160
-			self::addTranslations($application);
161
-		}
162
-
163
-		// store app in dependency list
164
-		if (!array_key_exists($application, self::$scriptDeps)) {
165
-			self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]);
166
-		} else {
167
-			self::$scriptDeps[$application]->addDep($afterAppId);
168
-		}
169
-
170
-		if ($prepend) {
171
-			array_unshift(self::$scripts[$application], $path);
172
-		} else {
173
-			self::$scripts[$application][] = $path;
174
-		}
175
-	}
176
-
177
-	/**
178
-	 * Return the list of scripts injected to the page
179
-	 *
180
-	 * @return array
181
-	 * @since 24.0.0
182
-	 */
183
-	public static function getScripts(): array {
184
-		// Sort scriptDeps into sortedScriptDeps
185
-		$scriptSort = \OCP\Server::get(AppScriptSort::class);
186
-		$sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps);
187
-
188
-		// Flatten array and remove duplicates
189
-		$sortedScripts = array_merge([self::$scriptsInit], $sortedScripts);
190
-		$sortedScripts = array_merge(...array_values($sortedScripts));
191
-
192
-		// Override core-common and core-main order
193
-		if (in_array('core/js/main', $sortedScripts)) {
194
-			array_unshift($sortedScripts, 'core/js/main');
195
-		}
196
-		if (in_array('core/js/common', $sortedScripts)) {
197
-			array_unshift($sortedScripts, 'core/js/common');
198
-		}
199
-
200
-		return array_unique($sortedScripts);
201
-	}
202
-
203
-	/**
204
-	 * Add a translation JS file
205
-	 * @param string $application application id
206
-	 * @param string $languageCode language code, defaults to the current locale
207
-	 * @param bool $init whether the translations should be loaded early or not
208
-	 * @since 8.0.0
209
-	 */
210
-	public static function addTranslations($application, $languageCode = null, $init = false) {
211
-		if (is_null($languageCode)) {
212
-			$languageCode = \OCP\Server::get(IFactory::class)->findLanguage($application);
213
-		}
214
-		if (!empty($application)) {
215
-			$path = "$application/l10n/$languageCode";
216
-		} else {
217
-			$path = "l10n/$languageCode";
218
-		}
219
-
220
-		if ($init) {
221
-			self::$scriptsInit[] = $path;
222
-		} else {
223
-			self::$scripts[$application][] = $path;
224
-		}
225
-	}
226
-
227
-	/**
228
-	 * Add a custom element to the header
229
-	 * If $text is null then the element will be written as empty element.
230
-	 * So use "" to get a closing tag.
231
-	 * @param string $tag tag name of the element
232
-	 * @param array $attributes array of attributes for the element
233
-	 * @param string $text the text content for the element
234
-	 * @since 4.0.0
235
-	 */
236
-	public static function addHeader($tag, $attributes, $text = null) {
237
-		\OC_Util::addHeader($tag, $attributes, $text);
238
-	}
239
-
240
-	/**
241
-	 * Creates an absolute url to the given app and file.
242
-	 * @param string $app app
243
-	 * @param string $file file
244
-	 * @param array $args array with param=>value, will be appended to the returned url
245
-	 *                    The value of $args will be urlencoded
246
-	 * @return string the url
247
-	 * @since 4.0.0 - parameter $args was added in 4.5.0
248
-	 */
249
-	public static function linkToAbsolute($app, $file, $args = []) {
250
-		$urlGenerator = \OCP\Server::get(IURLGenerator::class);
251
-		return $urlGenerator->getAbsoluteURL(
252
-			$urlGenerator->linkTo($app, $file, $args)
253
-		);
254
-	}
255
-
256
-	/**
257
-	 * Creates an absolute url for remote use.
258
-	 * @param string $service id
259
-	 * @return string the url
260
-	 * @since 4.0.0
261
-	 */
262
-	public static function linkToRemote($service) {
263
-		$urlGenerator = \OCP\Server::get(IURLGenerator::class);
264
-		$remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
265
-		return $urlGenerator->getAbsoluteURL(
266
-			$remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
267
-		);
268
-	}
269
-
270
-	/**
271
-	 * Returns the server host name without an eventual port number
272
-	 * @return string the server hostname
273
-	 * @since 5.0.0
274
-	 */
275
-	public static function getServerHostName() {
276
-		$host_name = \OCP\Server::get(IRequest::class)->getServerHost();
277
-		// strip away port number (if existing)
278
-		$colon_pos = strpos($host_name, ':');
279
-		if ($colon_pos != false) {
280
-			$host_name = substr($host_name, 0, $colon_pos);
281
-		}
282
-		return $host_name;
283
-	}
284
-
285
-	/**
286
-	 * Returns the default email address
287
-	 * @param string $user_part the user part of the address
288
-	 * @return string the default email address
289
-	 *
290
-	 * Assembles a default email address (using the server hostname
291
-	 * and the given user part, and returns it
292
-	 * Example: when given lostpassword-noreply as $user_part param,
293
-	 *     and is currently accessed via http(s)://example.com/,
294
-	 *     it would return '[email protected]'
295
-	 *
296
-	 * If the configuration value 'mail_from_address' is set in
297
-	 * config.php, this value will override the $user_part that
298
-	 * is passed to this function
299
-	 * @since 5.0.0
300
-	 */
301
-	public static function getDefaultEmailAddress(string $user_part): string {
302
-		$config = \OCP\Server::get(IConfig::class);
303
-		$user_part = $config->getSystemValueString('mail_from_address', $user_part);
304
-		$host_name = self::getServerHostName();
305
-		$host_name = $config->getSystemValueString('mail_domain', $host_name);
306
-		$defaultEmailAddress = $user_part . '@' . $host_name;
307
-
308
-		$emailValidator = \OCP\Server::get(IEmailValidator::class);
309
-		if ($emailValidator->isValid($defaultEmailAddress)) {
310
-			return $defaultEmailAddress;
311
-		}
312
-
313
-		// in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain'
314
-		return $user_part . '@localhost.localdomain';
315
-	}
316
-
317
-	/**
318
-	 * Converts string to int of float depending on if it fits an int
319
-	 * @param numeric-string|float|int $number numeric string
320
-	 * @return int|float int if it fits, float if it is too big
321
-	 * @since 26.0.0
322
-	 */
323
-	public static function numericToNumber(string|float|int $number): int|float {
324
-		/* This is a hack to cast to (int|float) */
325
-		return 0 + (string)$number;
326
-	}
327
-
328
-	/**
329
-	 * Make a human file size (2048 to 2 kB)
330
-	 * @param int|float $bytes file size in bytes
331
-	 * @return string a human readable file size
332
-	 * @since 4.0.0
333
-	 */
334
-	public static function humanFileSize(int|float $bytes): string {
335
-		if ($bytes < 0) {
336
-			return '?';
337
-		}
338
-		if ($bytes < 1024) {
339
-			return "$bytes B";
340
-		}
341
-		$bytes = round($bytes / 1024, 0);
342
-		if ($bytes < 1024) {
343
-			return "$bytes KB";
344
-		}
345
-		$bytes = round($bytes / 1024, 1);
346
-		if ($bytes < 1024) {
347
-			return "$bytes MB";
348
-		}
349
-		$bytes = round($bytes / 1024, 1);
350
-		if ($bytes < 1024) {
351
-			return "$bytes GB";
352
-		}
353
-		$bytes = round($bytes / 1024, 1);
354
-		if ($bytes < 1024) {
355
-			return "$bytes TB";
356
-		}
357
-
358
-		$bytes = round($bytes / 1024, 1);
359
-		return "$bytes PB";
360
-	}
361
-
362
-	/**
363
-	 * Make a computer file size (2 kB to 2048)
364
-	 * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
365
-	 *
366
-	 * @param string $str file size in a fancy format
367
-	 * @return false|int|float a file size in bytes
368
-	 * @since 4.0.0
369
-	 */
370
-	public static function computerFileSize(string $str): false|int|float {
371
-		$str = strtolower($str);
372
-		if (is_numeric($str)) {
373
-			return Util::numericToNumber($str);
374
-		}
375
-
376
-		$bytes_array = [
377
-			'b' => 1,
378
-			'k' => 1024,
379
-			'kb' => 1024,
380
-			'mb' => 1024 * 1024,
381
-			'm' => 1024 * 1024,
382
-			'gb' => 1024 * 1024 * 1024,
383
-			'g' => 1024 * 1024 * 1024,
384
-			'tb' => 1024 * 1024 * 1024 * 1024,
385
-			't' => 1024 * 1024 * 1024 * 1024,
386
-			'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
387
-			'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388
-		];
389
-
390
-		$bytes = (float)$str;
391
-
392
-		if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393
-			$bytes *= $bytes_array[$matches[1]];
394
-		} else {
395
-			return false;
396
-		}
397
-
398
-		return Util::numericToNumber(round($bytes));
399
-	}
400
-
401
-	/**
402
-	 * connects a function to a hook
403
-	 *
404
-	 * @param string $signalClass class name of emitter
405
-	 * @param string $signalName name of signal
406
-	 * @param string|object $slotClass class name of slot
407
-	 * @param string $slotName name of slot
408
-	 * @return bool
409
-	 *
410
-	 * This function makes it very easy to connect to use hooks.
411
-	 *
412
-	 * TODO: write example
413
-	 * @since 4.0.0
414
-	 * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener
415
-	 */
416
-	public static function connectHook($signalClass, $signalName, $slotClass, $slotName) {
417
-		return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName);
418
-	}
419
-
420
-	/**
421
-	 * Emits a signal. To get data from the slot use references!
422
-	 * @param string $signalclass class name of emitter
423
-	 * @param string $signalname name of signal
424
-	 * @param array $params default: array() array with additional data
425
-	 * @return bool true if slots exists or false if not
426
-	 *
427
-	 * TODO: write example
428
-	 * @since 4.0.0
429
-	 * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent
430
-	 */
431
-	public static function emitHook($signalclass, $signalname, $params = []) {
432
-		return \OC_Hook::emit($signalclass, $signalname, $params);
433
-	}
434
-
435
-	/**
436
-	 * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare
437
-	 * multiple Template elements which invoke `callRegister`. If the value
438
-	 * would not be cached these unit-tests would fail.
439
-	 * @var string
440
-	 */
441
-	private static $token = '';
442
-
443
-	/**
444
-	 * Register an get/post call. This is important to prevent CSRF attacks
445
-	 * @since 4.5.0
446
-	 * @deprecated 32.0.0 directly use CsrfTokenManager instead
447
-	 */
448
-	public static function callRegister() {
449
-		if (self::$token === '') {
450
-			self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
451
-		}
452
-		return self::$token;
453
-	}
454
-
455
-	/**
456
-	 * Used to sanitize HTML
457
-	 *
458
-	 * This function is used to sanitize HTML and should be applied on any
459
-	 * string or array of strings before displaying it on a web page.
460
-	 *
461
-	 * @param string|string[] $value
462
-	 * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter.
463
-	 * @since 4.5.0
464
-	 */
465
-	public static function sanitizeHTML($value) {
466
-		return \OC_Util::sanitizeHTML($value);
467
-	}
468
-
469
-	/**
470
-	 * Public function to encode url parameters
471
-	 *
472
-	 * This function is used to encode path to file before output.
473
-	 * Encoding is done according to RFC 3986 with one exception:
474
-	 * Character '/' is preserved as is.
475
-	 *
476
-	 * @param string $component part of URI to encode
477
-	 * @return string
478
-	 * @since 6.0.0
479
-	 */
480
-	public static function encodePath($component) {
481
-		return \OC_Util::encodePath($component);
482
-	}
483
-
484
-	/**
485
-	 * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
486
-	 *
487
-	 * @param array $input The array to work on
488
-	 * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
489
-	 * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
490
-	 * @return array
491
-	 * @since 4.5.0
492
-	 */
493
-	public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
494
-		$case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
495
-		$ret = [];
496
-		foreach ($input as $k => $v) {
497
-			$ret[mb_convert_case($k, $case, $encoding)] = $v;
498
-		}
499
-		return $ret;
500
-	}
501
-
502
-	/**
503
-	 * performs a search in a nested array
504
-	 *
505
-	 * @param array $haystack the array to be searched
506
-	 * @param string $needle the search string
507
-	 * @param mixed $index optional, only search this key name
508
-	 * @return mixed the key of the matching field, otherwise false
509
-	 * @since 4.5.0
510
-	 * @deprecated 15.0.0
511
-	 */
512
-	public static function recursiveArraySearch($haystack, $needle, $index = null) {
513
-		$aIt = new \RecursiveArrayIterator($haystack);
514
-		$it = new \RecursiveIteratorIterator($aIt);
515
-
516
-		while ($it->valid()) {
517
-			if (((isset($index) && ($it->key() == $index)) || !isset($index)) && ($it->current() == $needle)) {
518
-				return $aIt->key();
519
-			}
520
-
521
-			$it->next();
522
-		}
523
-
524
-		return false;
525
-	}
526
-
527
-	/**
528
-	 * calculates the maximum upload size respecting system settings, free space and user quota
529
-	 *
530
-	 * @param string $dir the current folder where the user currently operates
531
-	 * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
532
-	 * @return int|float number of bytes representing
533
-	 * @since 5.0.0
534
-	 */
535
-	public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
536
-		if (is_null($free) || $free < 0) {
537
-			$free = self::freeSpace($dir);
538
-		}
539
-		return min($free, self::uploadLimit());
540
-	}
541
-
542
-	/**
543
-	 * Calculate free space left within user quota
544
-	 * @param string $dir the current folder where the user currently operates
545
-	 * @return int|float number of bytes representing
546
-	 * @since 7.0.0
547
-	 */
548
-	public static function freeSpace(string $dir): int|float {
549
-		$freeSpace = \OC\Files\Filesystem::free_space($dir);
550
-		if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
551
-			$freeSpace = max($freeSpace, 0);
552
-			return $freeSpace;
553
-		} else {
554
-			return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
555
-		}
556
-	}
557
-
558
-	/**
559
-	 * Calculate PHP upload limit
560
-	 *
561
-	 * @return int|float number of bytes representing
562
-	 * @since 7.0.0
563
-	 */
564
-	public static function uploadLimit(): int|float {
565
-		$ini = Server::get(IniGetWrapper::class);
566
-		$upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
567
-		$post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0;
568
-		if ($upload_max_filesize === 0 && $post_max_size === 0) {
569
-			return INF;
570
-		} elseif ($upload_max_filesize === 0 || $post_max_size === 0) {
571
-			return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
572
-		} else {
573
-			return min($upload_max_filesize, $post_max_size);
574
-		}
575
-	}
576
-
577
-	/**
578
-	 * Compare two strings to provide a natural sort
579
-	 * @param string $a first string to compare
580
-	 * @param string $b second string to compare
581
-	 * @return int -1 if $b comes before $a, 1 if $a comes before $b
582
-	 *             or 0 if the strings are identical
583
-	 * @since 7.0.0
584
-	 */
585
-	public static function naturalSortCompare($a, $b) {
586
-		return \OC\NaturalSort::getInstance()->compare($a, $b);
587
-	}
588
-
589
-	/**
590
-	 * Check if a password is required for each public link
591
-	 *
592
-	 * @param bool $checkGroupMembership Check group membership exclusion
593
-	 * @return boolean
594
-	 * @since 7.0.0
595
-	 */
596
-	public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
597
-		return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership);
598
-	}
599
-
600
-	/**
601
-	 * check if share API enforces a default expire date
602
-	 * @return boolean
603
-	 * @since 8.0.0
604
-	 */
605
-	public static function isDefaultExpireDateEnforced() {
606
-		return \OC_Util::isDefaultExpireDateEnforced();
607
-	}
608
-
609
-	protected static $needUpgradeCache = null;
610
-
611
-	/**
612
-	 * Checks whether the current version needs upgrade.
613
-	 *
614
-	 * @return bool true if upgrade is needed, false otherwise
615
-	 * @since 7.0.0
616
-	 */
617
-	public static function needUpgrade() {
618
-		if (!isset(self::$needUpgradeCache)) {
619
-			self::$needUpgradeCache = \OC_Util::needUpgrade(\OCP\Server::get(\OC\SystemConfig::class));
620
-		}
621
-		return self::$needUpgradeCache;
622
-	}
623
-
624
-	/**
625
-	 * Sometimes a string has to be shortened to fit within a certain maximum
626
-	 * data length in bytes. substr() you may break multibyte characters,
627
-	 * because it operates on single byte level. mb_substr() operates on
628
-	 * characters, so does not ensure that the shortened string satisfies the
629
-	 * max length in bytes.
630
-	 *
631
-	 * For example, json_encode is messing with multibyte characters a lot,
632
-	 * replacing them with something along "\u1234".
633
-	 *
634
-	 * This function shortens the string with by $accuracy (-5) from
635
-	 * $dataLength characters, until it fits within $dataLength bytes.
636
-	 *
637
-	 * @since 23.0.0
638
-	 */
639
-	public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string {
640
-		$temp = mb_substr($subject, 0, $dataLength);
641
-		// json encodes encapsulates the string in double quotes, they need to be substracted
642
-		while ((strlen(json_encode($temp)) - 2) > $dataLength) {
643
-			$temp = mb_substr($temp, 0, -$accuracy);
644
-		}
645
-		return $temp;
646
-	}
647
-
648
-	/**
649
-	 * Check if a function is enabled in the php configuration
650
-	 *
651
-	 * @since 25.0.0
652
-	 */
653
-	public static function isFunctionEnabled(string $functionName): bool {
654
-		if (!function_exists($functionName)) {
655
-			return false;
656
-		}
657
-		$ini = Server::get(IniGetWrapper::class);
658
-		$disabled = explode(',', $ini->get('disable_functions') ?: '');
659
-		$disabled = array_map('trim', $disabled);
660
-		if (in_array($functionName, $disabled)) {
661
-			return false;
662
-		}
663
-		return true;
664
-	}
28
+    private static ?IManager $shareManager = null;
29
+
30
+    private static array $scriptsInit = [];
31
+    private static array $scripts = [];
32
+    private static array $scriptDeps = [];
33
+
34
+    /**
35
+     * get the current installed version of Nextcloud
36
+     * @return array
37
+     * @since 4.0.0
38
+     * @deprecated 31.0.0 Use \OCP\ServerVersion::getVersion
39
+     */
40
+    public static function getVersion() {
41
+        return Server::get(ServerVersion::class)->getVersion();
42
+    }
43
+
44
+    /**
45
+     * @since 17.0.0
46
+     */
47
+    public static function hasExtendedSupport(): bool {
48
+        try {
49
+            /** @var \OCP\Support\Subscription\IRegistry */
50
+            $subscriptionRegistry = Server::get(\OCP\Support\Subscription\IRegistry::class);
51
+            return $subscriptionRegistry->delegateHasExtendedSupport();
52
+        } catch (ContainerExceptionInterface $e) {
53
+        }
54
+        return \OCP\Server::get(IConfig::class)->getSystemValueBool('extendedSupport', false);
55
+    }
56
+
57
+    /**
58
+     * Set current update channel
59
+     * @param string $channel
60
+     * @since 8.1.0
61
+     */
62
+    public static function setChannel($channel) {
63
+        \OCP\Server::get(IConfig::class)->setSystemValue('updater.release.channel', $channel);
64
+    }
65
+
66
+    /**
67
+     * Get current update channel
68
+     * @return string
69
+     * @since 8.1.0
70
+     * @deprecated 31.0.0 Use \OCP\ServerVersion::getChannel
71
+     */
72
+    public static function getChannel() {
73
+        return \OCP\Server::get(ServerVersion::class)->getChannel();
74
+    }
75
+
76
+    /**
77
+     * check if sharing is disabled for the current user
78
+     *
79
+     * @return boolean
80
+     * @since 7.0.0
81
+     * @deprecated 9.1.0 Use Server::get(\OCP\Share\IManager::class)->sharingDisabledForUser
82
+     */
83
+    public static function isSharingDisabledForUser() {
84
+        if (self::$shareManager === null) {
85
+            self::$shareManager = Server::get(IManager::class);
86
+        }
87
+
88
+        $user = Server::get(\OCP\IUserSession::class)->getUser();
89
+
90
+        return self::$shareManager->sharingDisabledForUser($user?->getUID());
91
+    }
92
+
93
+    /**
94
+     * get l10n object
95
+     * @since 6.0.0 - parameter $language was added in 8.0.0
96
+     */
97
+    public static function getL10N(string $application, ?string $language = null): IL10N {
98
+        return Server::get(\OCP\L10N\IFactory::class)->get($application, $language);
99
+    }
100
+
101
+    /**
102
+     * Add a css file
103
+     *
104
+     * @param string $application application id
105
+     * @param ?string $file filename
106
+     * @param bool $prepend prepend the style to the beginning of the list
107
+     * @since 4.0.0
108
+     */
109
+    public static function addStyle(string $application, ?string $file = null, bool $prepend = false): void {
110
+        \OC_Util::addStyle($application, $file, $prepend);
111
+    }
112
+
113
+    /**
114
+     * Add a standalone init js file that is loaded for initialization
115
+     *
116
+     * Be careful loading scripts using this method as they are loaded early
117
+     * and block the initial page rendering. They should not have dependencies
118
+     * on any other scripts than core-common and core-main.
119
+     *
120
+     * @since 28.0.0
121
+     */
122
+    public static function addInitScript(string $application, string $file): void {
123
+        if (!empty($application)) {
124
+            $path = "$application/js/$file";
125
+        } else {
126
+            $path = "js/$file";
127
+        }
128
+
129
+        // We need to handle the translation BEFORE the init script
130
+        // is loaded, as the init script might use translations
131
+        if ($application !== 'core' && !str_contains($file, 'l10n')) {
132
+            self::addTranslations($application, null, true);
133
+        }
134
+
135
+        self::$scriptsInit[] = $path;
136
+    }
137
+
138
+    /**
139
+     * add a javascript file
140
+     *
141
+     * @param string $application
142
+     * @param string|null $file
143
+     * @param string $afterAppId
144
+     * @param bool $prepend
145
+     * @since 4.0.0
146
+     */
147
+    public static function addScript(string $application, ?string $file = null, string $afterAppId = 'core', bool $prepend = false): void {
148
+        if (!empty($application)) {
149
+            $path = "$application/js/$file";
150
+        } else {
151
+            $path = "js/$file";
152
+        }
153
+
154
+        // Inject js translations if we load a script for
155
+        // a specific app that is not core, as those js files
156
+        // need separate handling
157
+        if ($application !== 'core'
158
+            && $file !== null
159
+            && !str_contains($file, 'l10n')) {
160
+            self::addTranslations($application);
161
+        }
162
+
163
+        // store app in dependency list
164
+        if (!array_key_exists($application, self::$scriptDeps)) {
165
+            self::$scriptDeps[$application] = new AppScriptDependency($application, [$afterAppId]);
166
+        } else {
167
+            self::$scriptDeps[$application]->addDep($afterAppId);
168
+        }
169
+
170
+        if ($prepend) {
171
+            array_unshift(self::$scripts[$application], $path);
172
+        } else {
173
+            self::$scripts[$application][] = $path;
174
+        }
175
+    }
176
+
177
+    /**
178
+     * Return the list of scripts injected to the page
179
+     *
180
+     * @return array
181
+     * @since 24.0.0
182
+     */
183
+    public static function getScripts(): array {
184
+        // Sort scriptDeps into sortedScriptDeps
185
+        $scriptSort = \OCP\Server::get(AppScriptSort::class);
186
+        $sortedScripts = $scriptSort->sort(self::$scripts, self::$scriptDeps);
187
+
188
+        // Flatten array and remove duplicates
189
+        $sortedScripts = array_merge([self::$scriptsInit], $sortedScripts);
190
+        $sortedScripts = array_merge(...array_values($sortedScripts));
191
+
192
+        // Override core-common and core-main order
193
+        if (in_array('core/js/main', $sortedScripts)) {
194
+            array_unshift($sortedScripts, 'core/js/main');
195
+        }
196
+        if (in_array('core/js/common', $sortedScripts)) {
197
+            array_unshift($sortedScripts, 'core/js/common');
198
+        }
199
+
200
+        return array_unique($sortedScripts);
201
+    }
202
+
203
+    /**
204
+     * Add a translation JS file
205
+     * @param string $application application id
206
+     * @param string $languageCode language code, defaults to the current locale
207
+     * @param bool $init whether the translations should be loaded early or not
208
+     * @since 8.0.0
209
+     */
210
+    public static function addTranslations($application, $languageCode = null, $init = false) {
211
+        if (is_null($languageCode)) {
212
+            $languageCode = \OCP\Server::get(IFactory::class)->findLanguage($application);
213
+        }
214
+        if (!empty($application)) {
215
+            $path = "$application/l10n/$languageCode";
216
+        } else {
217
+            $path = "l10n/$languageCode";
218
+        }
219
+
220
+        if ($init) {
221
+            self::$scriptsInit[] = $path;
222
+        } else {
223
+            self::$scripts[$application][] = $path;
224
+        }
225
+    }
226
+
227
+    /**
228
+     * Add a custom element to the header
229
+     * If $text is null then the element will be written as empty element.
230
+     * So use "" to get a closing tag.
231
+     * @param string $tag tag name of the element
232
+     * @param array $attributes array of attributes for the element
233
+     * @param string $text the text content for the element
234
+     * @since 4.0.0
235
+     */
236
+    public static function addHeader($tag, $attributes, $text = null) {
237
+        \OC_Util::addHeader($tag, $attributes, $text);
238
+    }
239
+
240
+    /**
241
+     * Creates an absolute url to the given app and file.
242
+     * @param string $app app
243
+     * @param string $file file
244
+     * @param array $args array with param=>value, will be appended to the returned url
245
+     *                    The value of $args will be urlencoded
246
+     * @return string the url
247
+     * @since 4.0.0 - parameter $args was added in 4.5.0
248
+     */
249
+    public static function linkToAbsolute($app, $file, $args = []) {
250
+        $urlGenerator = \OCP\Server::get(IURLGenerator::class);
251
+        return $urlGenerator->getAbsoluteURL(
252
+            $urlGenerator->linkTo($app, $file, $args)
253
+        );
254
+    }
255
+
256
+    /**
257
+     * Creates an absolute url for remote use.
258
+     * @param string $service id
259
+     * @return string the url
260
+     * @since 4.0.0
261
+     */
262
+    public static function linkToRemote($service) {
263
+        $urlGenerator = \OCP\Server::get(IURLGenerator::class);
264
+        $remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
265
+        return $urlGenerator->getAbsoluteURL(
266
+            $remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
267
+        );
268
+    }
269
+
270
+    /**
271
+     * Returns the server host name without an eventual port number
272
+     * @return string the server hostname
273
+     * @since 5.0.0
274
+     */
275
+    public static function getServerHostName() {
276
+        $host_name = \OCP\Server::get(IRequest::class)->getServerHost();
277
+        // strip away port number (if existing)
278
+        $colon_pos = strpos($host_name, ':');
279
+        if ($colon_pos != false) {
280
+            $host_name = substr($host_name, 0, $colon_pos);
281
+        }
282
+        return $host_name;
283
+    }
284
+
285
+    /**
286
+     * Returns the default email address
287
+     * @param string $user_part the user part of the address
288
+     * @return string the default email address
289
+     *
290
+     * Assembles a default email address (using the server hostname
291
+     * and the given user part, and returns it
292
+     * Example: when given lostpassword-noreply as $user_part param,
293
+     *     and is currently accessed via http(s)://example.com/,
294
+     *     it would return '[email protected]'
295
+     *
296
+     * If the configuration value 'mail_from_address' is set in
297
+     * config.php, this value will override the $user_part that
298
+     * is passed to this function
299
+     * @since 5.0.0
300
+     */
301
+    public static function getDefaultEmailAddress(string $user_part): string {
302
+        $config = \OCP\Server::get(IConfig::class);
303
+        $user_part = $config->getSystemValueString('mail_from_address', $user_part);
304
+        $host_name = self::getServerHostName();
305
+        $host_name = $config->getSystemValueString('mail_domain', $host_name);
306
+        $defaultEmailAddress = $user_part . '@' . $host_name;
307
+
308
+        $emailValidator = \OCP\Server::get(IEmailValidator::class);
309
+        if ($emailValidator->isValid($defaultEmailAddress)) {
310
+            return $defaultEmailAddress;
311
+        }
312
+
313
+        // in case we cannot build a valid email address from the hostname let's fallback to 'localhost.localdomain'
314
+        return $user_part . '@localhost.localdomain';
315
+    }
316
+
317
+    /**
318
+     * Converts string to int of float depending on if it fits an int
319
+     * @param numeric-string|float|int $number numeric string
320
+     * @return int|float int if it fits, float if it is too big
321
+     * @since 26.0.0
322
+     */
323
+    public static function numericToNumber(string|float|int $number): int|float {
324
+        /* This is a hack to cast to (int|float) */
325
+        return 0 + (string)$number;
326
+    }
327
+
328
+    /**
329
+     * Make a human file size (2048 to 2 kB)
330
+     * @param int|float $bytes file size in bytes
331
+     * @return string a human readable file size
332
+     * @since 4.0.0
333
+     */
334
+    public static function humanFileSize(int|float $bytes): string {
335
+        if ($bytes < 0) {
336
+            return '?';
337
+        }
338
+        if ($bytes < 1024) {
339
+            return "$bytes B";
340
+        }
341
+        $bytes = round($bytes / 1024, 0);
342
+        if ($bytes < 1024) {
343
+            return "$bytes KB";
344
+        }
345
+        $bytes = round($bytes / 1024, 1);
346
+        if ($bytes < 1024) {
347
+            return "$bytes MB";
348
+        }
349
+        $bytes = round($bytes / 1024, 1);
350
+        if ($bytes < 1024) {
351
+            return "$bytes GB";
352
+        }
353
+        $bytes = round($bytes / 1024, 1);
354
+        if ($bytes < 1024) {
355
+            return "$bytes TB";
356
+        }
357
+
358
+        $bytes = round($bytes / 1024, 1);
359
+        return "$bytes PB";
360
+    }
361
+
362
+    /**
363
+     * Make a computer file size (2 kB to 2048)
364
+     * Inspired by: https://www.php.net/manual/en/function.filesize.php#92418
365
+     *
366
+     * @param string $str file size in a fancy format
367
+     * @return false|int|float a file size in bytes
368
+     * @since 4.0.0
369
+     */
370
+    public static function computerFileSize(string $str): false|int|float {
371
+        $str = strtolower($str);
372
+        if (is_numeric($str)) {
373
+            return Util::numericToNumber($str);
374
+        }
375
+
376
+        $bytes_array = [
377
+            'b' => 1,
378
+            'k' => 1024,
379
+            'kb' => 1024,
380
+            'mb' => 1024 * 1024,
381
+            'm' => 1024 * 1024,
382
+            'gb' => 1024 * 1024 * 1024,
383
+            'g' => 1024 * 1024 * 1024,
384
+            'tb' => 1024 * 1024 * 1024 * 1024,
385
+            't' => 1024 * 1024 * 1024 * 1024,
386
+            'pb' => 1024 * 1024 * 1024 * 1024 * 1024,
387
+            'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388
+        ];
389
+
390
+        $bytes = (float)$str;
391
+
392
+        if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393
+            $bytes *= $bytes_array[$matches[1]];
394
+        } else {
395
+            return false;
396
+        }
397
+
398
+        return Util::numericToNumber(round($bytes));
399
+    }
400
+
401
+    /**
402
+     * connects a function to a hook
403
+     *
404
+     * @param string $signalClass class name of emitter
405
+     * @param string $signalName name of signal
406
+     * @param string|object $slotClass class name of slot
407
+     * @param string $slotName name of slot
408
+     * @return bool
409
+     *
410
+     * This function makes it very easy to connect to use hooks.
411
+     *
412
+     * TODO: write example
413
+     * @since 4.0.0
414
+     * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::addListener
415
+     */
416
+    public static function connectHook($signalClass, $signalName, $slotClass, $slotName) {
417
+        return \OC_Hook::connect($signalClass, $signalName, $slotClass, $slotName);
418
+    }
419
+
420
+    /**
421
+     * Emits a signal. To get data from the slot use references!
422
+     * @param string $signalclass class name of emitter
423
+     * @param string $signalname name of signal
424
+     * @param array $params default: array() array with additional data
425
+     * @return bool true if slots exists or false if not
426
+     *
427
+     * TODO: write example
428
+     * @since 4.0.0
429
+     * @deprecated 21.0.0 use \OCP\EventDispatcher\IEventDispatcher::dispatchTypedEvent
430
+     */
431
+    public static function emitHook($signalclass, $signalname, $params = []) {
432
+        return \OC_Hook::emit($signalclass, $signalname, $params);
433
+    }
434
+
435
+    /**
436
+     * Cached encrypted CSRF token. Some static unit-tests of ownCloud compare
437
+     * multiple Template elements which invoke `callRegister`. If the value
438
+     * would not be cached these unit-tests would fail.
439
+     * @var string
440
+     */
441
+    private static $token = '';
442
+
443
+    /**
444
+     * Register an get/post call. This is important to prevent CSRF attacks
445
+     * @since 4.5.0
446
+     * @deprecated 32.0.0 directly use CsrfTokenManager instead
447
+     */
448
+    public static function callRegister() {
449
+        if (self::$token === '') {
450
+            self::$token = \OCP\Server::get(CsrfTokenManager::class)->getToken()->getEncryptedValue();
451
+        }
452
+        return self::$token;
453
+    }
454
+
455
+    /**
456
+     * Used to sanitize HTML
457
+     *
458
+     * This function is used to sanitize HTML and should be applied on any
459
+     * string or array of strings before displaying it on a web page.
460
+     *
461
+     * @param string|string[] $value
462
+     * @return ($value is array ? string[] : string) an array of sanitized strings or a single sanitized string, depends on the input parameter.
463
+     * @since 4.5.0
464
+     */
465
+    public static function sanitizeHTML($value) {
466
+        return \OC_Util::sanitizeHTML($value);
467
+    }
468
+
469
+    /**
470
+     * Public function to encode url parameters
471
+     *
472
+     * This function is used to encode path to file before output.
473
+     * Encoding is done according to RFC 3986 with one exception:
474
+     * Character '/' is preserved as is.
475
+     *
476
+     * @param string $component part of URI to encode
477
+     * @return string
478
+     * @since 6.0.0
479
+     */
480
+    public static function encodePath($component) {
481
+        return \OC_Util::encodePath($component);
482
+    }
483
+
484
+    /**
485
+     * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is.
486
+     *
487
+     * @param array $input The array to work on
488
+     * @param int $case Either MB_CASE_UPPER or MB_CASE_LOWER (default)
489
+     * @param string $encoding The encoding parameter is the character encoding. Defaults to UTF-8
490
+     * @return array
491
+     * @since 4.5.0
492
+     */
493
+    public static function mb_array_change_key_case($input, $case = MB_CASE_LOWER, $encoding = 'UTF-8') {
494
+        $case = ($case != MB_CASE_UPPER) ? MB_CASE_LOWER : MB_CASE_UPPER;
495
+        $ret = [];
496
+        foreach ($input as $k => $v) {
497
+            $ret[mb_convert_case($k, $case, $encoding)] = $v;
498
+        }
499
+        return $ret;
500
+    }
501
+
502
+    /**
503
+     * performs a search in a nested array
504
+     *
505
+     * @param array $haystack the array to be searched
506
+     * @param string $needle the search string
507
+     * @param mixed $index optional, only search this key name
508
+     * @return mixed the key of the matching field, otherwise false
509
+     * @since 4.5.0
510
+     * @deprecated 15.0.0
511
+     */
512
+    public static function recursiveArraySearch($haystack, $needle, $index = null) {
513
+        $aIt = new \RecursiveArrayIterator($haystack);
514
+        $it = new \RecursiveIteratorIterator($aIt);
515
+
516
+        while ($it->valid()) {
517
+            if (((isset($index) && ($it->key() == $index)) || !isset($index)) && ($it->current() == $needle)) {
518
+                return $aIt->key();
519
+            }
520
+
521
+            $it->next();
522
+        }
523
+
524
+        return false;
525
+    }
526
+
527
+    /**
528
+     * calculates the maximum upload size respecting system settings, free space and user quota
529
+     *
530
+     * @param string $dir the current folder where the user currently operates
531
+     * @param int|float|null $free the number of bytes free on the storage holding $dir, if not set this will be received from the storage directly
532
+     * @return int|float number of bytes representing
533
+     * @since 5.0.0
534
+     */
535
+    public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
536
+        if (is_null($free) || $free < 0) {
537
+            $free = self::freeSpace($dir);
538
+        }
539
+        return min($free, self::uploadLimit());
540
+    }
541
+
542
+    /**
543
+     * Calculate free space left within user quota
544
+     * @param string $dir the current folder where the user currently operates
545
+     * @return int|float number of bytes representing
546
+     * @since 7.0.0
547
+     */
548
+    public static function freeSpace(string $dir): int|float {
549
+        $freeSpace = \OC\Files\Filesystem::free_space($dir);
550
+        if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
551
+            $freeSpace = max($freeSpace, 0);
552
+            return $freeSpace;
553
+        } else {
554
+            return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
555
+        }
556
+    }
557
+
558
+    /**
559
+     * Calculate PHP upload limit
560
+     *
561
+     * @return int|float number of bytes representing
562
+     * @since 7.0.0
563
+     */
564
+    public static function uploadLimit(): int|float {
565
+        $ini = Server::get(IniGetWrapper::class);
566
+        $upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
567
+        $post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0;
568
+        if ($upload_max_filesize === 0 && $post_max_size === 0) {
569
+            return INF;
570
+        } elseif ($upload_max_filesize === 0 || $post_max_size === 0) {
571
+            return max($upload_max_filesize, $post_max_size); //only the non 0 value counts
572
+        } else {
573
+            return min($upload_max_filesize, $post_max_size);
574
+        }
575
+    }
576
+
577
+    /**
578
+     * Compare two strings to provide a natural sort
579
+     * @param string $a first string to compare
580
+     * @param string $b second string to compare
581
+     * @return int -1 if $b comes before $a, 1 if $a comes before $b
582
+     *             or 0 if the strings are identical
583
+     * @since 7.0.0
584
+     */
585
+    public static function naturalSortCompare($a, $b) {
586
+        return \OC\NaturalSort::getInstance()->compare($a, $b);
587
+    }
588
+
589
+    /**
590
+     * Check if a password is required for each public link
591
+     *
592
+     * @param bool $checkGroupMembership Check group membership exclusion
593
+     * @return boolean
594
+     * @since 7.0.0
595
+     */
596
+    public static function isPublicLinkPasswordRequired(bool $checkGroupMembership = true) {
597
+        return \OC_Util::isPublicLinkPasswordRequired($checkGroupMembership);
598
+    }
599
+
600
+    /**
601
+     * check if share API enforces a default expire date
602
+     * @return boolean
603
+     * @since 8.0.0
604
+     */
605
+    public static function isDefaultExpireDateEnforced() {
606
+        return \OC_Util::isDefaultExpireDateEnforced();
607
+    }
608
+
609
+    protected static $needUpgradeCache = null;
610
+
611
+    /**
612
+     * Checks whether the current version needs upgrade.
613
+     *
614
+     * @return bool true if upgrade is needed, false otherwise
615
+     * @since 7.0.0
616
+     */
617
+    public static function needUpgrade() {
618
+        if (!isset(self::$needUpgradeCache)) {
619
+            self::$needUpgradeCache = \OC_Util::needUpgrade(\OCP\Server::get(\OC\SystemConfig::class));
620
+        }
621
+        return self::$needUpgradeCache;
622
+    }
623
+
624
+    /**
625
+     * Sometimes a string has to be shortened to fit within a certain maximum
626
+     * data length in bytes. substr() you may break multibyte characters,
627
+     * because it operates on single byte level. mb_substr() operates on
628
+     * characters, so does not ensure that the shortened string satisfies the
629
+     * max length in bytes.
630
+     *
631
+     * For example, json_encode is messing with multibyte characters a lot,
632
+     * replacing them with something along "\u1234".
633
+     *
634
+     * This function shortens the string with by $accuracy (-5) from
635
+     * $dataLength characters, until it fits within $dataLength bytes.
636
+     *
637
+     * @since 23.0.0
638
+     */
639
+    public static function shortenMultibyteString(string $subject, int $dataLength, int $accuracy = 5): string {
640
+        $temp = mb_substr($subject, 0, $dataLength);
641
+        // json encodes encapsulates the string in double quotes, they need to be substracted
642
+        while ((strlen(json_encode($temp)) - 2) > $dataLength) {
643
+            $temp = mb_substr($temp, 0, -$accuracy);
644
+        }
645
+        return $temp;
646
+    }
647
+
648
+    /**
649
+     * Check if a function is enabled in the php configuration
650
+     *
651
+     * @since 25.0.0
652
+     */
653
+    public static function isFunctionEnabled(string $functionName): bool {
654
+        if (!function_exists($functionName)) {
655
+            return false;
656
+        }
657
+        $ini = Server::get(IniGetWrapper::class);
658
+        $disabled = explode(',', $ini->get('disable_functions') ?: '');
659
+        $disabled = array_map('trim', $disabled);
660
+        if (in_array($functionName, $disabled)) {
661
+            return false;
662
+        }
663
+        return true;
664
+    }
665 665
 }
Please login to merge, or discard this patch.
lib/private/Collaboration/Collaborators/MailPlugin.php 1 patch
Indentation   +236 added lines, -236 removed lines patch added patch discarded remove patch
@@ -21,242 +21,242 @@
 block discarded – undo
21 21
 use OCP\Share\IShare;
22 22
 
23 23
 class MailPlugin implements ISearchPlugin {
24
-	protected bool $shareWithGroupOnly;
25
-
26
-	protected bool $shareeEnumeration;
27
-
28
-	protected bool $shareeEnumerationInGroupOnly;
29
-
30
-	protected bool $shareeEnumerationPhone;
31
-
32
-	protected bool $shareeEnumerationFullMatch;
33
-
34
-	protected bool $shareeEnumerationFullMatchEmail;
35
-
36
-	public function __construct(
37
-		private IManager $contactsManager,
38
-		private ICloudIdManager $cloudIdManager,
39
-		private IConfig $config,
40
-		private IGroupManager $groupManager,
41
-		private KnownUserService $knownUserService,
42
-		private IUserSession $userSession,
43
-		private IEmailValidator $emailValidator,
44
-		private mixed $shareWithGroupOnlyExcludeGroupsList = [],
45
-	) {
46
-		$this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
47
-		$this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
48
-		$this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
49
-		$this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
50
-		$this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
51
-		$this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
52
-
53
-		if ($this->shareWithGroupOnly) {
54
-			$this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
55
-		}
56
-	}
57
-
58
-	/**
59
-	 * {@inheritdoc}
60
-	 */
61
-	public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
62
-		if ($this->shareeEnumerationFullMatch && !$this->shareeEnumerationFullMatchEmail) {
63
-			return false;
64
-		}
65
-
66
-		// Extract the email address from "Foo Bar <[email protected]>" and then search with "[email protected]" instead
67
-		$result = preg_match('/<([^@]+@.+)>$/', $search, $matches);
68
-		if ($result && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) {
69
-			return $this->search($matches[1], $limit, $offset, $searchResult);
70
-		}
71
-
72
-		$currentUserId = $this->userSession->getUser()->getUID();
73
-
74
-		$result = $userResults = ['wide' => [], 'exact' => []];
75
-		$userType = new SearchResultType('users');
76
-		$emailType = new SearchResultType('emails');
77
-
78
-		// Search in contacts
79
-		$addressBookContacts = $this->contactsManager->search(
80
-			$search,
81
-			['EMAIL', 'FN'],
82
-			[
83
-				'limit' => $limit,
84
-				'offset' => $offset,
85
-				'enumeration' => $this->shareeEnumeration,
86
-				'fullmatch' => $this->shareeEnumerationFullMatch,
87
-			]
88
-		);
89
-		$lowerSearch = strtolower($search);
90
-		foreach ($addressBookContacts as $contact) {
91
-			if (isset($contact['EMAIL'])) {
92
-				$emailAddresses = $contact['EMAIL'];
93
-				if (\is_string($emailAddresses)) {
94
-					$emailAddresses = [$emailAddresses];
95
-				}
96
-				foreach ($emailAddresses as $type => $emailAddress) {
97
-					$displayName = $emailAddress;
98
-					$emailAddressType = null;
99
-					if (\is_array($emailAddress)) {
100
-						$emailAddressData = $emailAddress;
101
-						$emailAddress = $emailAddressData['value'];
102
-						$emailAddressType = $emailAddressData['type'];
103
-					}
104
-
105
-					if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
106
-						continue;
107
-					}
108
-
109
-					if (isset($contact['FN'])) {
110
-						$displayName = $contact['FN'] . ' (' . $emailAddress . ')';
111
-					}
112
-					$exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
113
-
114
-					if (isset($contact['isLocalSystemBook'])) {
115
-						if ($this->shareWithGroupOnly) {
116
-							/*
24
+    protected bool $shareWithGroupOnly;
25
+
26
+    protected bool $shareeEnumeration;
27
+
28
+    protected bool $shareeEnumerationInGroupOnly;
29
+
30
+    protected bool $shareeEnumerationPhone;
31
+
32
+    protected bool $shareeEnumerationFullMatch;
33
+
34
+    protected bool $shareeEnumerationFullMatchEmail;
35
+
36
+    public function __construct(
37
+        private IManager $contactsManager,
38
+        private ICloudIdManager $cloudIdManager,
39
+        private IConfig $config,
40
+        private IGroupManager $groupManager,
41
+        private KnownUserService $knownUserService,
42
+        private IUserSession $userSession,
43
+        private IEmailValidator $emailValidator,
44
+        private mixed $shareWithGroupOnlyExcludeGroupsList = [],
45
+    ) {
46
+        $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes';
47
+        $this->shareWithGroupOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes';
48
+        $this->shareeEnumerationInGroupOnly = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
49
+        $this->shareeEnumerationPhone = $this->shareeEnumeration && $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_phone', 'no') === 'yes';
50
+        $this->shareeEnumerationFullMatch = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match', 'yes') === 'yes';
51
+        $this->shareeEnumerationFullMatchEmail = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_full_match_email', 'yes') === 'yes';
52
+
53
+        if ($this->shareWithGroupOnly) {
54
+            $this->shareWithGroupOnlyExcludeGroupsList = json_decode($this->config->getAppValue('core', 'shareapi_only_share_with_group_members_exclude_group_list', ''), true) ?? [];
55
+        }
56
+    }
57
+
58
+    /**
59
+     * {@inheritdoc}
60
+     */
61
+    public function search($search, $limit, $offset, ISearchResult $searchResult): bool {
62
+        if ($this->shareeEnumerationFullMatch && !$this->shareeEnumerationFullMatchEmail) {
63
+            return false;
64
+        }
65
+
66
+        // Extract the email address from "Foo Bar <[email protected]>" and then search with "[email protected]" instead
67
+        $result = preg_match('/<([^@]+@.+)>$/', $search, $matches);
68
+        if ($result && filter_var($matches[1], FILTER_VALIDATE_EMAIL)) {
69
+            return $this->search($matches[1], $limit, $offset, $searchResult);
70
+        }
71
+
72
+        $currentUserId = $this->userSession->getUser()->getUID();
73
+
74
+        $result = $userResults = ['wide' => [], 'exact' => []];
75
+        $userType = new SearchResultType('users');
76
+        $emailType = new SearchResultType('emails');
77
+
78
+        // Search in contacts
79
+        $addressBookContacts = $this->contactsManager->search(
80
+            $search,
81
+            ['EMAIL', 'FN'],
82
+            [
83
+                'limit' => $limit,
84
+                'offset' => $offset,
85
+                'enumeration' => $this->shareeEnumeration,
86
+                'fullmatch' => $this->shareeEnumerationFullMatch,
87
+            ]
88
+        );
89
+        $lowerSearch = strtolower($search);
90
+        foreach ($addressBookContacts as $contact) {
91
+            if (isset($contact['EMAIL'])) {
92
+                $emailAddresses = $contact['EMAIL'];
93
+                if (\is_string($emailAddresses)) {
94
+                    $emailAddresses = [$emailAddresses];
95
+                }
96
+                foreach ($emailAddresses as $type => $emailAddress) {
97
+                    $displayName = $emailAddress;
98
+                    $emailAddressType = null;
99
+                    if (\is_array($emailAddress)) {
100
+                        $emailAddressData = $emailAddress;
101
+                        $emailAddress = $emailAddressData['value'];
102
+                        $emailAddressType = $emailAddressData['type'];
103
+                    }
104
+
105
+                    if (!filter_var($emailAddress, FILTER_VALIDATE_EMAIL)) {
106
+                        continue;
107
+                    }
108
+
109
+                    if (isset($contact['FN'])) {
110
+                        $displayName = $contact['FN'] . ' (' . $emailAddress . ')';
111
+                    }
112
+                    $exactEmailMatch = strtolower($emailAddress) === $lowerSearch;
113
+
114
+                    if (isset($contact['isLocalSystemBook'])) {
115
+                        if ($this->shareWithGroupOnly) {
116
+                            /*
117 117
 							 * Check if the user may share with the user associated with the e-mail of the just found contact
118 118
 							 */
119
-							$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
120
-
121
-							// ShareWithGroupOnly filtering
122
-							$userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList);
123
-
124
-							$found = false;
125
-							foreach ($userGroups as $userGroup) {
126
-								if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
127
-									$found = true;
128
-									break;
129
-								}
130
-							}
131
-							if (!$found) {
132
-								continue;
133
-							}
134
-						}
135
-						if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
136
-							try {
137
-								$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
138
-							} catch (\InvalidArgumentException $e) {
139
-								continue;
140
-							}
141
-
142
-							if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
143
-								$singleResult = [[
144
-									'label' => $displayName,
145
-									'uuid' => $contact['UID'] ?? $emailAddress,
146
-									'name' => $contact['FN'] ?? $displayName,
147
-									'value' => [
148
-										'shareType' => IShare::TYPE_USER,
149
-										'shareWith' => $cloud->getUser(),
150
-									],
151
-									'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
152
-
153
-								]];
154
-								$searchResult->addResultSet($userType, [], $singleResult);
155
-								$searchResult->markExactIdMatch($emailType);
156
-							}
157
-							return false;
158
-						}
159
-
160
-						if ($this->shareeEnumeration) {
161
-							try {
162
-								$cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
163
-							} catch (\InvalidArgumentException $e) {
164
-								continue;
165
-							}
166
-
167
-							$addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone);
168
-							if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) {
169
-								$addToWide = true;
170
-							}
171
-
172
-							if (!$addToWide && $this->shareeEnumerationInGroupOnly) {
173
-								$addToWide = false;
174
-								$userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
175
-								foreach ($userGroups as $userGroup) {
176
-									if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
177
-										$addToWide = true;
178
-										break;
179
-									}
180
-								}
181
-							}
182
-							if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
183
-								$userResults['wide'][] = [
184
-									'label' => $displayName,
185
-									'uuid' => $contact['UID'] ?? $emailAddress,
186
-									'name' => $contact['FN'] ?? $displayName,
187
-									'value' => [
188
-										'shareType' => IShare::TYPE_USER,
189
-										'shareWith' => $cloud->getUser(),
190
-									],
191
-									'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
192
-								];
193
-								continue;
194
-							}
195
-						}
196
-						continue;
197
-					}
198
-
199
-					if ($exactEmailMatch
200
-						|| (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) {
201
-						if ($exactEmailMatch) {
202
-							$searchResult->markExactIdMatch($emailType);
203
-						}
204
-						$result['exact'][] = [
205
-							'label' => $displayName,
206
-							'uuid' => $contact['UID'] ?? $emailAddress,
207
-							'name' => $contact['FN'] ?? $displayName,
208
-							'type' => $emailAddressType ?? '',
209
-							'value' => [
210
-								'shareType' => IShare::TYPE_EMAIL,
211
-								'shareWith' => $emailAddress,
212
-							],
213
-						];
214
-					} else {
215
-						$result['wide'][] = [
216
-							'label' => $displayName,
217
-							'uuid' => $contact['UID'] ?? $emailAddress,
218
-							'name' => $contact['FN'] ?? $displayName,
219
-							'type' => $emailAddressType ?? '',
220
-							'value' => [
221
-								'shareType' => IShare::TYPE_EMAIL,
222
-								'shareWith' => $emailAddress,
223
-							],
224
-						];
225
-					}
226
-				}
227
-			}
228
-		}
229
-
230
-		$reachedEnd = true;
231
-		if ($this->shareeEnumeration) {
232
-			$reachedEnd = (count($result['wide']) < $offset + $limit)
233
-				&& (count($userResults['wide']) < $offset + $limit);
234
-
235
-			$result['wide'] = array_slice($result['wide'], $offset, $limit);
236
-			$userResults['wide'] = array_slice($userResults['wide'], $offset, $limit);
237
-		}
238
-
239
-		if (!$searchResult->hasExactIdMatch($emailType) && $this->emailValidator->isValid($search)) {
240
-			$result['exact'][] = [
241
-				'label' => $search,
242
-				'uuid' => $search,
243
-				'value' => [
244
-					'shareType' => IShare::TYPE_EMAIL,
245
-					'shareWith' => $search,
246
-				],
247
-			];
248
-		}
249
-
250
-		if (!empty($userResults['wide'])) {
251
-			$searchResult->addResultSet($userType, $userResults['wide'], []);
252
-		}
253
-		$searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
254
-
255
-		return !$reachedEnd;
256
-	}
257
-
258
-	public function isCurrentUser(ICloudId $cloud): bool {
259
-		$currentUser = $this->userSession->getUser();
260
-		return $currentUser instanceof IUser && $currentUser->getUID() === $cloud->getUser();
261
-	}
119
+                            $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
120
+
121
+                            // ShareWithGroupOnly filtering
122
+                            $userGroups = array_diff($userGroups, $this->shareWithGroupOnlyExcludeGroupsList);
123
+
124
+                            $found = false;
125
+                            foreach ($userGroups as $userGroup) {
126
+                                if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
127
+                                    $found = true;
128
+                                    break;
129
+                                }
130
+                            }
131
+                            if (!$found) {
132
+                                continue;
133
+                            }
134
+                        }
135
+                        if ($exactEmailMatch && $this->shareeEnumerationFullMatch) {
136
+                            try {
137
+                                $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
138
+                            } catch (\InvalidArgumentException $e) {
139
+                                continue;
140
+                            }
141
+
142
+                            if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
143
+                                $singleResult = [[
144
+                                    'label' => $displayName,
145
+                                    'uuid' => $contact['UID'] ?? $emailAddress,
146
+                                    'name' => $contact['FN'] ?? $displayName,
147
+                                    'value' => [
148
+                                        'shareType' => IShare::TYPE_USER,
149
+                                        'shareWith' => $cloud->getUser(),
150
+                                    ],
151
+                                    'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
152
+
153
+                                ]];
154
+                                $searchResult->addResultSet($userType, [], $singleResult);
155
+                                $searchResult->markExactIdMatch($emailType);
156
+                            }
157
+                            return false;
158
+                        }
159
+
160
+                        if ($this->shareeEnumeration) {
161
+                            try {
162
+                                $cloud = $this->cloudIdManager->resolveCloudId($contact['CLOUD'][0] ?? '');
163
+                            } catch (\InvalidArgumentException $e) {
164
+                                continue;
165
+                            }
166
+
167
+                            $addToWide = !($this->shareeEnumerationInGroupOnly || $this->shareeEnumerationPhone);
168
+                            if (!$addToWide && $this->shareeEnumerationPhone && $this->knownUserService->isKnownToUser($currentUserId, $contact['UID'])) {
169
+                                $addToWide = true;
170
+                            }
171
+
172
+                            if (!$addToWide && $this->shareeEnumerationInGroupOnly) {
173
+                                $addToWide = false;
174
+                                $userGroups = $this->groupManager->getUserGroupIds($this->userSession->getUser());
175
+                                foreach ($userGroups as $userGroup) {
176
+                                    if ($this->groupManager->isInGroup($contact['UID'], $userGroup)) {
177
+                                        $addToWide = true;
178
+                                        break;
179
+                                    }
180
+                                }
181
+                            }
182
+                            if ($addToWide && !$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) {
183
+                                $userResults['wide'][] = [
184
+                                    'label' => $displayName,
185
+                                    'uuid' => $contact['UID'] ?? $emailAddress,
186
+                                    'name' => $contact['FN'] ?? $displayName,
187
+                                    'value' => [
188
+                                        'shareType' => IShare::TYPE_USER,
189
+                                        'shareWith' => $cloud->getUser(),
190
+                                    ],
191
+                                    'shareWithDisplayNameUnique' => !empty($emailAddress) ? $emailAddress : $cloud->getUser()
192
+                                ];
193
+                                continue;
194
+                            }
195
+                        }
196
+                        continue;
197
+                    }
198
+
199
+                    if ($exactEmailMatch
200
+                        || (isset($contact['FN']) && strtolower($contact['FN']) === $lowerSearch)) {
201
+                        if ($exactEmailMatch) {
202
+                            $searchResult->markExactIdMatch($emailType);
203
+                        }
204
+                        $result['exact'][] = [
205
+                            'label' => $displayName,
206
+                            'uuid' => $contact['UID'] ?? $emailAddress,
207
+                            'name' => $contact['FN'] ?? $displayName,
208
+                            'type' => $emailAddressType ?? '',
209
+                            'value' => [
210
+                                'shareType' => IShare::TYPE_EMAIL,
211
+                                'shareWith' => $emailAddress,
212
+                            ],
213
+                        ];
214
+                    } else {
215
+                        $result['wide'][] = [
216
+                            'label' => $displayName,
217
+                            'uuid' => $contact['UID'] ?? $emailAddress,
218
+                            'name' => $contact['FN'] ?? $displayName,
219
+                            'type' => $emailAddressType ?? '',
220
+                            'value' => [
221
+                                'shareType' => IShare::TYPE_EMAIL,
222
+                                'shareWith' => $emailAddress,
223
+                            ],
224
+                        ];
225
+                    }
226
+                }
227
+            }
228
+        }
229
+
230
+        $reachedEnd = true;
231
+        if ($this->shareeEnumeration) {
232
+            $reachedEnd = (count($result['wide']) < $offset + $limit)
233
+                && (count($userResults['wide']) < $offset + $limit);
234
+
235
+            $result['wide'] = array_slice($result['wide'], $offset, $limit);
236
+            $userResults['wide'] = array_slice($userResults['wide'], $offset, $limit);
237
+        }
238
+
239
+        if (!$searchResult->hasExactIdMatch($emailType) && $this->emailValidator->isValid($search)) {
240
+            $result['exact'][] = [
241
+                'label' => $search,
242
+                'uuid' => $search,
243
+                'value' => [
244
+                    'shareType' => IShare::TYPE_EMAIL,
245
+                    'shareWith' => $search,
246
+                ],
247
+            ];
248
+        }
249
+
250
+        if (!empty($userResults['wide'])) {
251
+            $searchResult->addResultSet($userType, $userResults['wide'], []);
252
+        }
253
+        $searchResult->addResultSet($emailType, $result['wide'], $result['exact']);
254
+
255
+        return !$reachedEnd;
256
+    }
257
+
258
+    public function isCurrentUser(ICloudId $cloud): bool {
259
+        $currentUser = $this->userSession->getUser();
260
+        return $currentUser instanceof IUser && $currentUser->getUID() === $cloud->getUser();
261
+    }
262 262
 }
Please login to merge, or discard this patch.
core/Command/User/Add.php 1 patch
Indentation   +165 added lines, -165 removed lines patch added patch discarded remove patch
@@ -27,169 +27,169 @@
 block discarded – undo
27 27
 use Symfony\Component\Console\Question\Question;
28 28
 
29 29
 class Add extends Command {
30
-	public function __construct(
31
-		protected IUserManager $userManager,
32
-		protected IGroupManager $groupManager,
33
-		private IEmailValidator $emailValidator,
34
-		private IAppConfig $appConfig,
35
-		private NewUserMailHelper $mailHelper,
36
-		private IEventDispatcher $eventDispatcher,
37
-		private ISecureRandom $secureRandom,
38
-	) {
39
-		parent::__construct();
40
-	}
41
-
42
-	protected function configure(): void {
43
-		$this
44
-			->setName('user:add')
45
-			->setDescription('adds an account')
46
-			->addArgument(
47
-				'uid',
48
-				InputArgument::REQUIRED,
49
-				'Account ID used to login (must only contain a-z, A-Z, 0-9, -, _ and @)'
50
-			)
51
-			->addOption(
52
-				'password-from-env',
53
-				null,
54
-				InputOption::VALUE_NONE,
55
-				'read password from environment variable NC_PASS/OC_PASS'
56
-			)
57
-			->addOption(
58
-				'generate-password',
59
-				null,
60
-				InputOption::VALUE_NONE,
61
-				'Generate a secure password. A welcome email with a reset link will be sent to the user via an email if --email option and newUser.sendEmail config are set'
62
-			)
63
-			->addOption(
64
-				'display-name',
65
-				null,
66
-				InputOption::VALUE_OPTIONAL,
67
-				'Login used in the web UI (can contain any characters)'
68
-			)
69
-			->addOption(
70
-				'group',
71
-				'g',
72
-				InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
73
-				'groups the account should be added to (The group will be created if it does not exist)'
74
-			)
75
-			->addOption(
76
-				'email',
77
-				null,
78
-				InputOption::VALUE_REQUIRED,
79
-				'When set, users may register using the default email verification workflow'
80
-			);
81
-	}
82
-
83
-	protected function execute(InputInterface $input, OutputInterface $output): int {
84
-		$uid = $input->getArgument('uid');
85
-		if ($this->userManager->userExists($uid)) {
86
-			$output->writeln('<error>The account "' . $uid . '" already exists.</error>');
87
-			return 1;
88
-		}
89
-
90
-		$password = '';
91
-
92
-		// Setup password.
93
-		if ($input->getOption('password-from-env')) {
94
-			$password = getenv('NC_PASS') ?: getenv('OC_PASS');
95
-
96
-			if (!$password) {
97
-				$output->writeln('<error>--password-from-env given, but NC_PASS/OC_PASS is empty!</error>');
98
-				return 1;
99
-			}
100
-		} elseif ($input->getOption('generate-password')) {
101
-			$passwordEvent = new GenerateSecurePasswordEvent();
102
-			$this->eventDispatcher->dispatchTyped($passwordEvent);
103
-			$password = $passwordEvent->getPassword() ?? $this->secureRandom->generate(20);
104
-		} elseif ($input->isInteractive()) {
105
-			/** @var QuestionHelper $helper */
106
-			$helper = $this->getHelper('question');
107
-
108
-			$question = new Question('Enter password: ');
109
-			$question->setHidden(true);
110
-			$password = $helper->ask($input, $output, $question);
111
-
112
-			$question = new Question('Confirm password: ');
113
-			$question->setHidden(true);
114
-			$confirm = $helper->ask($input, $output, $question);
115
-
116
-			if ($password !== $confirm) {
117
-				$output->writeln('<error>Passwords did not match!</error>');
118
-				return 1;
119
-			}
120
-		} else {
121
-			$output->writeln('<error>Interactive input or --password-from-env or --generate-password is needed for setting a password!</error>');
122
-			return 1;
123
-		}
124
-
125
-		try {
126
-			$user = $this->userManager->createUser(
127
-				$input->getArgument('uid'),
128
-				$password,
129
-			);
130
-		} catch (\Exception $e) {
131
-			$output->writeln('<error>' . $e->getMessage() . '</error>');
132
-			return 1;
133
-		}
134
-
135
-		if ($user instanceof IUser) {
136
-			$output->writeln('<info>The account "' . $user->getUID() . '" was created successfully</info>');
137
-		} else {
138
-			$output->writeln('<error>An error occurred while creating the account</error>');
139
-			return 1;
140
-		}
141
-
142
-		if ($input->getOption('display-name')) {
143
-			$user->setDisplayName($input->getOption('display-name'));
144
-			$output->writeln('Display name set to "' . $user->getDisplayName() . '"');
145
-		}
146
-
147
-		$groups = $input->getOption('group');
148
-
149
-		if (!empty($groups)) {
150
-			// Make sure we init the Filesystem for the user, in case we need to
151
-			// init some group shares.
152
-			Filesystem::init($user->getUID(), '');
153
-		}
154
-
155
-		foreach ($groups as $groupName) {
156
-			$group = $this->groupManager->get($groupName);
157
-			if (!$group) {
158
-				$this->groupManager->createGroup($groupName);
159
-				$group = $this->groupManager->get($groupName);
160
-				if ($group instanceof IGroup) {
161
-					$output->writeln('Created group "' . $group->getGID() . '"');
162
-				}
163
-			}
164
-			if ($group instanceof IGroup) {
165
-				$group->addUser($user);
166
-				$output->writeln('Account "' . $user->getUID() . '" added to group "' . $group->getGID() . '"');
167
-			}
168
-		}
169
-
170
-		$email = $input->getOption('email');
171
-		if (!empty($email)) {
172
-			if (!$this->emailValidator->isValid($email)) {
173
-				$output->writeln(\sprintf(
174
-					'<error>The given email address "%s" is invalid. Email not set for the user.</error>',
175
-					$email,
176
-				));
177
-
178
-				return 1;
179
-			}
180
-
181
-			$user->setSystemEMailAddress($email);
182
-
183
-			if ($this->appConfig->getValueString('core', 'newUser.sendEmail', 'yes') === 'yes') {
184
-				try {
185
-					$this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true));
186
-					$output->writeln('Welcome email sent to ' . $email);
187
-				} catch (\Exception $e) {
188
-					$output->writeln('Unable to send the welcome email to ' . $email);
189
-				}
190
-			}
191
-		}
192
-
193
-		return 0;
194
-	}
30
+    public function __construct(
31
+        protected IUserManager $userManager,
32
+        protected IGroupManager $groupManager,
33
+        private IEmailValidator $emailValidator,
34
+        private IAppConfig $appConfig,
35
+        private NewUserMailHelper $mailHelper,
36
+        private IEventDispatcher $eventDispatcher,
37
+        private ISecureRandom $secureRandom,
38
+    ) {
39
+        parent::__construct();
40
+    }
41
+
42
+    protected function configure(): void {
43
+        $this
44
+            ->setName('user:add')
45
+            ->setDescription('adds an account')
46
+            ->addArgument(
47
+                'uid',
48
+                InputArgument::REQUIRED,
49
+                'Account ID used to login (must only contain a-z, A-Z, 0-9, -, _ and @)'
50
+            )
51
+            ->addOption(
52
+                'password-from-env',
53
+                null,
54
+                InputOption::VALUE_NONE,
55
+                'read password from environment variable NC_PASS/OC_PASS'
56
+            )
57
+            ->addOption(
58
+                'generate-password',
59
+                null,
60
+                InputOption::VALUE_NONE,
61
+                'Generate a secure password. A welcome email with a reset link will be sent to the user via an email if --email option and newUser.sendEmail config are set'
62
+            )
63
+            ->addOption(
64
+                'display-name',
65
+                null,
66
+                InputOption::VALUE_OPTIONAL,
67
+                'Login used in the web UI (can contain any characters)'
68
+            )
69
+            ->addOption(
70
+                'group',
71
+                'g',
72
+                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
73
+                'groups the account should be added to (The group will be created if it does not exist)'
74
+            )
75
+            ->addOption(
76
+                'email',
77
+                null,
78
+                InputOption::VALUE_REQUIRED,
79
+                'When set, users may register using the default email verification workflow'
80
+            );
81
+    }
82
+
83
+    protected function execute(InputInterface $input, OutputInterface $output): int {
84
+        $uid = $input->getArgument('uid');
85
+        if ($this->userManager->userExists($uid)) {
86
+            $output->writeln('<error>The account "' . $uid . '" already exists.</error>');
87
+            return 1;
88
+        }
89
+
90
+        $password = '';
91
+
92
+        // Setup password.
93
+        if ($input->getOption('password-from-env')) {
94
+            $password = getenv('NC_PASS') ?: getenv('OC_PASS');
95
+
96
+            if (!$password) {
97
+                $output->writeln('<error>--password-from-env given, but NC_PASS/OC_PASS is empty!</error>');
98
+                return 1;
99
+            }
100
+        } elseif ($input->getOption('generate-password')) {
101
+            $passwordEvent = new GenerateSecurePasswordEvent();
102
+            $this->eventDispatcher->dispatchTyped($passwordEvent);
103
+            $password = $passwordEvent->getPassword() ?? $this->secureRandom->generate(20);
104
+        } elseif ($input->isInteractive()) {
105
+            /** @var QuestionHelper $helper */
106
+            $helper = $this->getHelper('question');
107
+
108
+            $question = new Question('Enter password: ');
109
+            $question->setHidden(true);
110
+            $password = $helper->ask($input, $output, $question);
111
+
112
+            $question = new Question('Confirm password: ');
113
+            $question->setHidden(true);
114
+            $confirm = $helper->ask($input, $output, $question);
115
+
116
+            if ($password !== $confirm) {
117
+                $output->writeln('<error>Passwords did not match!</error>');
118
+                return 1;
119
+            }
120
+        } else {
121
+            $output->writeln('<error>Interactive input or --password-from-env or --generate-password is needed for setting a password!</error>');
122
+            return 1;
123
+        }
124
+
125
+        try {
126
+            $user = $this->userManager->createUser(
127
+                $input->getArgument('uid'),
128
+                $password,
129
+            );
130
+        } catch (\Exception $e) {
131
+            $output->writeln('<error>' . $e->getMessage() . '</error>');
132
+            return 1;
133
+        }
134
+
135
+        if ($user instanceof IUser) {
136
+            $output->writeln('<info>The account "' . $user->getUID() . '" was created successfully</info>');
137
+        } else {
138
+            $output->writeln('<error>An error occurred while creating the account</error>');
139
+            return 1;
140
+        }
141
+
142
+        if ($input->getOption('display-name')) {
143
+            $user->setDisplayName($input->getOption('display-name'));
144
+            $output->writeln('Display name set to "' . $user->getDisplayName() . '"');
145
+        }
146
+
147
+        $groups = $input->getOption('group');
148
+
149
+        if (!empty($groups)) {
150
+            // Make sure we init the Filesystem for the user, in case we need to
151
+            // init some group shares.
152
+            Filesystem::init($user->getUID(), '');
153
+        }
154
+
155
+        foreach ($groups as $groupName) {
156
+            $group = $this->groupManager->get($groupName);
157
+            if (!$group) {
158
+                $this->groupManager->createGroup($groupName);
159
+                $group = $this->groupManager->get($groupName);
160
+                if ($group instanceof IGroup) {
161
+                    $output->writeln('Created group "' . $group->getGID() . '"');
162
+                }
163
+            }
164
+            if ($group instanceof IGroup) {
165
+                $group->addUser($user);
166
+                $output->writeln('Account "' . $user->getUID() . '" added to group "' . $group->getGID() . '"');
167
+            }
168
+        }
169
+
170
+        $email = $input->getOption('email');
171
+        if (!empty($email)) {
172
+            if (!$this->emailValidator->isValid($email)) {
173
+                $output->writeln(\sprintf(
174
+                    '<error>The given email address "%s" is invalid. Email not set for the user.</error>',
175
+                    $email,
176
+                ));
177
+
178
+                return 1;
179
+            }
180
+
181
+            $user->setSystemEMailAddress($email);
182
+
183
+            if ($this->appConfig->getValueString('core', 'newUser.sendEmail', 'yes') === 'yes') {
184
+                try {
185
+                    $this->mailHelper->sendMail($user, $this->mailHelper->generateTemplate($user, true));
186
+                    $output->writeln('Welcome email sent to ' . $email);
187
+                } catch (\Exception $e) {
188
+                    $output->writeln('Unable to send the welcome email to ' . $email);
189
+                }
190
+            }
191
+        }
192
+
193
+        return 0;
194
+    }
195 195
 }
Please login to merge, or discard this patch.
apps/sharebymail/lib/ShareByMailProvider.php 1 patch
Indentation   +1194 added lines, -1194 removed lines patch added patch discarded remove patch
@@ -46,1200 +46,1200 @@
 block discarded – undo
46 46
  * @package OCA\ShareByMail
47 47
  */
48 48
 class ShareByMailProvider extends DefaultShareProvider implements IShareProviderWithNotification {
49
-	/**
50
-	 * Return the identifier of this provider.
51
-	 *
52
-	 * @return string Containing only [a-zA-Z0-9]
53
-	 */
54
-	public function identifier(): string {
55
-		return 'ocMailShare';
56
-	}
57
-
58
-	public function __construct(
59
-		private IConfig $config,
60
-		private IDBConnection $dbConnection,
61
-		private ISecureRandom $secureRandom,
62
-		private IUserManager $userManager,
63
-		private IRootFolder $rootFolder,
64
-		private IL10N $l,
65
-		private LoggerInterface $logger,
66
-		private IMailer $mailer,
67
-		private IURLGenerator $urlGenerator,
68
-		private IManager $activityManager,
69
-		private SettingsManager $settingsManager,
70
-		private Defaults $defaults,
71
-		private IHasher $hasher,
72
-		private IEventDispatcher $eventDispatcher,
73
-		private IShareManager $shareManager,
74
-		private IEmailValidator $emailValidator,
75
-	) {
76
-	}
77
-
78
-	/**
79
-	 * Share a path
80
-	 *
81
-	 * @throws ShareNotFound
82
-	 * @throws \Exception
83
-	 */
84
-	public function create(IShare $share): IShare {
85
-		$shareWith = $share->getSharedWith();
86
-		// Check if file is not already shared with the given email,
87
-		// if we have an email at all.
88
-		$alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_EMAIL, $share->getNode(), 1, 0);
89
-		if ($shareWith !== '' && !empty($alreadyShared)) {
90
-			$message = 'Sharing %1$s failed, because this item is already shared with the account %2$s';
91
-			$message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with the account %2$s', [$share->getNode()->getName(), $shareWith]);
92
-			$this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
93
-			throw new \Exception($message_t);
94
-		}
95
-
96
-		// if the admin enforces a password for all mail shares we create a
97
-		// random password and send it to the recipient
98
-		$password = $share->getPassword() ?: '';
99
-		$passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword();
100
-		if ($passwordEnforced && empty($password)) {
101
-			$password = $this->autoGeneratePassword($share);
102
-		}
103
-
104
-		if (!empty($password)) {
105
-			$share->setPassword($this->hasher->hash($password));
106
-		}
107
-
108
-		$shareId = $this->createMailShare($share);
109
-
110
-		$this->createShareActivity($share);
111
-		$data = $this->getRawShare($shareId);
112
-
113
-		// Temporary set the clear password again to send it by mail
114
-		// This need to be done after the share was created in the database
115
-		// as the password is hashed in between.
116
-		if (!empty($password)) {
117
-			$data['password'] = $password;
118
-		}
119
-
120
-		return $this->createShareObject($data);
121
-	}
122
-
123
-	/**
124
-	 * auto generate password in case of password enforcement on mail shares
125
-	 *
126
-	 * @throws \Exception
127
-	 */
128
-	protected function autoGeneratePassword(IShare $share): string {
129
-		$initiatorUser = $this->userManager->get($share->getSharedBy());
130
-		$initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
131
-		$allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
132
-
133
-		if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
134
-			throw new \Exception(
135
-				$this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.')
136
-			);
137
-		}
138
-
139
-		$passwordEvent = new GenerateSecurePasswordEvent(PasswordContext::SHARING);
140
-		$this->eventDispatcher->dispatchTyped($passwordEvent);
141
-
142
-		$password = $passwordEvent->getPassword();
143
-		if ($password === null) {
144
-			$password = $this->secureRandom->generate(8, ISecureRandom::CHAR_HUMAN_READABLE);
145
-		}
146
-
147
-		return $password;
148
-	}
149
-
150
-	/**
151
-	 * create activity if a file/folder was shared by mail
152
-	 */
153
-	protected function createShareActivity(IShare $share, string $type = 'share'): void {
154
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
155
-
156
-		$this->publishActivity(
157
-			$type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_SELF : Activity::SUBJECT_UNSHARED_EMAIL_SELF,
158
-			[$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
159
-			$share->getSharedBy(),
160
-			$share->getNode()->getId(),
161
-			(string)$userFolder->getRelativePath($share->getNode()->getPath())
162
-		);
163
-
164
-		if ($share->getShareOwner() !== $share->getSharedBy()) {
165
-			$ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
166
-			$fileId = $share->getNode()->getId();
167
-			$nodes = $ownerFolder->getById($fileId);
168
-			$ownerPath = $nodes[0]->getPath();
169
-			$this->publishActivity(
170
-				$type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_BY : Activity::SUBJECT_UNSHARED_EMAIL_BY,
171
-				[$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
172
-				$share->getShareOwner(),
173
-				$fileId,
174
-				(string)$ownerFolder->getRelativePath($ownerPath)
175
-			);
176
-		}
177
-	}
178
-
179
-	/**
180
-	 * create activity if a file/folder was shared by mail
181
-	 */
182
-	protected function createPasswordSendActivity(IShare $share, string $sharedWith, bool $sendToSelf): void {
183
-		$userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
184
-
185
-		if ($sendToSelf) {
186
-			$this->publishActivity(
187
-				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
188
-				[$userFolder->getRelativePath($share->getNode()->getPath())],
189
-				$share->getSharedBy(),
190
-				$share->getNode()->getId(),
191
-				(string)$userFolder->getRelativePath($share->getNode()->getPath())
192
-			);
193
-		} else {
194
-			$this->publishActivity(
195
-				Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
196
-				[$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
197
-				$share->getSharedBy(),
198
-				$share->getNode()->getId(),
199
-				(string)$userFolder->getRelativePath($share->getNode()->getPath())
200
-			);
201
-		}
202
-	}
203
-
204
-
205
-	/**
206
-	 * publish activity if a file/folder was shared by mail
207
-	 */
208
-	protected function publishActivity(string $subject, array $parameters, string $affectedUser, int $fileId, string $filePath): void {
209
-		$event = $this->activityManager->generateEvent();
210
-		$event->setApp('sharebymail')
211
-			->setType('shared')
212
-			->setSubject($subject, $parameters)
213
-			->setAffectedUser($affectedUser)
214
-			->setObject('files', $fileId, $filePath);
215
-		$this->activityManager->publish($event);
216
-	}
217
-
218
-	/**
219
-	 * @throws \Exception
220
-	 */
221
-	protected function createMailShare(IShare $share): int {
222
-		$share->setToken($this->generateToken());
223
-		return $this->addShareToDB(
224
-			$share->getNodeId(),
225
-			$share->getNodeType(),
226
-			$share->getSharedWith(),
227
-			$share->getSharedBy(),
228
-			$share->getShareOwner(),
229
-			$share->getPermissions(),
230
-			$share->getToken(),
231
-			$share->getPassword(),
232
-			$share->getPasswordExpirationTime(),
233
-			$share->getSendPasswordByTalk(),
234
-			$share->getHideDownload(),
235
-			$share->getLabel(),
236
-			$share->getExpirationDate(),
237
-			$share->getNote(),
238
-			$share->getAttributes(),
239
-			$share->getMailSend(),
240
-		);
241
-	}
242
-
243
-	/**
244
-	 * @inheritDoc
245
-	 */
246
-	public function sendMailNotification(IShare $share): bool {
247
-		$shareId = $share->getId();
248
-
249
-		$emails = $this->getSharedWithEmails($share);
250
-		$validEmails = array_filter($emails, function (string $email) {
251
-			return $this->emailValidator->isValid($email);
252
-		});
253
-
254
-		if (count($validEmails) === 0) {
255
-			$this->removeShareFromTable((int)$shareId);
256
-			$e = new HintException('Failed to send share by mail. Could not find a valid email address: ' . join(', ', $emails),
257
-				$this->l->t('Failed to send share by email. Got an invalid email address'));
258
-			$this->logger->error('Failed to send share by mail. Could not find a valid email address ' . join(', ', $emails), [
259
-				'app' => 'sharebymail',
260
-				'exception' => $e,
261
-			]);
262
-		}
263
-
264
-		try {
265
-			$this->sendEmail($share, $validEmails);
266
-
267
-			// If we have a password set, we send it to the recipient
268
-			if ($share->getPassword() !== null) {
269
-				// If share-by-talk password is enabled, we do not send the notification
270
-				// to the recipient. They will have to request it to the owner after opening the link.
271
-				// Secondly, if the password expiration is disabled, we send the notification to the recipient
272
-				// Lastly, if the mail to recipient failed, we send the password to the owner as a fallback.
273
-				// If a password expires, the recipient will still be able to request a new one via talk.
274
-				$passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false);
275
-				$passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword();
276
-				if ($passwordExpire === false || $share->getSendPasswordByTalk()) {
277
-					$send = $this->sendPassword($share, $share->getPassword(), $validEmails);
278
-					if ($passwordEnforced && $send === false) {
279
-						$this->sendPasswordToOwner($share, $share->getPassword());
280
-					}
281
-				}
282
-			}
283
-
284
-			return true;
285
-		} catch (HintException $hintException) {
286
-			$this->logger->error('Failed to send share by mail.', [
287
-				'app' => 'sharebymail',
288
-				'exception' => $hintException,
289
-			]);
290
-			$this->removeShareFromTable((int)$shareId);
291
-			throw $hintException;
292
-		} catch (\Exception $e) {
293
-			$this->logger->error('Failed to send share by mail.', [
294
-				'app' => 'sharebymail',
295
-				'exception' => $e,
296
-			]);
297
-			$this->removeShareFromTable((int)$shareId);
298
-			throw new HintException(
299
-				'Failed to send share by mail',
300
-				$this->l->t('Failed to send share by email'),
301
-				0,
302
-				$e,
303
-			);
304
-		}
305
-		return false;
306
-	}
307
-
308
-	/**
309
-	 * @param IShare $share The share to send the email for
310
-	 * @param array $emails The email addresses to send the email to
311
-	 */
312
-	protected function sendEmail(IShare $share, array $emails): void {
313
-		$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [
314
-			'token' => $share->getToken()
315
-		]);
316
-
317
-		$expiration = $share->getExpirationDate();
318
-		$filename = $share->getNode()->getName();
319
-		$initiator = $share->getSharedBy();
320
-		$note = $share->getNote();
321
-		$shareWith = $share->getSharedWith();
322
-
323
-		$initiatorUser = $this->userManager->get($initiator);
324
-		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
325
-		$message = $this->mailer->createMessage();
326
-
327
-		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [
328
-			'filename' => $filename,
329
-			'link' => $link,
330
-			'initiator' => $initiatorDisplayName,
331
-			'expiration' => $expiration,
332
-			'shareWith' => $shareWith,
333
-			'note' => $note
334
-		]);
335
-
336
-		$emailTemplate->setSubject($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]));
337
-		$emailTemplate->addHeader();
338
-		$emailTemplate->addHeading($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false);
339
-
340
-		if ($note !== '') {
341
-			$emailTemplate->addBodyListItem(
342
-				htmlspecialchars($note),
343
-				$this->l->t('Note:'),
344
-				$this->getAbsoluteImagePath('caldav/description.png'),
345
-				$note
346
-			);
347
-		}
348
-
349
-		if ($expiration !== null) {
350
-			$dateString = (string)$this->l->l('date', $expiration, ['width' => 'medium']);
351
-			$emailTemplate->addBodyListItem(
352
-				$this->l->t('This share is valid until %s at midnight', [$dateString]),
353
-				$this->l->t('Expiration:'),
354
-				$this->getAbsoluteImagePath('caldav/time.png'),
355
-			);
356
-		}
357
-
358
-		$emailTemplate->addBodyButton(
359
-			$this->l->t('Open %s', [$filename]),
360
-			$link
361
-		);
362
-
363
-		// If multiple recipients are given, we send the mail to all of them
364
-		if (count($emails) > 1) {
365
-			// We do not want to expose the email addresses of the other recipients
366
-			$message->setBcc($emails);
367
-		} else {
368
-			$message->setTo($emails);
369
-		}
370
-
371
-		// The "From" contains the sharers name
372
-		$instanceName = $this->defaults->getName();
373
-		$senderName = $instanceName;
374
-		if ($this->settingsManager->replyToInitiator()) {
375
-			$senderName = $this->l->t(
376
-				'%1$s via %2$s',
377
-				[
378
-					$initiatorDisplayName,
379
-					$instanceName
380
-				]
381
-			);
382
-		}
383
-		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
384
-
385
-		// The "Reply-To" is set to the sharer if an mail address is configured
386
-		// also the default footer contains a "Do not reply" which needs to be adjusted.
387
-		if ($initiatorUser && $this->settingsManager->replyToInitiator()) {
388
-			$initiatorEmail = $initiatorUser->getEMailAddress();
389
-			if ($initiatorEmail !== null) {
390
-				$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
391
-				$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
392
-			} else {
393
-				$emailTemplate->addFooter();
394
-			}
395
-		} else {
396
-			$emailTemplate->addFooter();
397
-		}
398
-
399
-		$message->useTemplate($emailTemplate);
400
-		$failedRecipients = $this->mailer->send($message);
401
-		if (!empty($failedRecipients)) {
402
-			$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
403
-			return;
404
-		}
405
-	}
406
-
407
-	/**
408
-	 * Send password to recipient of a mail share
409
-	 * Will return false if
410
-	 *  1. the password is empty
411
-	 *  2. the setting to send the password by mail is disabled
412
-	 *  3. the share is set to send the password by talk
413
-	 *
414
-	 * @param IShare $share
415
-	 * @param string $password
416
-	 * @param array $emails
417
-	 * @return bool
418
-	 */
419
-	protected function sendPassword(IShare $share, string $password, array $emails): bool {
420
-		$filename = $share->getNode()->getName();
421
-		$initiator = $share->getSharedBy();
422
-		$shareWith = $share->getSharedWith();
423
-
424
-		if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) {
425
-			return false;
426
-		}
427
-
428
-		$initiatorUser = $this->userManager->get($initiator);
429
-		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
430
-		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
431
-
432
-		$plainBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
433
-		$htmlBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
434
-
435
-		$message = $this->mailer->createMessage();
436
-
437
-		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [
438
-			'filename' => $filename,
439
-			'password' => $password,
440
-			'initiator' => $initiatorDisplayName,
441
-			'initiatorEmail' => $initiatorEmailAddress,
442
-			'shareWith' => $shareWith,
443
-		]);
444
-
445
-		$emailTemplate->setSubject($this->l->t('Password to access %1$s shared to you by %2$s', [$filename, $initiatorDisplayName]));
446
-		$emailTemplate->addHeader();
447
-		$emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false);
448
-		$emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
449
-		$emailTemplate->addBodyText($this->l->t('It is protected with the following password:'));
450
-		$emailTemplate->addBodyText($password);
451
-
452
-		if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
453
-			$expirationTime = new \DateTime();
454
-			$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
455
-			$expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
456
-			$emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
457
-		}
458
-
459
-		// If multiple recipients are given, we send the mail to all of them
460
-		if (count($emails) > 1) {
461
-			// We do not want to expose the email addresses of the other recipients
462
-			$message->setBcc($emails);
463
-		} else {
464
-			$message->setTo($emails);
465
-		}
466
-
467
-		// The "From" contains the sharers name
468
-		$instanceName = $this->defaults->getName();
469
-		$senderName = $instanceName;
470
-		if ($this->settingsManager->replyToInitiator()) {
471
-			$senderName = $this->l->t(
472
-				'%1$s via %2$s',
473
-				[
474
-					$initiatorDisplayName,
475
-					$instanceName
476
-				]
477
-			);
478
-		}
479
-		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
480
-
481
-		// The "Reply-To" is set to the sharer if an mail address is configured
482
-		// also the default footer contains a "Do not reply" which needs to be adjusted.
483
-		if ($initiatorUser && $this->settingsManager->replyToInitiator()) {
484
-			$initiatorEmail = $initiatorUser->getEMailAddress();
485
-			if ($initiatorEmail !== null) {
486
-				$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
487
-				$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
488
-			} else {
489
-				$emailTemplate->addFooter();
490
-			}
491
-		} else {
492
-			$emailTemplate->addFooter();
493
-		}
494
-
495
-		$message->useTemplate($emailTemplate);
496
-		$failedRecipients = $this->mailer->send($message);
497
-		if (!empty($failedRecipients)) {
498
-			$this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients));
499
-			return false;
500
-		}
501
-
502
-		$this->createPasswordSendActivity($share, $shareWith, false);
503
-		return true;
504
-	}
505
-
506
-	protected function sendNote(IShare $share): void {
507
-		$recipient = $share->getSharedWith();
508
-
509
-
510
-		$filename = $share->getNode()->getName();
511
-		$initiator = $share->getSharedBy();
512
-		$note = $share->getNote();
513
-
514
-		$initiatorUser = $this->userManager->get($initiator);
515
-		$initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
516
-		$initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
517
-
518
-		$plainHeading = $this->l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]);
519
-		$htmlHeading = $this->l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]);
520
-
521
-		$message = $this->mailer->createMessage();
522
-
523
-		$emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote');
524
-
525
-		$emailTemplate->setSubject($this->l->t('%s added a note to a file shared with you', [$initiatorDisplayName]));
526
-		$emailTemplate->addHeader();
527
-		$emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading);
528
-		$emailTemplate->addBodyText(htmlspecialchars($note), $note);
529
-
530
-		$link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
531
-			['token' => $share->getToken()]);
532
-		$emailTemplate->addBodyButton(
533
-			$this->l->t('Open %s', [$filename]),
534
-			$link
535
-		);
536
-
537
-		// The "From" contains the sharers name
538
-		$instanceName = $this->defaults->getName();
539
-		$senderName = $instanceName;
540
-		if ($this->settingsManager->replyToInitiator()) {
541
-			$senderName = $this->l->t(
542
-				'%1$s via %2$s',
543
-				[
544
-					$initiatorDisplayName,
545
-					$instanceName
546
-				]
547
-			);
548
-		}
549
-		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
550
-		if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
551
-			$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
552
-			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
553
-		} else {
554
-			$emailTemplate->addFooter();
555
-		}
556
-
557
-		$message->setTo([$recipient]);
558
-		$message->useTemplate($emailTemplate);
559
-		$this->mailer->send($message);
560
-	}
561
-
562
-	/**
563
-	 * send auto generated password to the owner. This happens if the admin enforces
564
-	 * a password for mail shares and forbid to send the password by mail to the recipient
565
-	 *
566
-	 * @throws \Exception
567
-	 */
568
-	protected function sendPasswordToOwner(IShare $share, string $password): bool {
569
-		$filename = $share->getNode()->getName();
570
-		$initiator = $this->userManager->get($share->getSharedBy());
571
-		$initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
572
-		$initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
573
-		$shareWith = implode(', ', $this->getSharedWithEmails($share));
574
-
575
-		if ($initiatorEMailAddress === null) {
576
-			throw new \Exception(
577
-				$this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.')
578
-			);
579
-		}
580
-
581
-		$bodyPart = $this->l->t('You just shared %1$s with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.', [$filename, $shareWith, $this->defaults->getName()]);
582
-
583
-		$message = $this->mailer->createMessage();
584
-		$emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [
585
-			'filename' => $filename,
586
-			'password' => $password,
587
-			'initiator' => $initiatorDisplayName,
588
-			'initiatorEmail' => $initiatorEMailAddress,
589
-			'shareWith' => $shareWith,
590
-		]);
591
-
592
-		$emailTemplate->setSubject($this->l->t('Password to access %1$s shared by you with %2$s', [$filename, $shareWith]));
593
-		$emailTemplate->addHeader();
594
-		$emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false);
595
-		$emailTemplate->addBodyText($bodyPart);
596
-		$emailTemplate->addBodyText($this->l->t('This is the password:'));
597
-		$emailTemplate->addBodyText($password);
598
-
599
-		if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
600
-			$expirationTime = new \DateTime();
601
-			$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
602
-			$expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
603
-			$emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
604
-		}
605
-
606
-		$emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
607
-
608
-		$emailTemplate->addFooter();
609
-
610
-		$instanceName = $this->defaults->getName();
611
-		$senderName = $this->l->t(
612
-			'%1$s via %2$s',
613
-			[
614
-				$initiatorDisplayName,
615
-				$instanceName
616
-			]
617
-		);
618
-		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
619
-		$message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
620
-		$message->useTemplate($emailTemplate);
621
-		$this->mailer->send($message);
622
-
623
-		$this->createPasswordSendActivity($share, $shareWith, true);
624
-
625
-		return true;
626
-	}
627
-
628
-	private function getAbsoluteImagePath(string $path):string {
629
-		return $this->urlGenerator->getAbsoluteURL(
630
-			$this->urlGenerator->imagePath('core', $path)
631
-		);
632
-	}
633
-
634
-	/**
635
-	 * generate share token
636
-	 */
637
-	protected function generateToken(int $size = 15): string {
638
-		$token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE);
639
-		return $token;
640
-	}
641
-
642
-	public function getChildren(IShare $parent): array {
643
-		$children = [];
644
-
645
-		$qb = $this->dbConnection->getQueryBuilder();
646
-		$qb->select('*')
647
-			->from('share')
648
-			->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
649
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
650
-			->orderBy('id');
651
-
652
-		$cursor = $qb->executeQuery();
653
-		while ($data = $cursor->fetch()) {
654
-			$children[] = $this->createShareObject($data);
655
-		}
656
-		$cursor->closeCursor();
657
-
658
-		return $children;
659
-	}
660
-
661
-	/**
662
-	 * Add share to the database and return the ID
663
-	 */
664
-	protected function addShareToDB(
665
-		?int $itemSource,
666
-		?string $itemType,
667
-		?string $shareWith,
668
-		?string $sharedBy,
669
-		?string $uidOwner,
670
-		?int $permissions,
671
-		?string $token,
672
-		?string $password,
673
-		?\DateTimeInterface $passwordExpirationTime,
674
-		?bool $sendPasswordByTalk,
675
-		?bool $hideDownload,
676
-		?string $label,
677
-		?\DateTimeInterface $expirationTime,
678
-		?string $note = '',
679
-		?IAttributes $attributes = null,
680
-		?bool $mailSend = true,
681
-	): int {
682
-		$qb = $this->dbConnection->getQueryBuilder();
683
-		$qb->insert('share')
684
-			->setValue('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
685
-			->setValue('item_type', $qb->createNamedParameter($itemType))
686
-			->setValue('item_source', $qb->createNamedParameter($itemSource))
687
-			->setValue('file_source', $qb->createNamedParameter($itemSource))
688
-			->setValue('share_with', $qb->createNamedParameter($shareWith))
689
-			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
690
-			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
691
-			->setValue('permissions', $qb->createNamedParameter($permissions))
692
-			->setValue('token', $qb->createNamedParameter($token))
693
-			->setValue('password', $qb->createNamedParameter($password))
694
-			->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE))
695
-			->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
696
-			->setValue('stime', $qb->createNamedParameter(time()))
697
-			->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT))
698
-			->setValue('label', $qb->createNamedParameter($label))
699
-			->setValue('note', $qb->createNamedParameter($note))
700
-			->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT));
701
-
702
-		// set share attributes
703
-		$shareAttributes = $this->formatShareAttributes($attributes);
704
-
705
-		$qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
706
-		if ($expirationTime !== null) {
707
-			$qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE));
708
-		}
709
-
710
-		$qb->executeStatement();
711
-		return $qb->getLastInsertId();
712
-	}
713
-
714
-	/**
715
-	 * Update a share
716
-	 */
717
-	public function update(IShare $share, ?string $plainTextPassword = null): IShare {
718
-		$originalShare = $this->getShareById($share->getId());
719
-
720
-		// a real password was given
721
-		$validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
722
-
723
-		if ($validPassword && ($originalShare->getPassword() !== $share->getPassword()
724
-								|| ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
725
-			$emails = $this->getSharedWithEmails($share);
726
-			$validEmails = array_filter($emails, function ($email) {
727
-				return $this->emailValidator->isValid($email);
728
-			});
729
-			$this->sendPassword($share, $plainTextPassword, $validEmails);
730
-		}
731
-
732
-		$shareAttributes = $this->formatShareAttributes($share->getAttributes());
733
-
734
-		/*
49
+    /**
50
+     * Return the identifier of this provider.
51
+     *
52
+     * @return string Containing only [a-zA-Z0-9]
53
+     */
54
+    public function identifier(): string {
55
+        return 'ocMailShare';
56
+    }
57
+
58
+    public function __construct(
59
+        private IConfig $config,
60
+        private IDBConnection $dbConnection,
61
+        private ISecureRandom $secureRandom,
62
+        private IUserManager $userManager,
63
+        private IRootFolder $rootFolder,
64
+        private IL10N $l,
65
+        private LoggerInterface $logger,
66
+        private IMailer $mailer,
67
+        private IURLGenerator $urlGenerator,
68
+        private IManager $activityManager,
69
+        private SettingsManager $settingsManager,
70
+        private Defaults $defaults,
71
+        private IHasher $hasher,
72
+        private IEventDispatcher $eventDispatcher,
73
+        private IShareManager $shareManager,
74
+        private IEmailValidator $emailValidator,
75
+    ) {
76
+    }
77
+
78
+    /**
79
+     * Share a path
80
+     *
81
+     * @throws ShareNotFound
82
+     * @throws \Exception
83
+     */
84
+    public function create(IShare $share): IShare {
85
+        $shareWith = $share->getSharedWith();
86
+        // Check if file is not already shared with the given email,
87
+        // if we have an email at all.
88
+        $alreadyShared = $this->getSharedWith($shareWith, IShare::TYPE_EMAIL, $share->getNode(), 1, 0);
89
+        if ($shareWith !== '' && !empty($alreadyShared)) {
90
+            $message = 'Sharing %1$s failed, because this item is already shared with the account %2$s';
91
+            $message_t = $this->l->t('Sharing %1$s failed, because this item is already shared with the account %2$s', [$share->getNode()->getName(), $shareWith]);
92
+            $this->logger->debug(sprintf($message, $share->getNode()->getName(), $shareWith), ['app' => 'Federated File Sharing']);
93
+            throw new \Exception($message_t);
94
+        }
95
+
96
+        // if the admin enforces a password for all mail shares we create a
97
+        // random password and send it to the recipient
98
+        $password = $share->getPassword() ?: '';
99
+        $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword();
100
+        if ($passwordEnforced && empty($password)) {
101
+            $password = $this->autoGeneratePassword($share);
102
+        }
103
+
104
+        if (!empty($password)) {
105
+            $share->setPassword($this->hasher->hash($password));
106
+        }
107
+
108
+        $shareId = $this->createMailShare($share);
109
+
110
+        $this->createShareActivity($share);
111
+        $data = $this->getRawShare($shareId);
112
+
113
+        // Temporary set the clear password again to send it by mail
114
+        // This need to be done after the share was created in the database
115
+        // as the password is hashed in between.
116
+        if (!empty($password)) {
117
+            $data['password'] = $password;
118
+        }
119
+
120
+        return $this->createShareObject($data);
121
+    }
122
+
123
+    /**
124
+     * auto generate password in case of password enforcement on mail shares
125
+     *
126
+     * @throws \Exception
127
+     */
128
+    protected function autoGeneratePassword(IShare $share): string {
129
+        $initiatorUser = $this->userManager->get($share->getSharedBy());
130
+        $initiatorEMailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
131
+        $allowPasswordByMail = $this->settingsManager->sendPasswordByMail();
132
+
133
+        if ($initiatorEMailAddress === null && !$allowPasswordByMail) {
134
+            throw new \Exception(
135
+                $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.')
136
+            );
137
+        }
138
+
139
+        $passwordEvent = new GenerateSecurePasswordEvent(PasswordContext::SHARING);
140
+        $this->eventDispatcher->dispatchTyped($passwordEvent);
141
+
142
+        $password = $passwordEvent->getPassword();
143
+        if ($password === null) {
144
+            $password = $this->secureRandom->generate(8, ISecureRandom::CHAR_HUMAN_READABLE);
145
+        }
146
+
147
+        return $password;
148
+    }
149
+
150
+    /**
151
+     * create activity if a file/folder was shared by mail
152
+     */
153
+    protected function createShareActivity(IShare $share, string $type = 'share'): void {
154
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
155
+
156
+        $this->publishActivity(
157
+            $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_SELF : Activity::SUBJECT_UNSHARED_EMAIL_SELF,
158
+            [$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
159
+            $share->getSharedBy(),
160
+            $share->getNode()->getId(),
161
+            (string)$userFolder->getRelativePath($share->getNode()->getPath())
162
+        );
163
+
164
+        if ($share->getShareOwner() !== $share->getSharedBy()) {
165
+            $ownerFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
166
+            $fileId = $share->getNode()->getId();
167
+            $nodes = $ownerFolder->getById($fileId);
168
+            $ownerPath = $nodes[0]->getPath();
169
+            $this->publishActivity(
170
+                $type === 'share' ? Activity::SUBJECT_SHARED_EMAIL_BY : Activity::SUBJECT_UNSHARED_EMAIL_BY,
171
+                [$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
172
+                $share->getShareOwner(),
173
+                $fileId,
174
+                (string)$ownerFolder->getRelativePath($ownerPath)
175
+            );
176
+        }
177
+    }
178
+
179
+    /**
180
+     * create activity if a file/folder was shared by mail
181
+     */
182
+    protected function createPasswordSendActivity(IShare $share, string $sharedWith, bool $sendToSelf): void {
183
+        $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy());
184
+
185
+        if ($sendToSelf) {
186
+            $this->publishActivity(
187
+                Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND_SELF,
188
+                [$userFolder->getRelativePath($share->getNode()->getPath())],
189
+                $share->getSharedBy(),
190
+                $share->getNode()->getId(),
191
+                (string)$userFolder->getRelativePath($share->getNode()->getPath())
192
+            );
193
+        } else {
194
+            $this->publishActivity(
195
+                Activity::SUBJECT_SHARED_EMAIL_PASSWORD_SEND,
196
+                [$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
197
+                $share->getSharedBy(),
198
+                $share->getNode()->getId(),
199
+                (string)$userFolder->getRelativePath($share->getNode()->getPath())
200
+            );
201
+        }
202
+    }
203
+
204
+
205
+    /**
206
+     * publish activity if a file/folder was shared by mail
207
+     */
208
+    protected function publishActivity(string $subject, array $parameters, string $affectedUser, int $fileId, string $filePath): void {
209
+        $event = $this->activityManager->generateEvent();
210
+        $event->setApp('sharebymail')
211
+            ->setType('shared')
212
+            ->setSubject($subject, $parameters)
213
+            ->setAffectedUser($affectedUser)
214
+            ->setObject('files', $fileId, $filePath);
215
+        $this->activityManager->publish($event);
216
+    }
217
+
218
+    /**
219
+     * @throws \Exception
220
+     */
221
+    protected function createMailShare(IShare $share): int {
222
+        $share->setToken($this->generateToken());
223
+        return $this->addShareToDB(
224
+            $share->getNodeId(),
225
+            $share->getNodeType(),
226
+            $share->getSharedWith(),
227
+            $share->getSharedBy(),
228
+            $share->getShareOwner(),
229
+            $share->getPermissions(),
230
+            $share->getToken(),
231
+            $share->getPassword(),
232
+            $share->getPasswordExpirationTime(),
233
+            $share->getSendPasswordByTalk(),
234
+            $share->getHideDownload(),
235
+            $share->getLabel(),
236
+            $share->getExpirationDate(),
237
+            $share->getNote(),
238
+            $share->getAttributes(),
239
+            $share->getMailSend(),
240
+        );
241
+    }
242
+
243
+    /**
244
+     * @inheritDoc
245
+     */
246
+    public function sendMailNotification(IShare $share): bool {
247
+        $shareId = $share->getId();
248
+
249
+        $emails = $this->getSharedWithEmails($share);
250
+        $validEmails = array_filter($emails, function (string $email) {
251
+            return $this->emailValidator->isValid($email);
252
+        });
253
+
254
+        if (count($validEmails) === 0) {
255
+            $this->removeShareFromTable((int)$shareId);
256
+            $e = new HintException('Failed to send share by mail. Could not find a valid email address: ' . join(', ', $emails),
257
+                $this->l->t('Failed to send share by email. Got an invalid email address'));
258
+            $this->logger->error('Failed to send share by mail. Could not find a valid email address ' . join(', ', $emails), [
259
+                'app' => 'sharebymail',
260
+                'exception' => $e,
261
+            ]);
262
+        }
263
+
264
+        try {
265
+            $this->sendEmail($share, $validEmails);
266
+
267
+            // If we have a password set, we send it to the recipient
268
+            if ($share->getPassword() !== null) {
269
+                // If share-by-talk password is enabled, we do not send the notification
270
+                // to the recipient. They will have to request it to the owner after opening the link.
271
+                // Secondly, if the password expiration is disabled, we send the notification to the recipient
272
+                // Lastly, if the mail to recipient failed, we send the password to the owner as a fallback.
273
+                // If a password expires, the recipient will still be able to request a new one via talk.
274
+                $passwordExpire = $this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false);
275
+                $passwordEnforced = $this->shareManager->shareApiLinkEnforcePassword();
276
+                if ($passwordExpire === false || $share->getSendPasswordByTalk()) {
277
+                    $send = $this->sendPassword($share, $share->getPassword(), $validEmails);
278
+                    if ($passwordEnforced && $send === false) {
279
+                        $this->sendPasswordToOwner($share, $share->getPassword());
280
+                    }
281
+                }
282
+            }
283
+
284
+            return true;
285
+        } catch (HintException $hintException) {
286
+            $this->logger->error('Failed to send share by mail.', [
287
+                'app' => 'sharebymail',
288
+                'exception' => $hintException,
289
+            ]);
290
+            $this->removeShareFromTable((int)$shareId);
291
+            throw $hintException;
292
+        } catch (\Exception $e) {
293
+            $this->logger->error('Failed to send share by mail.', [
294
+                'app' => 'sharebymail',
295
+                'exception' => $e,
296
+            ]);
297
+            $this->removeShareFromTable((int)$shareId);
298
+            throw new HintException(
299
+                'Failed to send share by mail',
300
+                $this->l->t('Failed to send share by email'),
301
+                0,
302
+                $e,
303
+            );
304
+        }
305
+        return false;
306
+    }
307
+
308
+    /**
309
+     * @param IShare $share The share to send the email for
310
+     * @param array $emails The email addresses to send the email to
311
+     */
312
+    protected function sendEmail(IShare $share, array $emails): void {
313
+        $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', [
314
+            'token' => $share->getToken()
315
+        ]);
316
+
317
+        $expiration = $share->getExpirationDate();
318
+        $filename = $share->getNode()->getName();
319
+        $initiator = $share->getSharedBy();
320
+        $note = $share->getNote();
321
+        $shareWith = $share->getSharedWith();
322
+
323
+        $initiatorUser = $this->userManager->get($initiator);
324
+        $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
325
+        $message = $this->mailer->createMessage();
326
+
327
+        $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientNotification', [
328
+            'filename' => $filename,
329
+            'link' => $link,
330
+            'initiator' => $initiatorDisplayName,
331
+            'expiration' => $expiration,
332
+            'shareWith' => $shareWith,
333
+            'note' => $note
334
+        ]);
335
+
336
+        $emailTemplate->setSubject($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]));
337
+        $emailTemplate->addHeader();
338
+        $emailTemplate->addHeading($this->l->t('%1$s shared %2$s with you', [$initiatorDisplayName, $filename]), false);
339
+
340
+        if ($note !== '') {
341
+            $emailTemplate->addBodyListItem(
342
+                htmlspecialchars($note),
343
+                $this->l->t('Note:'),
344
+                $this->getAbsoluteImagePath('caldav/description.png'),
345
+                $note
346
+            );
347
+        }
348
+
349
+        if ($expiration !== null) {
350
+            $dateString = (string)$this->l->l('date', $expiration, ['width' => 'medium']);
351
+            $emailTemplate->addBodyListItem(
352
+                $this->l->t('This share is valid until %s at midnight', [$dateString]),
353
+                $this->l->t('Expiration:'),
354
+                $this->getAbsoluteImagePath('caldav/time.png'),
355
+            );
356
+        }
357
+
358
+        $emailTemplate->addBodyButton(
359
+            $this->l->t('Open %s', [$filename]),
360
+            $link
361
+        );
362
+
363
+        // If multiple recipients are given, we send the mail to all of them
364
+        if (count($emails) > 1) {
365
+            // We do not want to expose the email addresses of the other recipients
366
+            $message->setBcc($emails);
367
+        } else {
368
+            $message->setTo($emails);
369
+        }
370
+
371
+        // The "From" contains the sharers name
372
+        $instanceName = $this->defaults->getName();
373
+        $senderName = $instanceName;
374
+        if ($this->settingsManager->replyToInitiator()) {
375
+            $senderName = $this->l->t(
376
+                '%1$s via %2$s',
377
+                [
378
+                    $initiatorDisplayName,
379
+                    $instanceName
380
+                ]
381
+            );
382
+        }
383
+        $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
384
+
385
+        // The "Reply-To" is set to the sharer if an mail address is configured
386
+        // also the default footer contains a "Do not reply" which needs to be adjusted.
387
+        if ($initiatorUser && $this->settingsManager->replyToInitiator()) {
388
+            $initiatorEmail = $initiatorUser->getEMailAddress();
389
+            if ($initiatorEmail !== null) {
390
+                $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
391
+                $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
392
+            } else {
393
+                $emailTemplate->addFooter();
394
+            }
395
+        } else {
396
+            $emailTemplate->addFooter();
397
+        }
398
+
399
+        $message->useTemplate($emailTemplate);
400
+        $failedRecipients = $this->mailer->send($message);
401
+        if (!empty($failedRecipients)) {
402
+            $this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
403
+            return;
404
+        }
405
+    }
406
+
407
+    /**
408
+     * Send password to recipient of a mail share
409
+     * Will return false if
410
+     *  1. the password is empty
411
+     *  2. the setting to send the password by mail is disabled
412
+     *  3. the share is set to send the password by talk
413
+     *
414
+     * @param IShare $share
415
+     * @param string $password
416
+     * @param array $emails
417
+     * @return bool
418
+     */
419
+    protected function sendPassword(IShare $share, string $password, array $emails): bool {
420
+        $filename = $share->getNode()->getName();
421
+        $initiator = $share->getSharedBy();
422
+        $shareWith = $share->getSharedWith();
423
+
424
+        if ($password === '' || $this->settingsManager->sendPasswordByMail() === false || $share->getSendPasswordByTalk()) {
425
+            return false;
426
+        }
427
+
428
+        $initiatorUser = $this->userManager->get($initiator);
429
+        $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
430
+        $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
431
+
432
+        $plainBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
433
+        $htmlBodyPart = $this->l->t('%1$s shared %2$s with you. You should have already received a separate mail with a link to access it.', [$initiatorDisplayName, $filename]);
434
+
435
+        $message = $this->mailer->createMessage();
436
+
437
+        $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.RecipientPasswordNotification', [
438
+            'filename' => $filename,
439
+            'password' => $password,
440
+            'initiator' => $initiatorDisplayName,
441
+            'initiatorEmail' => $initiatorEmailAddress,
442
+            'shareWith' => $shareWith,
443
+        ]);
444
+
445
+        $emailTemplate->setSubject($this->l->t('Password to access %1$s shared to you by %2$s', [$filename, $initiatorDisplayName]));
446
+        $emailTemplate->addHeader();
447
+        $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false);
448
+        $emailTemplate->addBodyText(htmlspecialchars($htmlBodyPart), $plainBodyPart);
449
+        $emailTemplate->addBodyText($this->l->t('It is protected with the following password:'));
450
+        $emailTemplate->addBodyText($password);
451
+
452
+        if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
453
+            $expirationTime = new \DateTime();
454
+            $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
455
+            $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
456
+            $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
457
+        }
458
+
459
+        // If multiple recipients are given, we send the mail to all of them
460
+        if (count($emails) > 1) {
461
+            // We do not want to expose the email addresses of the other recipients
462
+            $message->setBcc($emails);
463
+        } else {
464
+            $message->setTo($emails);
465
+        }
466
+
467
+        // The "From" contains the sharers name
468
+        $instanceName = $this->defaults->getName();
469
+        $senderName = $instanceName;
470
+        if ($this->settingsManager->replyToInitiator()) {
471
+            $senderName = $this->l->t(
472
+                '%1$s via %2$s',
473
+                [
474
+                    $initiatorDisplayName,
475
+                    $instanceName
476
+                ]
477
+            );
478
+        }
479
+        $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
480
+
481
+        // The "Reply-To" is set to the sharer if an mail address is configured
482
+        // also the default footer contains a "Do not reply" which needs to be adjusted.
483
+        if ($initiatorUser && $this->settingsManager->replyToInitiator()) {
484
+            $initiatorEmail = $initiatorUser->getEMailAddress();
485
+            if ($initiatorEmail !== null) {
486
+                $message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
487
+                $emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
488
+            } else {
489
+                $emailTemplate->addFooter();
490
+            }
491
+        } else {
492
+            $emailTemplate->addFooter();
493
+        }
494
+
495
+        $message->useTemplate($emailTemplate);
496
+        $failedRecipients = $this->mailer->send($message);
497
+        if (!empty($failedRecipients)) {
498
+            $this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients));
499
+            return false;
500
+        }
501
+
502
+        $this->createPasswordSendActivity($share, $shareWith, false);
503
+        return true;
504
+    }
505
+
506
+    protected function sendNote(IShare $share): void {
507
+        $recipient = $share->getSharedWith();
508
+
509
+
510
+        $filename = $share->getNode()->getName();
511
+        $initiator = $share->getSharedBy();
512
+        $note = $share->getNote();
513
+
514
+        $initiatorUser = $this->userManager->get($initiator);
515
+        $initiatorDisplayName = ($initiatorUser instanceof IUser) ? $initiatorUser->getDisplayName() : $initiator;
516
+        $initiatorEmailAddress = ($initiatorUser instanceof IUser) ? $initiatorUser->getEMailAddress() : null;
517
+
518
+        $plainHeading = $this->l->t('%1$s shared %2$s with you and wants to add:', [$initiatorDisplayName, $filename]);
519
+        $htmlHeading = $this->l->t('%1$s shared %2$s with you and wants to add', [$initiatorDisplayName, $filename]);
520
+
521
+        $message = $this->mailer->createMessage();
522
+
523
+        $emailTemplate = $this->mailer->createEMailTemplate('shareByMail.sendNote');
524
+
525
+        $emailTemplate->setSubject($this->l->t('%s added a note to a file shared with you', [$initiatorDisplayName]));
526
+        $emailTemplate->addHeader();
527
+        $emailTemplate->addHeading(htmlspecialchars($htmlHeading), $plainHeading);
528
+        $emailTemplate->addBodyText(htmlspecialchars($note), $note);
529
+
530
+        $link = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare',
531
+            ['token' => $share->getToken()]);
532
+        $emailTemplate->addBodyButton(
533
+            $this->l->t('Open %s', [$filename]),
534
+            $link
535
+        );
536
+
537
+        // The "From" contains the sharers name
538
+        $instanceName = $this->defaults->getName();
539
+        $senderName = $instanceName;
540
+        if ($this->settingsManager->replyToInitiator()) {
541
+            $senderName = $this->l->t(
542
+                '%1$s via %2$s',
543
+                [
544
+                    $initiatorDisplayName,
545
+                    $instanceName
546
+                ]
547
+            );
548
+        }
549
+        $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
550
+        if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
551
+            $message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
552
+            $emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
553
+        } else {
554
+            $emailTemplate->addFooter();
555
+        }
556
+
557
+        $message->setTo([$recipient]);
558
+        $message->useTemplate($emailTemplate);
559
+        $this->mailer->send($message);
560
+    }
561
+
562
+    /**
563
+     * send auto generated password to the owner. This happens if the admin enforces
564
+     * a password for mail shares and forbid to send the password by mail to the recipient
565
+     *
566
+     * @throws \Exception
567
+     */
568
+    protected function sendPasswordToOwner(IShare $share, string $password): bool {
569
+        $filename = $share->getNode()->getName();
570
+        $initiator = $this->userManager->get($share->getSharedBy());
571
+        $initiatorEMailAddress = ($initiator instanceof IUser) ? $initiator->getEMailAddress() : null;
572
+        $initiatorDisplayName = ($initiator instanceof IUser) ? $initiator->getDisplayName() : $share->getSharedBy();
573
+        $shareWith = implode(', ', $this->getSharedWithEmails($share));
574
+
575
+        if ($initiatorEMailAddress === null) {
576
+            throw new \Exception(
577
+                $this->l->t('We cannot send you the auto-generated password. Please set a valid email address in your personal settings and try again.')
578
+            );
579
+        }
580
+
581
+        $bodyPart = $this->l->t('You just shared %1$s with %2$s. The share was already sent to the recipient. Due to the security policies defined by the administrator of %3$s each share needs to be protected by password and it is not allowed to send the password directly to the recipient. Therefore you need to forward the password manually to the recipient.', [$filename, $shareWith, $this->defaults->getName()]);
582
+
583
+        $message = $this->mailer->createMessage();
584
+        $emailTemplate = $this->mailer->createEMailTemplate('sharebymail.OwnerPasswordNotification', [
585
+            'filename' => $filename,
586
+            'password' => $password,
587
+            'initiator' => $initiatorDisplayName,
588
+            'initiatorEmail' => $initiatorEMailAddress,
589
+            'shareWith' => $shareWith,
590
+        ]);
591
+
592
+        $emailTemplate->setSubject($this->l->t('Password to access %1$s shared by you with %2$s', [$filename, $shareWith]));
593
+        $emailTemplate->addHeader();
594
+        $emailTemplate->addHeading($this->l->t('Password to access %s', [$filename]), false);
595
+        $emailTemplate->addBodyText($bodyPart);
596
+        $emailTemplate->addBodyText($this->l->t('This is the password:'));
597
+        $emailTemplate->addBodyText($password);
598
+
599
+        if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
600
+            $expirationTime = new \DateTime();
601
+            $expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
602
+            $expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
603
+            $emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
604
+        }
605
+
606
+        $emailTemplate->addBodyText($this->l->t('You can choose a different password at any time in the share dialog.'));
607
+
608
+        $emailTemplate->addFooter();
609
+
610
+        $instanceName = $this->defaults->getName();
611
+        $senderName = $this->l->t(
612
+            '%1$s via %2$s',
613
+            [
614
+                $initiatorDisplayName,
615
+                $instanceName
616
+            ]
617
+        );
618
+        $message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
619
+        $message->setTo([$initiatorEMailAddress => $initiatorDisplayName]);
620
+        $message->useTemplate($emailTemplate);
621
+        $this->mailer->send($message);
622
+
623
+        $this->createPasswordSendActivity($share, $shareWith, true);
624
+
625
+        return true;
626
+    }
627
+
628
+    private function getAbsoluteImagePath(string $path):string {
629
+        return $this->urlGenerator->getAbsoluteURL(
630
+            $this->urlGenerator->imagePath('core', $path)
631
+        );
632
+    }
633
+
634
+    /**
635
+     * generate share token
636
+     */
637
+    protected function generateToken(int $size = 15): string {
638
+        $token = $this->secureRandom->generate($size, ISecureRandom::CHAR_HUMAN_READABLE);
639
+        return $token;
640
+    }
641
+
642
+    public function getChildren(IShare $parent): array {
643
+        $children = [];
644
+
645
+        $qb = $this->dbConnection->getQueryBuilder();
646
+        $qb->select('*')
647
+            ->from('share')
648
+            ->where($qb->expr()->eq('parent', $qb->createNamedParameter($parent->getId())))
649
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
650
+            ->orderBy('id');
651
+
652
+        $cursor = $qb->executeQuery();
653
+        while ($data = $cursor->fetch()) {
654
+            $children[] = $this->createShareObject($data);
655
+        }
656
+        $cursor->closeCursor();
657
+
658
+        return $children;
659
+    }
660
+
661
+    /**
662
+     * Add share to the database and return the ID
663
+     */
664
+    protected function addShareToDB(
665
+        ?int $itemSource,
666
+        ?string $itemType,
667
+        ?string $shareWith,
668
+        ?string $sharedBy,
669
+        ?string $uidOwner,
670
+        ?int $permissions,
671
+        ?string $token,
672
+        ?string $password,
673
+        ?\DateTimeInterface $passwordExpirationTime,
674
+        ?bool $sendPasswordByTalk,
675
+        ?bool $hideDownload,
676
+        ?string $label,
677
+        ?\DateTimeInterface $expirationTime,
678
+        ?string $note = '',
679
+        ?IAttributes $attributes = null,
680
+        ?bool $mailSend = true,
681
+    ): int {
682
+        $qb = $this->dbConnection->getQueryBuilder();
683
+        $qb->insert('share')
684
+            ->setValue('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
685
+            ->setValue('item_type', $qb->createNamedParameter($itemType))
686
+            ->setValue('item_source', $qb->createNamedParameter($itemSource))
687
+            ->setValue('file_source', $qb->createNamedParameter($itemSource))
688
+            ->setValue('share_with', $qb->createNamedParameter($shareWith))
689
+            ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
690
+            ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
691
+            ->setValue('permissions', $qb->createNamedParameter($permissions))
692
+            ->setValue('token', $qb->createNamedParameter($token))
693
+            ->setValue('password', $qb->createNamedParameter($password))
694
+            ->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE))
695
+            ->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
696
+            ->setValue('stime', $qb->createNamedParameter(time()))
697
+            ->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT))
698
+            ->setValue('label', $qb->createNamedParameter($label))
699
+            ->setValue('note', $qb->createNamedParameter($note))
700
+            ->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT));
701
+
702
+        // set share attributes
703
+        $shareAttributes = $this->formatShareAttributes($attributes);
704
+
705
+        $qb->setValue('attributes', $qb->createNamedParameter($shareAttributes));
706
+        if ($expirationTime !== null) {
707
+            $qb->setValue('expiration', $qb->createNamedParameter($expirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE));
708
+        }
709
+
710
+        $qb->executeStatement();
711
+        return $qb->getLastInsertId();
712
+    }
713
+
714
+    /**
715
+     * Update a share
716
+     */
717
+    public function update(IShare $share, ?string $plainTextPassword = null): IShare {
718
+        $originalShare = $this->getShareById($share->getId());
719
+
720
+        // a real password was given
721
+        $validPassword = $plainTextPassword !== null && $plainTextPassword !== '';
722
+
723
+        if ($validPassword && ($originalShare->getPassword() !== $share->getPassword()
724
+                                || ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
725
+            $emails = $this->getSharedWithEmails($share);
726
+            $validEmails = array_filter($emails, function ($email) {
727
+                return $this->emailValidator->isValid($email);
728
+            });
729
+            $this->sendPassword($share, $plainTextPassword, $validEmails);
730
+        }
731
+
732
+        $shareAttributes = $this->formatShareAttributes($share->getAttributes());
733
+
734
+        /*
735 735
 		 * We allow updating mail shares
736 736
 		 */
737
-		$qb = $this->dbConnection->getQueryBuilder();
738
-		$qb->update('share')
739
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
740
-			->set('item_source', $qb->createNamedParameter($share->getNodeId()))
741
-			->set('file_source', $qb->createNamedParameter($share->getNodeId()))
742
-			->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
743
-			->set('permissions', $qb->createNamedParameter($share->getPermissions()))
744
-			->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
745
-			->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
746
-			->set('password', $qb->createNamedParameter($share->getPassword()))
747
-			->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATETIME_MUTABLE))
748
-			->set('label', $qb->createNamedParameter($share->getLabel()))
749
-			->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
750
-			->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME_MUTABLE))
751
-			->set('note', $qb->createNamedParameter($share->getNote()))
752
-			->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
753
-			->set('attributes', $qb->createNamedParameter($shareAttributes))
754
-			->set('mail_send', $qb->createNamedParameter((int)$share->getMailSend(), IQueryBuilder::PARAM_INT))
755
-			->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL))
756
-			->executeStatement();
757
-
758
-		if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
759
-			$this->sendNote($share);
760
-		}
761
-
762
-		return $share;
763
-	}
764
-
765
-	/**
766
-	 * @inheritdoc
767
-	 */
768
-	public function move(IShare $share, $recipient): IShare {
769
-		/**
770
-		 * nothing to do here, mail shares are only outgoing shares
771
-		 */
772
-		return $share;
773
-	}
774
-
775
-	/**
776
-	 * Delete a share (owner unShares the file)
777
-	 *
778
-	 * @param IShare $share
779
-	 */
780
-	public function delete(IShare $share): void {
781
-		try {
782
-			$this->createShareActivity($share, 'unshare');
783
-		} catch (\Exception $e) {
784
-		}
785
-
786
-		$this->removeShareFromTable((int)$share->getId());
787
-	}
788
-
789
-	/**
790
-	 * @inheritdoc
791
-	 */
792
-	public function deleteFromSelf(IShare $share, $recipient): void {
793
-		// nothing to do here, mail shares are only outgoing shares
794
-	}
795
-
796
-	public function restore(IShare $share, string $recipient): IShare {
797
-		throw new GenericShareException('not implemented');
798
-	}
799
-
800
-	/**
801
-	 * @inheritdoc
802
-	 */
803
-	public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset): array {
804
-		$qb = $this->dbConnection->getQueryBuilder();
805
-		$qb->select('*')
806
-			->from('share');
807
-
808
-		$qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
809
-
810
-		/**
811
-		 * Reshares for this user are shares where they are the owner.
812
-		 */
813
-		if ($reshares === false) {
814
-			//Special case for old shares created via the web UI
815
-			$or1 = $qb->expr()->andX(
816
-				$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
817
-				$qb->expr()->isNull('uid_initiator')
818
-			);
819
-
820
-			$qb->andWhere(
821
-				$qb->expr()->orX(
822
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
823
-					$or1
824
-				)
825
-			);
826
-		} elseif ($node === null) {
827
-			$qb->andWhere(
828
-				$qb->expr()->orX(
829
-					$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
830
-					$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
831
-				)
832
-			);
833
-		}
834
-
835
-		if ($node !== null) {
836
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
837
-		}
838
-
839
-		if ($limit !== -1) {
840
-			$qb->setMaxResults($limit);
841
-		}
842
-
843
-		$qb->setFirstResult($offset);
844
-		$qb->orderBy('id');
845
-
846
-		$cursor = $qb->executeQuery();
847
-		$shares = [];
848
-		while ($data = $cursor->fetch()) {
849
-			$shares[] = $this->createShareObject($data);
850
-		}
851
-		$cursor->closeCursor();
852
-
853
-		return $shares;
854
-	}
855
-
856
-	/**
857
-	 * @inheritdoc
858
-	 */
859
-	public function getShareById($id, $recipientId = null): IShare {
860
-		$qb = $this->dbConnection->getQueryBuilder();
861
-
862
-		$qb->select('*')
863
-			->from('share')
864
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
865
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
866
-
867
-		$cursor = $qb->executeQuery();
868
-		$data = $cursor->fetch();
869
-		$cursor->closeCursor();
870
-
871
-		if ($data === false) {
872
-			throw new ShareNotFound();
873
-		}
874
-
875
-		try {
876
-			$share = $this->createShareObject($data);
877
-		} catch (InvalidShare $e) {
878
-			throw new ShareNotFound();
879
-		}
880
-
881
-		return $share;
882
-	}
883
-
884
-	/**
885
-	 * Get shares for a given path
886
-	 *
887
-	 * @return IShare[]
888
-	 */
889
-	public function getSharesByPath(Node $path): array {
890
-		$qb = $this->dbConnection->getQueryBuilder();
891
-
892
-		$cursor = $qb->select('*')
893
-			->from('share')
894
-			->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
895
-			->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
896
-			->executeQuery();
897
-
898
-		$shares = [];
899
-		while ($data = $cursor->fetch()) {
900
-			$shares[] = $this->createShareObject($data);
901
-		}
902
-		$cursor->closeCursor();
903
-
904
-		return $shares;
905
-	}
906
-
907
-	/**
908
-	 * @inheritdoc
909
-	 */
910
-	public function getSharedWith($userId, $shareType, $node, $limit, $offset): array {
911
-		/** @var IShare[] $shares */
912
-		$shares = [];
913
-
914
-		//Get shares directly with this user
915
-		$qb = $this->dbConnection->getQueryBuilder();
916
-		$qb->select('*')
917
-			->from('share');
918
-
919
-		// Order by id
920
-		$qb->orderBy('id');
921
-
922
-		// Set limit and offset
923
-		if ($limit !== -1) {
924
-			$qb->setMaxResults($limit);
925
-		}
926
-		$qb->setFirstResult($offset);
927
-
928
-		$qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
929
-		$qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
930
-
931
-		// Filter by node if provided
932
-		if ($node !== null) {
933
-			$qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
934
-		}
935
-
936
-		$cursor = $qb->executeQuery();
937
-
938
-		while ($data = $cursor->fetch()) {
939
-			$shares[] = $this->createShareObject($data);
940
-		}
941
-		$cursor->closeCursor();
942
-
943
-
944
-		return $shares;
945
-	}
946
-
947
-	/**
948
-	 * Get a share by token
949
-	 *
950
-	 * @throws ShareNotFound
951
-	 */
952
-	public function getShareByToken($token): IShare {
953
-		$qb = $this->dbConnection->getQueryBuilder();
954
-
955
-		$cursor = $qb->select('*')
956
-			->from('share')
957
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
958
-			->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
959
-			->executeQuery();
960
-
961
-		$data = $cursor->fetch();
962
-
963
-		if ($data === false) {
964
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
965
-		}
966
-
967
-		try {
968
-			$share = $this->createShareObject($data);
969
-		} catch (InvalidShare $e) {
970
-			throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
971
-		}
972
-
973
-		return $share;
974
-	}
975
-
976
-	/**
977
-	 * remove share from table
978
-	 */
979
-	protected function removeShareFromTable(int $shareId): void {
980
-		$qb = $this->dbConnection->getQueryBuilder();
981
-		$qb->delete('share')
982
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
983
-		$qb->executeStatement();
984
-	}
985
-
986
-	/**
987
-	 * Create a share object from a database row
988
-	 *
989
-	 * @throws InvalidShare
990
-	 * @throws ShareNotFound
991
-	 */
992
-	protected function createShareObject(array $data): IShare {
993
-		$share = new Share($this->rootFolder, $this->userManager);
994
-		$share->setId((int)$data['id'])
995
-			->setShareType((int)$data['share_type'])
996
-			->setPermissions((int)$data['permissions'])
997
-			->setTarget($data['file_target'])
998
-			->setMailSend((bool)$data['mail_send'])
999
-			->setNote($data['note'])
1000
-			->setToken($data['token']);
1001
-
1002
-		$shareTime = new \DateTime();
1003
-		$shareTime->setTimestamp((int)$data['stime']);
1004
-		$share->setShareTime($shareTime);
1005
-		$share->setSharedWith($data['share_with'] ?? '');
1006
-		$share->setPassword($data['password']);
1007
-		$passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? '');
1008
-		$share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null);
1009
-		$share->setLabel($data['label'] ?? '');
1010
-		$share->setSendPasswordByTalk((bool)$data['password_by_talk']);
1011
-		$share->setHideDownload((bool)$data['hide_download']);
1012
-		$share->setReminderSent((bool)$data['reminder_sent']);
1013
-
1014
-		if ($data['uid_initiator'] !== null) {
1015
-			$share->setShareOwner($data['uid_owner']);
1016
-			$share->setSharedBy($data['uid_initiator']);
1017
-		} else {
1018
-			//OLD SHARE
1019
-			$share->setSharedBy($data['uid_owner']);
1020
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
1021
-
1022
-			$owner = $path->getOwner();
1023
-			$share->setShareOwner($owner->getUID());
1024
-		}
1025
-
1026
-		if ($data['expiration'] !== null) {
1027
-			$expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
1028
-			if ($expiration !== false) {
1029
-				$share->setExpirationDate($expiration);
1030
-			}
1031
-		}
1032
-
1033
-		$share = $this->updateShareAttributes($share, $data['attributes']);
1034
-
1035
-		$share->setNodeId((int)$data['file_source']);
1036
-		$share->setNodeType($data['item_type']);
1037
-
1038
-		$share->setProviderId($this->identifier());
1039
-
1040
-		return $share;
1041
-	}
1042
-
1043
-	/**
1044
-	 * Get the node with file $id for $user
1045
-	 *
1046
-	 * @throws InvalidShare
1047
-	 */
1048
-	private function getNode(string $userId, int $id): Node {
1049
-		try {
1050
-			$userFolder = $this->rootFolder->getUserFolder($userId);
1051
-		} catch (NoUserException $e) {
1052
-			throw new InvalidShare();
1053
-		}
1054
-
1055
-		$nodes = $userFolder->getById($id);
1056
-
1057
-		if (empty($nodes)) {
1058
-			throw new InvalidShare();
1059
-		}
1060
-
1061
-		return $nodes[0];
1062
-	}
1063
-
1064
-	/**
1065
-	 * A user is deleted from the system
1066
-	 * So clean up the relevant shares.
1067
-	 */
1068
-	public function userDeleted($uid, $shareType): void {
1069
-		$qb = $this->dbConnection->getQueryBuilder();
1070
-
1071
-		$qb->delete('share')
1072
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1073
-			->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
1074
-			->executeStatement();
1075
-	}
1076
-
1077
-	/**
1078
-	 * This provider does not support group shares
1079
-	 */
1080
-	public function groupDeleted($gid): void {
1081
-	}
1082
-
1083
-	/**
1084
-	 * This provider does not support group shares
1085
-	 */
1086
-	public function userDeletedFromGroup($uid, $gid): void {
1087
-	}
1088
-
1089
-	/**
1090
-	 * get database row of a give share
1091
-	 *
1092
-	 * @throws ShareNotFound
1093
-	 */
1094
-	protected function getRawShare(int $id): array {
1095
-		// Now fetch the inserted share and create a complete share object
1096
-		$qb = $this->dbConnection->getQueryBuilder();
1097
-		$qb->select('*')
1098
-			->from('share')
1099
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
1100
-
1101
-		$cursor = $qb->executeQuery();
1102
-		$data = $cursor->fetch();
1103
-		$cursor->closeCursor();
1104
-
1105
-		if ($data === false) {
1106
-			throw new ShareNotFound;
1107
-		}
1108
-
1109
-		return $data;
1110
-	}
1111
-
1112
-	public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true): array {
1113
-		return $this->getSharesInFolderInternal($userId, $node, $reshares);
1114
-	}
1115
-
1116
-	public function getAllSharesInFolder(Folder $node): array {
1117
-		return $this->getSharesInFolderInternal(null, $node, null);
1118
-	}
1119
-
1120
-	/**
1121
-	 * @return array<int, list<IShare>>
1122
-	 */
1123
-	private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array {
1124
-		$qb = $this->dbConnection->getQueryBuilder();
1125
-		$qb->select('*')
1126
-			->from('share', 's')
1127
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
1128
-			->andWhere(
1129
-				$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
1130
-			);
1131
-
1132
-		if ($userId !== null) {
1133
-			/**
1134
-			 * Reshares for this user are shares where they are the owner.
1135
-			 */
1136
-			if ($reshares !== true) {
1137
-				$qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
1138
-			} else {
1139
-				$qb->andWhere(
1140
-					$qb->expr()->orX(
1141
-						$qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
1142
-						$qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
1143
-					)
1144
-				);
1145
-			}
1146
-		}
1147
-
1148
-		$qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
1149
-
1150
-		$qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
1151
-
1152
-		$qb->orderBy('id');
1153
-
1154
-		$cursor = $qb->executeQuery();
1155
-		$shares = [];
1156
-		while ($data = $cursor->fetch()) {
1157
-			$shares[$data['fileid']][] = $this->createShareObject($data);
1158
-		}
1159
-		$cursor->closeCursor();
1160
-
1161
-		return $shares;
1162
-	}
1163
-
1164
-	/**
1165
-	 * @inheritdoc
1166
-	 */
1167
-	public function getAccessList($nodes, $currentAccess): array {
1168
-		$ids = [];
1169
-		foreach ($nodes as $node) {
1170
-			$ids[] = $node->getId();
1171
-		}
1172
-
1173
-		$qb = $this->dbConnection->getQueryBuilder();
1174
-		$qb->select('share_with', 'file_source', 'token')
1175
-			->from('share')
1176
-			->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1177
-			->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1178
-			->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1179
-		$cursor = $qb->executeQuery();
1180
-
1181
-		$public = false;
1182
-		$mail = [];
1183
-		while ($row = $cursor->fetch()) {
1184
-			$public = true;
1185
-			if ($currentAccess === false) {
1186
-				$mail[] = $row['share_with'];
1187
-			} else {
1188
-				$mail[$row['share_with']] = [
1189
-					'node_id' => $row['file_source'],
1190
-					'token' => $row['token']
1191
-				];
1192
-			}
1193
-		}
1194
-		$cursor->closeCursor();
1195
-
1196
-		return ['public' => $public, 'mail' => $mail];
1197
-	}
1198
-
1199
-	public function getAllShares(): iterable {
1200
-		$qb = $this->dbConnection->getQueryBuilder();
1201
-
1202
-		$qb->select('*')
1203
-			->from('share')
1204
-			->where(
1205
-				$qb->expr()->orX(
1206
-					$qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
1207
-				)
1208
-			);
1209
-
1210
-		$cursor = $qb->executeQuery();
1211
-		while ($data = $cursor->fetch()) {
1212
-			try {
1213
-				$share = $this->createShareObject($data);
1214
-			} catch (InvalidShare $e) {
1215
-				continue;
1216
-			} catch (ShareNotFound $e) {
1217
-				continue;
1218
-			}
1219
-
1220
-			yield $share;
1221
-		}
1222
-		$cursor->closeCursor();
1223
-	}
1224
-
1225
-	/**
1226
-	 * Extract the emails from the share
1227
-	 * It can be a single email, from the share_with field
1228
-	 * or a list of emails from the emails attributes field.
1229
-	 * @param IShare $share
1230
-	 * @return string[]
1231
-	 */
1232
-	protected function getSharedWithEmails(IShare $share): array {
1233
-		$attributes = $share->getAttributes();
1234
-
1235
-		if ($attributes === null) {
1236
-			return [$share->getSharedWith()];
1237
-		}
1238
-
1239
-		$emails = $attributes->getAttribute('shareWith', 'emails');
1240
-		if (isset($emails) && is_array($emails) && !empty($emails)) {
1241
-			return $emails;
1242
-		}
1243
-		return [$share->getSharedWith()];
1244
-	}
737
+        $qb = $this->dbConnection->getQueryBuilder();
738
+        $qb->update('share')
739
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($share->getId())))
740
+            ->set('item_source', $qb->createNamedParameter($share->getNodeId()))
741
+            ->set('file_source', $qb->createNamedParameter($share->getNodeId()))
742
+            ->set('share_with', $qb->createNamedParameter($share->getSharedWith()))
743
+            ->set('permissions', $qb->createNamedParameter($share->getPermissions()))
744
+            ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
745
+            ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
746
+            ->set('password', $qb->createNamedParameter($share->getPassword()))
747
+            ->set('password_expiration_time', $qb->createNamedParameter($share->getPasswordExpirationTime(), IQueryBuilder::PARAM_DATETIME_MUTABLE))
748
+            ->set('label', $qb->createNamedParameter($share->getLabel()))
749
+            ->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
750
+            ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME_MUTABLE))
751
+            ->set('note', $qb->createNamedParameter($share->getNote()))
752
+            ->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
753
+            ->set('attributes', $qb->createNamedParameter($shareAttributes))
754
+            ->set('mail_send', $qb->createNamedParameter((int)$share->getMailSend(), IQueryBuilder::PARAM_INT))
755
+            ->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL))
756
+            ->executeStatement();
757
+
758
+        if ($originalShare->getNote() !== $share->getNote() && $share->getNote() !== '') {
759
+            $this->sendNote($share);
760
+        }
761
+
762
+        return $share;
763
+    }
764
+
765
+    /**
766
+     * @inheritdoc
767
+     */
768
+    public function move(IShare $share, $recipient): IShare {
769
+        /**
770
+         * nothing to do here, mail shares are only outgoing shares
771
+         */
772
+        return $share;
773
+    }
774
+
775
+    /**
776
+     * Delete a share (owner unShares the file)
777
+     *
778
+     * @param IShare $share
779
+     */
780
+    public function delete(IShare $share): void {
781
+        try {
782
+            $this->createShareActivity($share, 'unshare');
783
+        } catch (\Exception $e) {
784
+        }
785
+
786
+        $this->removeShareFromTable((int)$share->getId());
787
+    }
788
+
789
+    /**
790
+     * @inheritdoc
791
+     */
792
+    public function deleteFromSelf(IShare $share, $recipient): void {
793
+        // nothing to do here, mail shares are only outgoing shares
794
+    }
795
+
796
+    public function restore(IShare $share, string $recipient): IShare {
797
+        throw new GenericShareException('not implemented');
798
+    }
799
+
800
+    /**
801
+     * @inheritdoc
802
+     */
803
+    public function getSharesBy($userId, $shareType, $node, $reshares, $limit, $offset): array {
804
+        $qb = $this->dbConnection->getQueryBuilder();
805
+        $qb->select('*')
806
+            ->from('share');
807
+
808
+        $qb->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
809
+
810
+        /**
811
+         * Reshares for this user are shares where they are the owner.
812
+         */
813
+        if ($reshares === false) {
814
+            //Special case for old shares created via the web UI
815
+            $or1 = $qb->expr()->andX(
816
+                $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
817
+                $qb->expr()->isNull('uid_initiator')
818
+            );
819
+
820
+            $qb->andWhere(
821
+                $qb->expr()->orX(
822
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)),
823
+                    $or1
824
+                )
825
+            );
826
+        } elseif ($node === null) {
827
+            $qb->andWhere(
828
+                $qb->expr()->orX(
829
+                    $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
830
+                    $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
831
+                )
832
+            );
833
+        }
834
+
835
+        if ($node !== null) {
836
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
837
+        }
838
+
839
+        if ($limit !== -1) {
840
+            $qb->setMaxResults($limit);
841
+        }
842
+
843
+        $qb->setFirstResult($offset);
844
+        $qb->orderBy('id');
845
+
846
+        $cursor = $qb->executeQuery();
847
+        $shares = [];
848
+        while ($data = $cursor->fetch()) {
849
+            $shares[] = $this->createShareObject($data);
850
+        }
851
+        $cursor->closeCursor();
852
+
853
+        return $shares;
854
+    }
855
+
856
+    /**
857
+     * @inheritdoc
858
+     */
859
+    public function getShareById($id, $recipientId = null): IShare {
860
+        $qb = $this->dbConnection->getQueryBuilder();
861
+
862
+        $qb->select('*')
863
+            ->from('share')
864
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
865
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
866
+
867
+        $cursor = $qb->executeQuery();
868
+        $data = $cursor->fetch();
869
+        $cursor->closeCursor();
870
+
871
+        if ($data === false) {
872
+            throw new ShareNotFound();
873
+        }
874
+
875
+        try {
876
+            $share = $this->createShareObject($data);
877
+        } catch (InvalidShare $e) {
878
+            throw new ShareNotFound();
879
+        }
880
+
881
+        return $share;
882
+    }
883
+
884
+    /**
885
+     * Get shares for a given path
886
+     *
887
+     * @return IShare[]
888
+     */
889
+    public function getSharesByPath(Node $path): array {
890
+        $qb = $this->dbConnection->getQueryBuilder();
891
+
892
+        $cursor = $qb->select('*')
893
+            ->from('share')
894
+            ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId())))
895
+            ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
896
+            ->executeQuery();
897
+
898
+        $shares = [];
899
+        while ($data = $cursor->fetch()) {
900
+            $shares[] = $this->createShareObject($data);
901
+        }
902
+        $cursor->closeCursor();
903
+
904
+        return $shares;
905
+    }
906
+
907
+    /**
908
+     * @inheritdoc
909
+     */
910
+    public function getSharedWith($userId, $shareType, $node, $limit, $offset): array {
911
+        /** @var IShare[] $shares */
912
+        $shares = [];
913
+
914
+        //Get shares directly with this user
915
+        $qb = $this->dbConnection->getQueryBuilder();
916
+        $qb->select('*')
917
+            ->from('share');
918
+
919
+        // Order by id
920
+        $qb->orderBy('id');
921
+
922
+        // Set limit and offset
923
+        if ($limit !== -1) {
924
+            $qb->setMaxResults($limit);
925
+        }
926
+        $qb->setFirstResult($offset);
927
+
928
+        $qb->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)));
929
+        $qb->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId)));
930
+
931
+        // Filter by node if provided
932
+        if ($node !== null) {
933
+            $qb->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($node->getId())));
934
+        }
935
+
936
+        $cursor = $qb->executeQuery();
937
+
938
+        while ($data = $cursor->fetch()) {
939
+            $shares[] = $this->createShareObject($data);
940
+        }
941
+        $cursor->closeCursor();
942
+
943
+
944
+        return $shares;
945
+    }
946
+
947
+    /**
948
+     * Get a share by token
949
+     *
950
+     * @throws ShareNotFound
951
+     */
952
+    public function getShareByToken($token): IShare {
953
+        $qb = $this->dbConnection->getQueryBuilder();
954
+
955
+        $cursor = $qb->select('*')
956
+            ->from('share')
957
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
958
+            ->andWhere($qb->expr()->eq('token', $qb->createNamedParameter($token)))
959
+            ->executeQuery();
960
+
961
+        $data = $cursor->fetch();
962
+
963
+        if ($data === false) {
964
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
965
+        }
966
+
967
+        try {
968
+            $share = $this->createShareObject($data);
969
+        } catch (InvalidShare $e) {
970
+            throw new ShareNotFound('Share not found', $this->l->t('Could not find share'));
971
+        }
972
+
973
+        return $share;
974
+    }
975
+
976
+    /**
977
+     * remove share from table
978
+     */
979
+    protected function removeShareFromTable(int $shareId): void {
980
+        $qb = $this->dbConnection->getQueryBuilder();
981
+        $qb->delete('share')
982
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)));
983
+        $qb->executeStatement();
984
+    }
985
+
986
+    /**
987
+     * Create a share object from a database row
988
+     *
989
+     * @throws InvalidShare
990
+     * @throws ShareNotFound
991
+     */
992
+    protected function createShareObject(array $data): IShare {
993
+        $share = new Share($this->rootFolder, $this->userManager);
994
+        $share->setId((int)$data['id'])
995
+            ->setShareType((int)$data['share_type'])
996
+            ->setPermissions((int)$data['permissions'])
997
+            ->setTarget($data['file_target'])
998
+            ->setMailSend((bool)$data['mail_send'])
999
+            ->setNote($data['note'])
1000
+            ->setToken($data['token']);
1001
+
1002
+        $shareTime = new \DateTime();
1003
+        $shareTime->setTimestamp((int)$data['stime']);
1004
+        $share->setShareTime($shareTime);
1005
+        $share->setSharedWith($data['share_with'] ?? '');
1006
+        $share->setPassword($data['password']);
1007
+        $passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? '');
1008
+        $share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null);
1009
+        $share->setLabel($data['label'] ?? '');
1010
+        $share->setSendPasswordByTalk((bool)$data['password_by_talk']);
1011
+        $share->setHideDownload((bool)$data['hide_download']);
1012
+        $share->setReminderSent((bool)$data['reminder_sent']);
1013
+
1014
+        if ($data['uid_initiator'] !== null) {
1015
+            $share->setShareOwner($data['uid_owner']);
1016
+            $share->setSharedBy($data['uid_initiator']);
1017
+        } else {
1018
+            //OLD SHARE
1019
+            $share->setSharedBy($data['uid_owner']);
1020
+            $path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
1021
+
1022
+            $owner = $path->getOwner();
1023
+            $share->setShareOwner($owner->getUID());
1024
+        }
1025
+
1026
+        if ($data['expiration'] !== null) {
1027
+            $expiration = \DateTime::createFromFormat('Y-m-d H:i:s', $data['expiration']);
1028
+            if ($expiration !== false) {
1029
+                $share->setExpirationDate($expiration);
1030
+            }
1031
+        }
1032
+
1033
+        $share = $this->updateShareAttributes($share, $data['attributes']);
1034
+
1035
+        $share->setNodeId((int)$data['file_source']);
1036
+        $share->setNodeType($data['item_type']);
1037
+
1038
+        $share->setProviderId($this->identifier());
1039
+
1040
+        return $share;
1041
+    }
1042
+
1043
+    /**
1044
+     * Get the node with file $id for $user
1045
+     *
1046
+     * @throws InvalidShare
1047
+     */
1048
+    private function getNode(string $userId, int $id): Node {
1049
+        try {
1050
+            $userFolder = $this->rootFolder->getUserFolder($userId);
1051
+        } catch (NoUserException $e) {
1052
+            throw new InvalidShare();
1053
+        }
1054
+
1055
+        $nodes = $userFolder->getById($id);
1056
+
1057
+        if (empty($nodes)) {
1058
+            throw new InvalidShare();
1059
+        }
1060
+
1061
+        return $nodes[0];
1062
+    }
1063
+
1064
+    /**
1065
+     * A user is deleted from the system
1066
+     * So clean up the relevant shares.
1067
+     */
1068
+    public function userDeleted($uid, $shareType): void {
1069
+        $qb = $this->dbConnection->getQueryBuilder();
1070
+
1071
+        $qb->delete('share')
1072
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1073
+            ->andWhere($qb->expr()->eq('uid_owner', $qb->createNamedParameter($uid)))
1074
+            ->executeStatement();
1075
+    }
1076
+
1077
+    /**
1078
+     * This provider does not support group shares
1079
+     */
1080
+    public function groupDeleted($gid): void {
1081
+    }
1082
+
1083
+    /**
1084
+     * This provider does not support group shares
1085
+     */
1086
+    public function userDeletedFromGroup($uid, $gid): void {
1087
+    }
1088
+
1089
+    /**
1090
+     * get database row of a give share
1091
+     *
1092
+     * @throws ShareNotFound
1093
+     */
1094
+    protected function getRawShare(int $id): array {
1095
+        // Now fetch the inserted share and create a complete share object
1096
+        $qb = $this->dbConnection->getQueryBuilder();
1097
+        $qb->select('*')
1098
+            ->from('share')
1099
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
1100
+
1101
+        $cursor = $qb->executeQuery();
1102
+        $data = $cursor->fetch();
1103
+        $cursor->closeCursor();
1104
+
1105
+        if ($data === false) {
1106
+            throw new ShareNotFound;
1107
+        }
1108
+
1109
+        return $data;
1110
+    }
1111
+
1112
+    public function getSharesInFolder($userId, Folder $node, $reshares, $shallow = true): array {
1113
+        return $this->getSharesInFolderInternal($userId, $node, $reshares);
1114
+    }
1115
+
1116
+    public function getAllSharesInFolder(Folder $node): array {
1117
+        return $this->getSharesInFolderInternal(null, $node, null);
1118
+    }
1119
+
1120
+    /**
1121
+     * @return array<int, list<IShare>>
1122
+     */
1123
+    private function getSharesInFolderInternal(?string $userId, Folder $node, ?bool $reshares): array {
1124
+        $qb = $this->dbConnection->getQueryBuilder();
1125
+        $qb->select('*')
1126
+            ->from('share', 's')
1127
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)))
1128
+            ->andWhere(
1129
+                $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
1130
+            );
1131
+
1132
+        if ($userId !== null) {
1133
+            /**
1134
+             * Reshares for this user are shares where they are the owner.
1135
+             */
1136
+            if ($reshares !== true) {
1137
+                $qb->andWhere($qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId)));
1138
+            } else {
1139
+                $qb->andWhere(
1140
+                    $qb->expr()->orX(
1141
+                        $qb->expr()->eq('uid_owner', $qb->createNamedParameter($userId)),
1142
+                        $qb->expr()->eq('uid_initiator', $qb->createNamedParameter($userId))
1143
+                    )
1144
+                );
1145
+            }
1146
+        }
1147
+
1148
+        $qb->innerJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'));
1149
+
1150
+        $qb->andWhere($qb->expr()->eq('f.parent', $qb->createNamedParameter($node->getId())));
1151
+
1152
+        $qb->orderBy('id');
1153
+
1154
+        $cursor = $qb->executeQuery();
1155
+        $shares = [];
1156
+        while ($data = $cursor->fetch()) {
1157
+            $shares[$data['fileid']][] = $this->createShareObject($data);
1158
+        }
1159
+        $cursor->closeCursor();
1160
+
1161
+        return $shares;
1162
+    }
1163
+
1164
+    /**
1165
+     * @inheritdoc
1166
+     */
1167
+    public function getAccessList($nodes, $currentAccess): array {
1168
+        $ids = [];
1169
+        foreach ($nodes as $node) {
1170
+            $ids[] = $node->getId();
1171
+        }
1172
+
1173
+        $qb = $this->dbConnection->getQueryBuilder();
1174
+        $qb->select('share_with', 'file_source', 'token')
1175
+            ->from('share')
1176
+            ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL)))
1177
+            ->andWhere($qb->expr()->in('file_source', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)))
1178
+            ->andWhere($qb->expr()->in('item_type', $qb->createNamedParameter(['file', 'folder'], IQueryBuilder::PARAM_STR_ARRAY)));
1179
+        $cursor = $qb->executeQuery();
1180
+
1181
+        $public = false;
1182
+        $mail = [];
1183
+        while ($row = $cursor->fetch()) {
1184
+            $public = true;
1185
+            if ($currentAccess === false) {
1186
+                $mail[] = $row['share_with'];
1187
+            } else {
1188
+                $mail[$row['share_with']] = [
1189
+                    'node_id' => $row['file_source'],
1190
+                    'token' => $row['token']
1191
+                ];
1192
+            }
1193
+        }
1194
+        $cursor->closeCursor();
1195
+
1196
+        return ['public' => $public, 'mail' => $mail];
1197
+    }
1198
+
1199
+    public function getAllShares(): iterable {
1200
+        $qb = $this->dbConnection->getQueryBuilder();
1201
+
1202
+        $qb->select('*')
1203
+            ->from('share')
1204
+            ->where(
1205
+                $qb->expr()->orX(
1206
+                    $qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_EMAIL))
1207
+                )
1208
+            );
1209
+
1210
+        $cursor = $qb->executeQuery();
1211
+        while ($data = $cursor->fetch()) {
1212
+            try {
1213
+                $share = $this->createShareObject($data);
1214
+            } catch (InvalidShare $e) {
1215
+                continue;
1216
+            } catch (ShareNotFound $e) {
1217
+                continue;
1218
+            }
1219
+
1220
+            yield $share;
1221
+        }
1222
+        $cursor->closeCursor();
1223
+    }
1224
+
1225
+    /**
1226
+     * Extract the emails from the share
1227
+     * It can be a single email, from the share_with field
1228
+     * or a list of emails from the emails attributes field.
1229
+     * @param IShare $share
1230
+     * @return string[]
1231
+     */
1232
+    protected function getSharedWithEmails(IShare $share): array {
1233
+        $attributes = $share->getAttributes();
1234
+
1235
+        if ($attributes === null) {
1236
+            return [$share->getSharedWith()];
1237
+        }
1238
+
1239
+        $emails = $attributes->getAttribute('shareWith', 'emails');
1240
+        if (isset($emails) && is_array($emails) && !empty($emails)) {
1241
+            return $emails;
1242
+        }
1243
+        return [$share->getSharedWith()];
1244
+    }
1245 1245
 }
Please login to merge, or discard this patch.
apps/sharebymail/tests/ShareByMailProviderTest.php 1 patch
Indentation   +1810 added lines, -1810 removed lines patch added patch discarded remove patch
@@ -50,1861 +50,1861 @@
 block discarded – undo
50 50
  * @group DB
51 51
  */
52 52
 class ShareByMailProviderTest extends TestCase {
53
-	use EmailValidatorTrait;
54
-
55
-	private IDBConnection $connection;
56
-
57
-	private IL10N&MockObject $l;
58
-	private IShare&MockObject $share;
59
-	private IConfig&MockObject $config;
60
-	private IMailer&MockObject $mailer;
61
-	private IHasher&MockObject $hasher;
62
-	private Defaults&MockObject $defaults;
63
-	private IManager&MockObject $shareManager;
64
-	private LoggerInterface&MockObject $logger;
65
-	private IRootFolder&MockObject $rootFolder;
66
-	private IUserManager&MockObject $userManager;
67
-	private ISecureRandom&MockObject $secureRandom;
68
-	private IURLGenerator&MockObject $urlGenerator;
69
-	private SettingsManager&MockObject $settingsManager;
70
-	private IActivityManager&MockObject $activityManager;
71
-	private IEventDispatcher&MockObject $eventDispatcher;
72
-
73
-	protected function setUp(): void {
74
-		parent::setUp();
75
-
76
-		$this->connection = Server::get(IDBConnection::class);
77
-
78
-		$this->l = $this->createMock(IL10N::class);
79
-		$this->l->method('t')
80
-			->willReturnCallback(function ($text, $parameters = []) {
81
-				return vsprintf($text, $parameters);
82
-			});
83
-		$this->config = $this->createMock(IConfig::class);
84
-		$this->logger = $this->createMock(LoggerInterface::class);
85
-		$this->rootFolder = $this->createMock('OCP\Files\IRootFolder');
86
-		$this->userManager = $this->createMock(IUserManager::class);
87
-		$this->secureRandom = $this->createMock('\OCP\Security\ISecureRandom');
88
-		$this->mailer = $this->createMock('\OCP\Mail\IMailer');
89
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
90
-		$this->share = $this->createMock(IShare::class);
91
-		$this->activityManager = $this->createMock('OCP\Activity\IManager');
92
-		$this->settingsManager = $this->createMock(SettingsManager::class);
93
-		$this->defaults = $this->createMock(Defaults::class);
94
-		$this->hasher = $this->createMock(IHasher::class);
95
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
96
-		$this->shareManager = $this->createMock(IManager::class);
97
-
98
-		$this->userManager->expects($this->any())->method('userExists')->willReturn(true);
99
-		$this->config->expects($this->any())->method('getAppValue')->with('core', 'enforce_strict_email_check')->willReturn('yes');
100
-	}
101
-
102
-	/**
103
-	 * get instance of Mocked ShareByMailProvider
104
-	 *
105
-	 * @param array $mockedMethods internal methods which should be mocked
106
-	 * @return \PHPUnit\Framework\MockObject\MockObject | ShareByMailProvider
107
-	 */
108
-	private function getInstance(array $mockedMethods = []) {
109
-		if (!empty($mockedMethods)) {
110
-			return $this->getMockBuilder(ShareByMailProvider::class)
111
-				->setConstructorArgs([
112
-					$this->config,
113
-					$this->connection,
114
-					$this->secureRandom,
115
-					$this->userManager,
116
-					$this->rootFolder,
117
-					$this->l,
118
-					$this->logger,
119
-					$this->mailer,
120
-					$this->urlGenerator,
121
-					$this->activityManager,
122
-					$this->settingsManager,
123
-					$this->defaults,
124
-					$this->hasher,
125
-					$this->eventDispatcher,
126
-					$this->shareManager,
127
-					$this->getEmailValidatorWithStrictEmailCheck(),
128
-				])
129
-				->onlyMethods($mockedMethods)
130
-				->getMock();
131
-		}
132
-
133
-		return new ShareByMailProvider(
134
-			$this->config,
135
-			$this->connection,
136
-			$this->secureRandom,
137
-			$this->userManager,
138
-			$this->rootFolder,
139
-			$this->l,
140
-			$this->logger,
141
-			$this->mailer,
142
-			$this->urlGenerator,
143
-			$this->activityManager,
144
-			$this->settingsManager,
145
-			$this->defaults,
146
-			$this->hasher,
147
-			$this->eventDispatcher,
148
-			$this->shareManager,
149
-			$this->getEmailValidatorWithStrictEmailCheck(),
150
-		);
151
-	}
152
-
153
-	protected function tearDown(): void {
154
-		$this->connection
155
-			->getQueryBuilder()
156
-			->delete('share')
157
-			->executeStatement();
158
-
159
-		parent::tearDown();
160
-	}
161
-
162
-	public function testCreate(): void {
163
-		$expectedShare = $this->createMock(IShare::class);
164
-
165
-		$share = $this->createMock(IShare::class);
166
-		$share->expects($this->any())->method('getSharedWith')->willReturn('user1');
167
-
168
-		$node = $this->createMock(File::class);
169
-		$node->expects($this->any())->method('getName')->willReturn('filename');
170
-
171
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'sendEmail', 'sendPassword']);
172
-
173
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
174
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
175
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
176
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']);
177
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare);
178
-		$share->expects($this->any())->method('getNode')->willReturn($node);
179
-
180
-		// As share api link password is not enforced, the password will not be generated.
181
-		$this->shareManager->expects($this->once())->method('shareApiLinkEnforcePassword')->willReturn(false);
182
-		$this->settingsManager->expects($this->never())->method('sendPasswordByMail');
183
-
184
-		// Mail notification is triggered by the share manager.
185
-		$instance->expects($this->never())->method('sendEmail');
186
-		$instance->expects($this->never())->method('sendPassword');
187
-
188
-		$this->assertSame($expectedShare, $instance->create($share));
189
-	}
190
-
191
-	public function testCreateSendPasswordByMailWithoutEnforcedPasswordProtection(): void {
192
-		$expectedShare = $this->createMock(IShare::class);
193
-
194
-		$node = $this->createMock(File::class);
195
-		$node->expects($this->any())->method('getName')->willReturn('filename');
196
-
197
-		$share = $this->createMock(IShare::class);
198
-		$share->expects($this->any())->method('getSharedWith')->willReturn('receiver@examplelölöl.com');
199
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
200
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
201
-		$share->expects($this->any())->method('getNode')->willReturn($node);
202
-		$share->expects($this->any())->method('getId')->willReturn(42);
203
-		$share->expects($this->any())->method('getNote')->willReturn('');
204
-		$share->expects($this->any())->method('getToken')->willReturn('token');
205
-
206
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
207
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
208
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
209
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
210
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']);
211
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare);
212
-		$share->expects($this->any())->method('getNode')->willReturn($node);
213
-
214
-		// The autogenerated password should not be mailed.
215
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
216
-		$this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
217
-		$instance->expects($this->never())->method('autoGeneratePassword');
218
-
219
-		// No password is set and no password sent via talk is requested
220
-		$instance->expects($this->once())->method('sendEmail')->with($share, ['receiver@examplelölöl.com']);
221
-		$instance->expects($this->never())->method('sendPassword');
222
-		$instance->expects($this->never())->method('sendPasswordToOwner');
223
-
224
-		// The manager sends the mail notification.
225
-		// For the sake of testing simplicity, we will handle it ourselves.
226
-		$this->assertSame($expectedShare, $instance->create($share));
227
-		$instance->sendMailNotification($share);
228
-	}
229
-
230
-	public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithPermanentPassword(): void {
231
-		$expectedShare = $this->createMock(IShare::class);
232
-
233
-		$node = $this->createMock(File::class);
234
-		$node->expects($this->any())->method('getName')->willReturn('filename');
235
-
236
-		$share = $this->createMock(IShare::class);
237
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
238
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
239
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
240
-		$share->expects($this->any())->method('getNode')->willReturn($node);
241
-		$share->expects($this->any())->method('getId')->willReturn(42);
242
-		$share->expects($this->any())->method('getNote')->willReturn('');
243
-		$share->expects($this->any())->method('getToken')->willReturn('token');
244
-
245
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
246
-
247
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
248
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
249
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
250
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
251
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
252
-		$share->expects($this->any())->method('getNode')->willReturn($node);
253
-
254
-		$share->expects($this->any())->method('getPassword')->willReturn('password');
255
-		$this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
256
-		$share->expects($this->once())->method('setPassword')->with('passwordHashed');
257
-
258
-		// The given password (but not the autogenerated password) should not be
259
-		// mailed to the receiver of the share because permanent passwords are not enforced.
260
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
261
-		$this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
262
-		$instance->expects($this->never())->method('autoGeneratePassword');
263
-
264
-		// A password is set but no password sent via talk has been requested
265
-		$instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']);
266
-		$instance->expects($this->once())->method('sendPassword')->with($share, 'password');
267
-		$instance->expects($this->never())->method('sendPasswordToOwner');
268
-
269
-		$this->assertSame($expectedShare, $instance->create($share));
270
-		$instance->sendMailNotification($share);
271
-	}
272
-
273
-	public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithoutPermanentPassword(): void {
274
-		$expectedShare = $this->createMock(IShare::class);
275
-
276
-		$node = $this->createMock(File::class);
277
-		$node->expects($this->any())->method('getName')->willReturn('filename');
278
-
279
-		$share = $this->createMock(IShare::class);
280
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
281
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
282
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
283
-		$share->expects($this->any())->method('getNode')->willReturn($node);
284
-		$share->expects($this->any())->method('getId')->willReturn(42);
285
-		$share->expects($this->any())->method('getNote')->willReturn('');
286
-		$share->expects($this->any())->method('getToken')->willReturn('token');
287
-
288
-		$instance = $this->getInstance([
289
-			'getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject',
290
-			'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity',
291
-			'sendEmail', 'sendPassword', 'sendPasswordToOwner',
292
-		]);
293
-
294
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
295
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
296
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
297
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
298
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
299
-		$share->expects($this->any())->method('getNode')->willReturn($node);
300
-
301
-		$share->expects($this->any())->method('getPassword')->willReturn('password');
302
-		$this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
303
-		$share->expects($this->once())->method('setPassword')->with('passwordHashed');
304
-
305
-		// No password is generated, so no emails need to be sent
306
-		// aside from the main email notification.
307
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
308
-		$instance->expects($this->never())->method('autoGeneratePassword');
309
-		$this->config->expects($this->once())->method('getSystemValue')
310
-			->with('sharing.enable_mail_link_password_expiration')
311
-			->willReturn(true);
312
-
313
-		// No password has been set and no password sent via talk has been requested,
314
-		// but password has been enforced for the whole instance and will be generated.
315
-		$instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']);
316
-		$instance->expects($this->never())->method('sendPassword');
317
-		$instance->expects($this->never())->method('sendPasswordToOwner');
318
-
319
-		$this->assertSame($expectedShare, $instance->create($share));
320
-		$instance->sendMailNotification($share);
321
-	}
322
-
323
-	public function testCreateSendPasswordByMailWithEnforcedPasswordProtectionWithPermanentPassword(): void {
324
-		$expectedShare = $this->createMock(IShare::class);
325
-
326
-		$node = $this->createMock(File::class);
327
-		$node->expects($this->any())->method('getName')->willReturn('filename');
328
-
329
-		$share = $this->createMock(IShare::class);
330
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
331
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
332
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
333
-		$share->expects($this->any())->method('getNode')->willReturn($node);
334
-		$share->expects($this->any())->method('getId')->willReturn(42);
335
-		$share->expects($this->any())->method('getNote')->willReturn('');
336
-		$share->expects($this->any())->method('getToken')->willReturn('token');
337
-
338
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
339
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
340
-			->willReturn('https://example.com/file.txt');
341
-
342
-		$this->secureRandom->expects($this->once())
343
-			->method('generate')
344
-			->with(8, ISecureRandom::CHAR_HUMAN_READABLE)
345
-			->willReturn('autogeneratedPassword');
346
-		$this->eventDispatcher->expects($this->once())
347
-			->method('dispatchTyped')
348
-			->with(new GenerateSecurePasswordEvent(PasswordContext::SHARING));
349
-
350
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'createPasswordSendActivity', 'sendPasswordToOwner']);
351
-
352
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
353
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
354
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
355
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']);
356
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare);
357
-
358
-		// Initially not set, but will be set by the autoGeneratePassword method.
359
-		$share->expects($this->exactly(3))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword');
360
-		$this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed');
361
-		$share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed');
362
-
363
-		// The autogenerated password should be mailed to the receiver of the share because permanent passwords are enforced.
364
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
365
-		$this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
366
-		$this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
367
-
368
-		$message = $this->createMock(IMessage::class);
369
-		$message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']);
370
-		$this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
371
-		$calls = [
372
-			[
373
-				'sharebymail.RecipientNotification',
374
-				[
375
-					'filename' => 'filename',
376
-					'link' => 'https://example.com/file.txt',
377
-					'initiator' => 'owner',
378
-					'expiration' => null,
379
-					'shareWith' => '[email protected]',
380
-					'note' => '',
381
-				],
382
-			],
383
-			[
384
-				'sharebymail.RecipientPasswordNotification',
385
-				[
386
-					'filename' => 'filename',
387
-					'password' => 'autogeneratedPassword',
388
-					'initiator' => 'owner',
389
-					'initiatorEmail' => null,
390
-					'shareWith' => '[email protected]',
391
-				],
392
-			],
393
-		];
394
-		$this->mailer->expects($this->exactly(2))
395
-			->method('createEMailTemplate')
396
-			->willReturnCallback(function () use (&$calls) {
397
-				$expected = array_shift($calls);
398
-				$this->assertEquals($expected, func_get_args());
399
-				return $this->createMock(IEMailTemplate::class);
400
-			});
401
-
402
-		// Main email notification is sent as well as the password
403
-		// to the recipient because shareApiLinkEnforcePassword is enabled.
404
-		$this->mailer->expects($this->exactly(2))->method('send');
405
-		$instance->expects($this->never())->method('sendPasswordToOwner');
406
-
407
-		$this->assertSame($expectedShare, $instance->create($share));
408
-		$instance->sendMailNotification($share);
409
-	}
410
-
411
-	public function testCreateSendPasswordByMailWithPasswordAndWithEnforcedPasswordProtectionWithPermanentPassword(): void {
412
-		$expectedShare = $this->createMock(IShare::class);
413
-
414
-		$node = $this->createMock(File::class);
415
-		$node->expects($this->any())->method('getName')->willReturn('filename');
416
-
417
-		$share = $this->createMock(IShare::class);
418
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
419
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
420
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
421
-		$share->expects($this->any())->method('getNode')->willReturn($node);
422
-		$share->expects($this->any())->method('getId')->willReturn(42);
423
-		$share->expects($this->any())->method('getNote')->willReturn('');
424
-		$share->expects($this->any())->method('getToken')->willReturn('token');
425
-
426
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
427
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
428
-			->willReturn('https://example.com/file.txt');
429
-
430
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendPasswordToOwner']);
431
-
432
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
433
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
434
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
435
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
436
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
437
-
438
-		$share->expects($this->exactly(3))->method('getPassword')->willReturn('password');
439
-		$this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
440
-		$share->expects($this->once())->method('setPassword')->with('passwordHashed');
441
-
442
-		// The given password (but not the autogenerated password) should be
443
-		// mailed to the receiver of the share.
444
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
445
-		$this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
446
-		$this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
447
-		$instance->expects($this->never())->method('autoGeneratePassword');
448
-
449
-		$message = $this->createMock(IMessage::class);
450
-		$message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']);
451
-		$this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
452
-
453
-		$calls = [
454
-			[
455
-				'sharebymail.RecipientNotification',
456
-				[
457
-					'filename' => 'filename',
458
-					'link' => 'https://example.com/file.txt',
459
-					'initiator' => 'owner',
460
-					'expiration' => null,
461
-					'shareWith' => '[email protected]',
462
-					'note' => '',
463
-				],
464
-			],
465
-			[
466
-				'sharebymail.RecipientPasswordNotification',
467
-				[
468
-					'filename' => 'filename',
469
-					'password' => 'password',
470
-					'initiator' => 'owner',
471
-					'initiatorEmail' => null,
472
-					'shareWith' => '[email protected]',
473
-				],
474
-			],
475
-		];
476
-		$this->mailer->expects($this->exactly(2))
477
-			->method('createEMailTemplate')
478
-			->willReturnCallback(function () use (&$calls) {
479
-				$expected = array_shift($calls);
480
-				$this->assertEquals($expected, func_get_args());
481
-				return $this->createMock(IEMailTemplate::class);
482
-			});
483
-
484
-		// Main email notification is sent as well as the password
485
-		// to the recipient because the password is set.
486
-		$this->mailer->expects($this->exactly(2))->method('send');
487
-		$instance->expects($this->never())->method('sendPasswordToOwner');
488
-
489
-		$this->assertSame($expectedShare, $instance->create($share));
490
-		$instance->sendMailNotification($share);
491
-	}
492
-
493
-	public function testCreateSendPasswordByTalkWithEnforcedPasswordProtectionWithPermanentPassword(): void {
494
-		$expectedShare = $this->createMock(IShare::class);
495
-
496
-		// The owner of the share.
497
-		$owner = $this->createMock(IUser::class);
498
-		$this->userManager->expects($this->any())->method('get')->with('owner')->willReturn($owner);
499
-		$owner->expects($this->any())->method('getEMailAddress')->willReturn('[email protected]');
500
-		$owner->expects($this->any())->method('getDisplayName')->willReturn('owner');
501
-
502
-		$node = $this->createMock(File::class);
503
-		$node->expects($this->any())->method('getName')->willReturn('filename');
504
-
505
-		$share = $this->createMock(IShare::class);
506
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
507
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(true);
508
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
509
-		$share->expects($this->any())->method('getNode')->willReturn($node);
510
-		$share->expects($this->any())->method('getId')->willReturn(42);
511
-		$share->expects($this->any())->method('getNote')->willReturn('');
512
-		$share->expects($this->any())->method('getToken')->willReturn('token');
513
-
514
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
515
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
516
-			->willReturn('https://example.com/file.txt');
517
-
518
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']);
519
-
520
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
521
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
522
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
523
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']);
524
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare);
525
-
526
-		$share->expects($this->exactly(4))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword', 'autogeneratedPassword');
527
-		$this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed');
528
-		$share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed');
529
-
530
-		// The autogenerated password should be mailed to the owner of the share.
531
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
532
-		$this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
533
-		$this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
534
-		$instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword');
535
-
536
-		$message = $this->createMock(IMessage::class);
537
-		$setToCalls = [
538
-			[['[email protected]']],
539
-			[['[email protected]' => 'owner']],
540
-		];
541
-		$message->expects($this->exactly(2))
542
-			->method('setTo')
543
-			->willReturnCallback(function () use (&$setToCalls, $message) {
544
-				$expected = array_shift($setToCalls);
545
-				$this->assertEquals($expected, func_get_args());
546
-				return $message;
547
-			});
548
-		$this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
549
-
550
-		$calls = [
551
-			[
552
-				'sharebymail.RecipientNotification',
553
-				[
554
-					'filename' => 'filename',
555
-					'link' => 'https://example.com/file.txt',
556
-					'initiator' => 'owner',
557
-					'expiration' => null,
558
-					'shareWith' => '[email protected]',
559
-					'note' => '',
560
-				],
561
-			],
562
-			[
563
-				'sharebymail.OwnerPasswordNotification',
564
-				[
565
-					'filename' => 'filename',
566
-					'password' => 'autogeneratedPassword',
567
-					'initiator' => 'owner',
568
-					'initiatorEmail' => '[email protected]',
569
-					'shareWith' => '[email protected]',
570
-				],
571
-			],
572
-		];
573
-		$this->mailer->expects($this->exactly(2))
574
-			->method('createEMailTemplate')
575
-			->willReturnCallback(function () use (&$calls) {
576
-				$expected = array_shift($calls);
577
-				$this->assertEquals($expected, func_get_args());
578
-				return $this->createMock(IEMailTemplate::class);
579
-			});
580
-
581
-		// Main email notification is sent as well as the password to owner
582
-		// because the password is set and SendPasswordByTalk is enabled.
583
-		$this->mailer->expects($this->exactly(2))->method('send');
584
-
585
-		$this->assertSame($expectedShare, $instance->create($share));
586
-		$instance->sendMailNotification($share);
587
-	}
588
-
589
-	// If attributes is set to multiple emails, use them as BCC
590
-	public function sendNotificationToMultipleEmails() {
591
-		$expectedShare = $this->createMock(IShare::class);
592
-
593
-		$node = $this->createMock(File::class);
594
-		$node->expects($this->any())->method('getName')->willReturn('filename');
595
-
596
-		$share = $this->createMock(IShare::class);
597
-		$share->expects($this->any())->method('getSharedWith')->willReturn('');
598
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
599
-		$share->expects($this->any())->method('getSharedBy')->willReturn('owner');
600
-		$share->expects($this->any())->method('getNode')->willReturn($node);
601
-		$share->expects($this->any())->method('getId')->willReturn(42);
602
-		$share->expects($this->any())->method('getNote')->willReturn('');
603
-		$share->expects($this->any())->method('getToken')->willReturn('token');
604
-
605
-		$attributes = $this->createMock(IAttributes::class);
606
-		$share->expects($this->any())->method('getAttributes')->willReturn($attributes);
607
-		$attributes->expects($this->any())->method('getAttribute')->with('shareWith', 'emails')->willReturn([
608
-			'[email protected]',
609
-			'[email protected]',
610
-			'[email protected]',
611
-		]);
612
-
613
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
614
-
615
-		$instance->expects($this->once())->method('getSharedWith')->willReturn([]);
616
-		$instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
617
-		$instance->expects($this->once())->method('createShareActivity')->with($share);
618
-		$instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
619
-		$instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
620
-		$share->expects($this->any())->method('getNode')->willReturn($node);
621
-
622
-		$share->expects($this->any())->method('getPassword')->willReturn('password');
623
-		$this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
624
-		$share->expects($this->once())->method('setPassword')->with('passwordHashed');
625
-
626
-		// The given password (but not the autogenerated password) should not be
627
-		// mailed to the receiver of the share because permanent passwords are not enforced.
628
-		$this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
629
-		$this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
630
-		$instance->expects($this->never())->method('autoGeneratePassword');
631
-
632
-		// A password is set but no password sent via talk has been requested
633
-		$instance->expects($this->once())->method('sendEmail')
634
-			->with($share, ['[email protected]', '[email protected]', '[email protected]']);
635
-		$instance->expects($this->once())->method('sendPassword')->with($share, 'password');
636
-		$instance->expects($this->never())->method('sendPasswordToOwner');
637
-
638
-
639
-		$message = $this->createMock(IMessage::class);
640
-		$message->expects($this->never())->method('setTo');
641
-		$message->expects($this->exactly(2))->method('setBcc')->with(['[email protected]', '[email protected]', '[email protected]']);
642
-		$this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
643
-
644
-		// Main email notification is sent as well as the password
645
-		// to recipients because the password is set.
646
-		$this->mailer->expects($this->exactly(2))->method('send');
647
-
648
-		$this->assertSame($expectedShare, $instance->create($share));
649
-		$instance->sendMailNotification($share);
650
-	}
651
-
652
-	public function testCreateFailed(): void {
653
-		$this->expectException(\Exception::class);
654
-
655
-		$this->share->expects($this->once())->method('getSharedWith')->willReturn('user1');
656
-		$node = $this->createMock('OCP\Files\Node');
657
-		$node->expects($this->any())->method('getName')->willReturn('fileName');
658
-		$this->share->expects($this->any())->method('getNode')->willReturn($node);
659
-
660
-		$instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject']);
661
-
662
-		$instance->expects($this->once())->method('getSharedWith')->willReturn(['found']);
663
-		$instance->expects($this->never())->method('createMailShare');
664
-		$instance->expects($this->never())->method('getRawShare');
665
-		$instance->expects($this->never())->method('createShareObject');
666
-
667
-		$this->assertSame('shareObject',
668
-			$instance->create($this->share)
669
-		);
670
-	}
671
-
672
-	public function testCreateMailShare(): void {
673
-		$this->share->expects($this->any())->method('getToken')->willReturn('token');
674
-		$this->share->expects($this->once())->method('setToken')->with('token');
675
-		$this->share->expects($this->any())->method('getSharedBy')->willReturn('[email protected]');
676
-		$this->share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
677
-		$this->share->expects($this->any())->method('getNote')->willReturn('Check this!');
678
-		$this->share->expects($this->any())->method('getMailSend')->willReturn(true);
679
-
680
-		$node = $this->createMock('OCP\Files\Node');
681
-		$node->expects($this->any())->method('getName')->willReturn('fileName');
682
-		$this->share->expects($this->any())->method('getNode')->willReturn($node);
683
-
684
-		$instance = $this->getInstance(['generateToken', 'addShareToDB', 'sendMailNotification']);
685
-
686
-		$instance->expects($this->once())->method('generateToken')->willReturn('token');
687
-		$instance->expects($this->once())->method('addShareToDB')->willReturn(42);
688
-
689
-		// The manager handle the mail sending
690
-		$instance->expects($this->never())->method('sendMailNotification');
691
-
692
-		$this->assertSame(42,
693
-			$this->invokePrivate($instance, 'createMailShare', [$this->share])
694
-		);
695
-	}
696
-
697
-	public function testGenerateToken(): void {
698
-		$instance = $this->getInstance();
699
-
700
-		$this->secureRandom->expects($this->once())->method('generate')->willReturn('token');
701
-
702
-		$this->assertSame('token',
703
-			$this->invokePrivate($instance, 'generateToken')
704
-		);
705
-	}
706
-
707
-	public function testAddShareToDB(): void {
708
-		$itemSource = 11;
709
-		$itemType = 'file';
710
-		$shareWith = '[email protected]';
711
-		$sharedBy = 'user1';
712
-		$uidOwner = 'user2';
713
-		$permissions = 1;
714
-		$token = 'token';
715
-		$password = 'password';
716
-		$sendPasswordByTalk = true;
717
-		$hideDownload = true;
718
-		$label = 'label';
719
-		$expiration = new \DateTime();
720
-		$passwordExpirationTime = new \DateTime();
721
-
722
-
723
-		$instance = $this->getInstance();
724
-		$id = $this->invokePrivate(
725
-			$instance,
726
-			'addShareToDB',
727
-			[
728
-				$itemSource,
729
-				$itemType,
730
-				$shareWith,
731
-				$sharedBy,
732
-				$uidOwner,
733
-				$permissions,
734
-				$token,
735
-				$password,
736
-				$passwordExpirationTime,
737
-				$sendPasswordByTalk,
738
-				$hideDownload,
739
-				$label,
740
-				$expiration
741
-			]
742
-		);
743
-
744
-		$qb = $this->connection->getQueryBuilder();
745
-		$qb->select('*')
746
-			->from('share')
747
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
748
-
749
-		$qResult = $qb->execute();
750
-		$result = $qResult->fetchAll();
751
-		$qResult->closeCursor();
752
-
753
-		$this->assertSame(1, count($result));
754
-
755
-		$this->assertSame($itemSource, (int)$result[0]['item_source']);
756
-		$this->assertSame($itemType, $result[0]['item_type']);
757
-		$this->assertSame($shareWith, $result[0]['share_with']);
758
-		$this->assertSame($sharedBy, $result[0]['uid_initiator']);
759
-		$this->assertSame($uidOwner, $result[0]['uid_owner']);
760
-		$this->assertSame($permissions, (int)$result[0]['permissions']);
761
-		$this->assertSame($token, $result[0]['token']);
762
-		$this->assertSame($password, $result[0]['password']);
763
-		$this->assertSame($passwordExpirationTime->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['password_expiration_time'])->getTimestamp());
764
-		$this->assertSame($sendPasswordByTalk, (bool)$result[0]['password_by_talk']);
765
-		$this->assertSame($hideDownload, (bool)$result[0]['hide_download']);
766
-		$this->assertSame($label, $result[0]['label']);
767
-		$this->assertSame($expiration->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['expiration'])->getTimestamp());
768
-	}
769
-
770
-	public function testUpdate(): void {
771
-		$itemSource = 11;
772
-		$itemType = 'file';
773
-		$shareWith = '[email protected]';
774
-		$sharedBy = 'user1';
775
-		$uidOwner = 'user2';
776
-		$permissions = 1;
777
-		$token = 'token';
778
-		$note = 'personal note';
779
-
780
-
781
-		$instance = $this->getInstance();
782
-
783
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note);
784
-
785
-		$this->share->expects($this->once())->method('getPermissions')->willReturn($permissions + 1);
786
-		$this->share->expects($this->once())->method('getShareOwner')->willReturn($uidOwner);
787
-		$this->share->expects($this->once())->method('getSharedBy')->willReturn($sharedBy);
788
-		$this->share->expects($this->any())->method('getNote')->willReturn($note);
789
-		$this->share->expects($this->atLeastOnce())->method('getId')->willReturn($id);
790
-		$this->share->expects($this->atLeastOnce())->method('getNodeId')->willReturn($itemSource);
791
-		$this->share->expects($this->once())->method('getSharedWith')->willReturn($shareWith);
792
-
793
-		$this->assertSame($this->share,
794
-			$instance->update($this->share)
795
-		);
796
-
797
-		$qb = $this->connection->getQueryBuilder();
798
-		$qb->select('*')
799
-			->from('share')
800
-			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
801
-
802
-		$qResult = $qb->execute();
803
-		$result = $qResult->fetchAll();
804
-		$qResult->closeCursor();
805
-
806
-		$this->assertSame(1, count($result));
807
-
808
-		$this->assertSame($itemSource, (int)$result[0]['item_source']);
809
-		$this->assertSame($itemType, $result[0]['item_type']);
810
-		$this->assertSame($shareWith, $result[0]['share_with']);
811
-		$this->assertSame($sharedBy, $result[0]['uid_initiator']);
812
-		$this->assertSame($uidOwner, $result[0]['uid_owner']);
813
-		$this->assertSame($permissions + 1, (int)$result[0]['permissions']);
814
-		$this->assertSame($token, $result[0]['token']);
815
-		$this->assertSame($note, $result[0]['note']);
816
-	}
817
-
818
-	public static function dataUpdateSendPassword(): array {
819
-		return [
820
-			['password', 'hashed', 'hashed new', false, false, true],
821
-			['', 'hashed', 'hashed new', false, false, false],
822
-			[null, 'hashed', 'hashed new', false, false, false],
823
-			['password', 'hashed', 'hashed', false, false, false],
824
-			['password', 'hashed', 'hashed new', false, true, false],
825
-			['password', 'hashed', 'hashed new', true, false, true],
826
-			['password', 'hashed', 'hashed', true, false, true],
827
-		];
828
-	}
829
-
830
-	#[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateSendPassword')]
831
-	public function testUpdateSendPassword(?string $plainTextPassword, string $originalPassword, string $newPassword, bool $originalSendPasswordByTalk, bool $newSendPasswordByTalk, bool $sendMail): void {
832
-		$node = $this->createMock(File::class);
833
-		$node->expects($this->any())->method('getName')->willReturn('filename');
834
-
835
-		$this->settingsManager->method('sendPasswordByMail')->willReturn(true);
836
-
837
-		$originalShare = $this->createMock(IShare::class);
838
-		$originalShare->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
839
-		$originalShare->expects($this->any())->method('getNode')->willReturn($node);
840
-		$originalShare->expects($this->any())->method('getId')->willReturn(42);
841
-		$originalShare->expects($this->any())->method('getPassword')->willReturn($originalPassword);
842
-		$originalShare->expects($this->any())->method('getSendPasswordByTalk')->willReturn($originalSendPasswordByTalk);
843
-
844
-		$share = $this->createMock(IShare::class);
845
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
846
-		$share->expects($this->any())->method('getNode')->willReturn($node);
847
-		$share->expects($this->any())->method('getId')->willReturn(42);
848
-		$share->expects($this->any())->method('getPassword')->willReturn($newPassword);
849
-		$share->expects($this->any())->method('getSendPasswordByTalk')->willReturn($newSendPasswordByTalk);
850
-
851
-		if ($sendMail) {
852
-			$this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [
853
-				'filename' => 'filename',
854
-				'password' => $plainTextPassword,
855
-				'initiator' => null,
856
-				'initiatorEmail' => null,
857
-				'shareWith' => '[email protected]',
858
-			]);
859
-			$this->mailer->expects($this->once())->method('send');
860
-		} else {
861
-			$this->mailer->expects($this->never())->method('send');
862
-		}
863
-
864
-		$instance = $this->getInstance(['getShareById', 'createPasswordSendActivity']);
865
-		$instance->expects($this->once())->method('getShareById')->willReturn($originalShare);
866
-
867
-		$this->assertSame($share,
868
-			$instance->update($share, $plainTextPassword)
869
-		);
870
-	}
871
-
872
-	public function testDelete(): void {
873
-		$instance = $this->getInstance(['removeShareFromTable', 'createShareActivity']);
874
-		$this->share->expects($this->once())->method('getId')->willReturn(42);
875
-		$instance->expects($this->once())->method('removeShareFromTable')->with(42);
876
-		$instance->expects($this->once())->method('createShareActivity')->with($this->share, 'unshare');
877
-		$instance->delete($this->share);
878
-	}
879
-
880
-	public function testGetShareById(): void {
881
-		$instance = $this->getInstance(['createShareObject']);
882
-
883
-		$itemSource = 11;
884
-		$itemType = 'file';
885
-		$shareWith = '[email protected]';
886
-		$sharedBy = 'user1';
887
-		$uidOwner = 'user2';
888
-		$permissions = 1;
889
-		$token = 'token';
890
-
891
-		$this->createDummyShare($itemType, $itemSource, $shareWith, 'user1wrong', 'user2wrong', $permissions, $token);
892
-		$id2 = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
893
-
894
-		$instance->expects($this->once())->method('createShareObject')
895
-			->willReturnCallback(
896
-				function ($data) use ($uidOwner, $sharedBy, $id2) {
897
-					$this->assertSame($uidOwner, $data['uid_owner']);
898
-					$this->assertSame($sharedBy, $data['uid_initiator']);
899
-					$this->assertSame($id2, (int)$data['id']);
900
-					return $this->share;
901
-				}
902
-			);
903
-
904
-		$result = $instance->getShareById($id2);
905
-
906
-		$this->assertInstanceOf('OCP\Share\IShare', $result);
907
-	}
908
-
909
-
910
-	public function testGetShareByIdFailed(): void {
911
-		$this->expectException(ShareNotFound::class);
912
-
913
-		$instance = $this->getInstance(['createShareObject']);
914
-
915
-		$itemSource = 11;
916
-		$itemType = 'file';
917
-		$shareWith = '[email protected]';
918
-		$sharedBy = 'user1';
919
-		$uidOwner = 'user2';
920
-		$permissions = 1;
921
-		$token = 'token';
922
-
923
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
924
-
925
-		$instance->getShareById($id + 1);
926
-	}
927
-
928
-	public function testGetShareByPath(): void {
929
-		$itemSource = 11;
930
-		$itemType = 'file';
931
-		$shareWith = '[email protected]';
932
-		$sharedBy = 'user1';
933
-		$uidOwner = 'user2';
934
-		$permissions = 1;
935
-		$token = 'token';
936
-
937
-		$node = $this->createMock(Node::class);
938
-		$node->expects($this->once())->method('getId')->willReturn($itemSource);
939
-
940
-
941
-		$instance = $this->getInstance(['createShareObject']);
942
-
943
-		$this->createDummyShare($itemType, 111, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
944
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
945
-
946
-		$instance->expects($this->once())->method('createShareObject')
947
-			->willReturnCallback(
948
-				function ($data) use ($uidOwner, $sharedBy, $id) {
949
-					$this->assertSame($uidOwner, $data['uid_owner']);
950
-					$this->assertSame($sharedBy, $data['uid_initiator']);
951
-					$this->assertSame($id, (int)$data['id']);
952
-					return $this->share;
953
-				}
954
-			);
955
-
956
-		$result = $instance->getSharesByPath($node);
957
-
958
-		$this->assertTrue(is_array($result));
959
-		$this->assertSame(1, count($result));
960
-		$this->assertInstanceOf('OCP\Share\IShare', $result[0]);
961
-	}
962
-
963
-	public function testGetShareByToken(): void {
964
-		$itemSource = 11;
965
-		$itemType = 'file';
966
-		$shareWith = '[email protected]';
967
-		$sharedBy = 'user1';
968
-		$uidOwner = 'user2';
969
-		$permissions = 1;
970
-		$token = 'token';
53
+    use EmailValidatorTrait;
54
+
55
+    private IDBConnection $connection;
56
+
57
+    private IL10N&MockObject $l;
58
+    private IShare&MockObject $share;
59
+    private IConfig&MockObject $config;
60
+    private IMailer&MockObject $mailer;
61
+    private IHasher&MockObject $hasher;
62
+    private Defaults&MockObject $defaults;
63
+    private IManager&MockObject $shareManager;
64
+    private LoggerInterface&MockObject $logger;
65
+    private IRootFolder&MockObject $rootFolder;
66
+    private IUserManager&MockObject $userManager;
67
+    private ISecureRandom&MockObject $secureRandom;
68
+    private IURLGenerator&MockObject $urlGenerator;
69
+    private SettingsManager&MockObject $settingsManager;
70
+    private IActivityManager&MockObject $activityManager;
71
+    private IEventDispatcher&MockObject $eventDispatcher;
72
+
73
+    protected function setUp(): void {
74
+        parent::setUp();
75
+
76
+        $this->connection = Server::get(IDBConnection::class);
77
+
78
+        $this->l = $this->createMock(IL10N::class);
79
+        $this->l->method('t')
80
+            ->willReturnCallback(function ($text, $parameters = []) {
81
+                return vsprintf($text, $parameters);
82
+            });
83
+        $this->config = $this->createMock(IConfig::class);
84
+        $this->logger = $this->createMock(LoggerInterface::class);
85
+        $this->rootFolder = $this->createMock('OCP\Files\IRootFolder');
86
+        $this->userManager = $this->createMock(IUserManager::class);
87
+        $this->secureRandom = $this->createMock('\OCP\Security\ISecureRandom');
88
+        $this->mailer = $this->createMock('\OCP\Mail\IMailer');
89
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
90
+        $this->share = $this->createMock(IShare::class);
91
+        $this->activityManager = $this->createMock('OCP\Activity\IManager');
92
+        $this->settingsManager = $this->createMock(SettingsManager::class);
93
+        $this->defaults = $this->createMock(Defaults::class);
94
+        $this->hasher = $this->createMock(IHasher::class);
95
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
96
+        $this->shareManager = $this->createMock(IManager::class);
97
+
98
+        $this->userManager->expects($this->any())->method('userExists')->willReturn(true);
99
+        $this->config->expects($this->any())->method('getAppValue')->with('core', 'enforce_strict_email_check')->willReturn('yes');
100
+    }
101
+
102
+    /**
103
+     * get instance of Mocked ShareByMailProvider
104
+     *
105
+     * @param array $mockedMethods internal methods which should be mocked
106
+     * @return \PHPUnit\Framework\MockObject\MockObject | ShareByMailProvider
107
+     */
108
+    private function getInstance(array $mockedMethods = []) {
109
+        if (!empty($mockedMethods)) {
110
+            return $this->getMockBuilder(ShareByMailProvider::class)
111
+                ->setConstructorArgs([
112
+                    $this->config,
113
+                    $this->connection,
114
+                    $this->secureRandom,
115
+                    $this->userManager,
116
+                    $this->rootFolder,
117
+                    $this->l,
118
+                    $this->logger,
119
+                    $this->mailer,
120
+                    $this->urlGenerator,
121
+                    $this->activityManager,
122
+                    $this->settingsManager,
123
+                    $this->defaults,
124
+                    $this->hasher,
125
+                    $this->eventDispatcher,
126
+                    $this->shareManager,
127
+                    $this->getEmailValidatorWithStrictEmailCheck(),
128
+                ])
129
+                ->onlyMethods($mockedMethods)
130
+                ->getMock();
131
+        }
132
+
133
+        return new ShareByMailProvider(
134
+            $this->config,
135
+            $this->connection,
136
+            $this->secureRandom,
137
+            $this->userManager,
138
+            $this->rootFolder,
139
+            $this->l,
140
+            $this->logger,
141
+            $this->mailer,
142
+            $this->urlGenerator,
143
+            $this->activityManager,
144
+            $this->settingsManager,
145
+            $this->defaults,
146
+            $this->hasher,
147
+            $this->eventDispatcher,
148
+            $this->shareManager,
149
+            $this->getEmailValidatorWithStrictEmailCheck(),
150
+        );
151
+    }
152
+
153
+    protected function tearDown(): void {
154
+        $this->connection
155
+            ->getQueryBuilder()
156
+            ->delete('share')
157
+            ->executeStatement();
158
+
159
+        parent::tearDown();
160
+    }
161
+
162
+    public function testCreate(): void {
163
+        $expectedShare = $this->createMock(IShare::class);
164
+
165
+        $share = $this->createMock(IShare::class);
166
+        $share->expects($this->any())->method('getSharedWith')->willReturn('user1');
167
+
168
+        $node = $this->createMock(File::class);
169
+        $node->expects($this->any())->method('getName')->willReturn('filename');
170
+
171
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'sendEmail', 'sendPassword']);
172
+
173
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
174
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
175
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
176
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']);
177
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare);
178
+        $share->expects($this->any())->method('getNode')->willReturn($node);
179
+
180
+        // As share api link password is not enforced, the password will not be generated.
181
+        $this->shareManager->expects($this->once())->method('shareApiLinkEnforcePassword')->willReturn(false);
182
+        $this->settingsManager->expects($this->never())->method('sendPasswordByMail');
183
+
184
+        // Mail notification is triggered by the share manager.
185
+        $instance->expects($this->never())->method('sendEmail');
186
+        $instance->expects($this->never())->method('sendPassword');
187
+
188
+        $this->assertSame($expectedShare, $instance->create($share));
189
+    }
190
+
191
+    public function testCreateSendPasswordByMailWithoutEnforcedPasswordProtection(): void {
192
+        $expectedShare = $this->createMock(IShare::class);
193
+
194
+        $node = $this->createMock(File::class);
195
+        $node->expects($this->any())->method('getName')->willReturn('filename');
196
+
197
+        $share = $this->createMock(IShare::class);
198
+        $share->expects($this->any())->method('getSharedWith')->willReturn('receiver@examplelölöl.com');
199
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
200
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
201
+        $share->expects($this->any())->method('getNode')->willReturn($node);
202
+        $share->expects($this->any())->method('getId')->willReturn(42);
203
+        $share->expects($this->any())->method('getNote')->willReturn('');
204
+        $share->expects($this->any())->method('getToken')->willReturn('token');
205
+
206
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
207
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
208
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
209
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
210
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare']);
211
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare'])->willReturn($expectedShare);
212
+        $share->expects($this->any())->method('getNode')->willReturn($node);
213
+
214
+        // The autogenerated password should not be mailed.
215
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
216
+        $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
217
+        $instance->expects($this->never())->method('autoGeneratePassword');
218
+
219
+        // No password is set and no password sent via talk is requested
220
+        $instance->expects($this->once())->method('sendEmail')->with($share, ['receiver@examplelölöl.com']);
221
+        $instance->expects($this->never())->method('sendPassword');
222
+        $instance->expects($this->never())->method('sendPasswordToOwner');
223
+
224
+        // The manager sends the mail notification.
225
+        // For the sake of testing simplicity, we will handle it ourselves.
226
+        $this->assertSame($expectedShare, $instance->create($share));
227
+        $instance->sendMailNotification($share);
228
+    }
229
+
230
+    public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithPermanentPassword(): void {
231
+        $expectedShare = $this->createMock(IShare::class);
232
+
233
+        $node = $this->createMock(File::class);
234
+        $node->expects($this->any())->method('getName')->willReturn('filename');
235
+
236
+        $share = $this->createMock(IShare::class);
237
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
238
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
239
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
240
+        $share->expects($this->any())->method('getNode')->willReturn($node);
241
+        $share->expects($this->any())->method('getId')->willReturn(42);
242
+        $share->expects($this->any())->method('getNote')->willReturn('');
243
+        $share->expects($this->any())->method('getToken')->willReturn('token');
244
+
245
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
246
+
247
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
248
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
249
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
250
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
251
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
252
+        $share->expects($this->any())->method('getNode')->willReturn($node);
253
+
254
+        $share->expects($this->any())->method('getPassword')->willReturn('password');
255
+        $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
256
+        $share->expects($this->once())->method('setPassword')->with('passwordHashed');
257
+
258
+        // The given password (but not the autogenerated password) should not be
259
+        // mailed to the receiver of the share because permanent passwords are not enforced.
260
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
261
+        $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
262
+        $instance->expects($this->never())->method('autoGeneratePassword');
263
+
264
+        // A password is set but no password sent via talk has been requested
265
+        $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']);
266
+        $instance->expects($this->once())->method('sendPassword')->with($share, 'password');
267
+        $instance->expects($this->never())->method('sendPasswordToOwner');
268
+
269
+        $this->assertSame($expectedShare, $instance->create($share));
270
+        $instance->sendMailNotification($share);
271
+    }
272
+
273
+    public function testCreateSendPasswordByMailWithPasswordAndWithoutEnforcedPasswordProtectionWithoutPermanentPassword(): void {
274
+        $expectedShare = $this->createMock(IShare::class);
275
+
276
+        $node = $this->createMock(File::class);
277
+        $node->expects($this->any())->method('getName')->willReturn('filename');
278
+
279
+        $share = $this->createMock(IShare::class);
280
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
281
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
282
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
283
+        $share->expects($this->any())->method('getNode')->willReturn($node);
284
+        $share->expects($this->any())->method('getId')->willReturn(42);
285
+        $share->expects($this->any())->method('getNote')->willReturn('');
286
+        $share->expects($this->any())->method('getToken')->willReturn('token');
287
+
288
+        $instance = $this->getInstance([
289
+            'getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject',
290
+            'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity',
291
+            'sendEmail', 'sendPassword', 'sendPasswordToOwner',
292
+        ]);
293
+
294
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
295
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
296
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
297
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
298
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
299
+        $share->expects($this->any())->method('getNode')->willReturn($node);
300
+
301
+        $share->expects($this->any())->method('getPassword')->willReturn('password');
302
+        $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
303
+        $share->expects($this->once())->method('setPassword')->with('passwordHashed');
304
+
305
+        // No password is generated, so no emails need to be sent
306
+        // aside from the main email notification.
307
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
308
+        $instance->expects($this->never())->method('autoGeneratePassword');
309
+        $this->config->expects($this->once())->method('getSystemValue')
310
+            ->with('sharing.enable_mail_link_password_expiration')
311
+            ->willReturn(true);
312
+
313
+        // No password has been set and no password sent via talk has been requested,
314
+        // but password has been enforced for the whole instance and will be generated.
315
+        $instance->expects($this->once())->method('sendEmail')->with($share, ['[email protected]']);
316
+        $instance->expects($this->never())->method('sendPassword');
317
+        $instance->expects($this->never())->method('sendPasswordToOwner');
318
+
319
+        $this->assertSame($expectedShare, $instance->create($share));
320
+        $instance->sendMailNotification($share);
321
+    }
322
+
323
+    public function testCreateSendPasswordByMailWithEnforcedPasswordProtectionWithPermanentPassword(): void {
324
+        $expectedShare = $this->createMock(IShare::class);
325
+
326
+        $node = $this->createMock(File::class);
327
+        $node->expects($this->any())->method('getName')->willReturn('filename');
328
+
329
+        $share = $this->createMock(IShare::class);
330
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
331
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
332
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
333
+        $share->expects($this->any())->method('getNode')->willReturn($node);
334
+        $share->expects($this->any())->method('getId')->willReturn(42);
335
+        $share->expects($this->any())->method('getNote')->willReturn('');
336
+        $share->expects($this->any())->method('getToken')->willReturn('token');
337
+
338
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
339
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
340
+            ->willReturn('https://example.com/file.txt');
341
+
342
+        $this->secureRandom->expects($this->once())
343
+            ->method('generate')
344
+            ->with(8, ISecureRandom::CHAR_HUMAN_READABLE)
345
+            ->willReturn('autogeneratedPassword');
346
+        $this->eventDispatcher->expects($this->once())
347
+            ->method('dispatchTyped')
348
+            ->with(new GenerateSecurePasswordEvent(PasswordContext::SHARING));
349
+
350
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'createPasswordSendActivity', 'sendPasswordToOwner']);
351
+
352
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
353
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
354
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
355
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']);
356
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare);
357
+
358
+        // Initially not set, but will be set by the autoGeneratePassword method.
359
+        $share->expects($this->exactly(3))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword');
360
+        $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed');
361
+        $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed');
362
+
363
+        // The autogenerated password should be mailed to the receiver of the share because permanent passwords are enforced.
364
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
365
+        $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
366
+        $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
367
+
368
+        $message = $this->createMock(IMessage::class);
369
+        $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']);
370
+        $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
371
+        $calls = [
372
+            [
373
+                'sharebymail.RecipientNotification',
374
+                [
375
+                    'filename' => 'filename',
376
+                    'link' => 'https://example.com/file.txt',
377
+                    'initiator' => 'owner',
378
+                    'expiration' => null,
379
+                    'shareWith' => '[email protected]',
380
+                    'note' => '',
381
+                ],
382
+            ],
383
+            [
384
+                'sharebymail.RecipientPasswordNotification',
385
+                [
386
+                    'filename' => 'filename',
387
+                    'password' => 'autogeneratedPassword',
388
+                    'initiator' => 'owner',
389
+                    'initiatorEmail' => null,
390
+                    'shareWith' => '[email protected]',
391
+                ],
392
+            ],
393
+        ];
394
+        $this->mailer->expects($this->exactly(2))
395
+            ->method('createEMailTemplate')
396
+            ->willReturnCallback(function () use (&$calls) {
397
+                $expected = array_shift($calls);
398
+                $this->assertEquals($expected, func_get_args());
399
+                return $this->createMock(IEMailTemplate::class);
400
+            });
401
+
402
+        // Main email notification is sent as well as the password
403
+        // to the recipient because shareApiLinkEnforcePassword is enabled.
404
+        $this->mailer->expects($this->exactly(2))->method('send');
405
+        $instance->expects($this->never())->method('sendPasswordToOwner');
406
+
407
+        $this->assertSame($expectedShare, $instance->create($share));
408
+        $instance->sendMailNotification($share);
409
+    }
410
+
411
+    public function testCreateSendPasswordByMailWithPasswordAndWithEnforcedPasswordProtectionWithPermanentPassword(): void {
412
+        $expectedShare = $this->createMock(IShare::class);
413
+
414
+        $node = $this->createMock(File::class);
415
+        $node->expects($this->any())->method('getName')->willReturn('filename');
416
+
417
+        $share = $this->createMock(IShare::class);
418
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
419
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
420
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
421
+        $share->expects($this->any())->method('getNode')->willReturn($node);
422
+        $share->expects($this->any())->method('getId')->willReturn(42);
423
+        $share->expects($this->any())->method('getNote')->willReturn('');
424
+        $share->expects($this->any())->method('getToken')->willReturn('token');
425
+
426
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
427
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
428
+            ->willReturn('https://example.com/file.txt');
429
+
430
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendPasswordToOwner']);
431
+
432
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
433
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
434
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
435
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
436
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
437
+
438
+        $share->expects($this->exactly(3))->method('getPassword')->willReturn('password');
439
+        $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
440
+        $share->expects($this->once())->method('setPassword')->with('passwordHashed');
441
+
442
+        // The given password (but not the autogenerated password) should be
443
+        // mailed to the receiver of the share.
444
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
445
+        $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
446
+        $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
447
+        $instance->expects($this->never())->method('autoGeneratePassword');
448
+
449
+        $message = $this->createMock(IMessage::class);
450
+        $message->expects($this->exactly(2))->method('setTo')->with(['[email protected]']);
451
+        $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
452
+
453
+        $calls = [
454
+            [
455
+                'sharebymail.RecipientNotification',
456
+                [
457
+                    'filename' => 'filename',
458
+                    'link' => 'https://example.com/file.txt',
459
+                    'initiator' => 'owner',
460
+                    'expiration' => null,
461
+                    'shareWith' => '[email protected]',
462
+                    'note' => '',
463
+                ],
464
+            ],
465
+            [
466
+                'sharebymail.RecipientPasswordNotification',
467
+                [
468
+                    'filename' => 'filename',
469
+                    'password' => 'password',
470
+                    'initiator' => 'owner',
471
+                    'initiatorEmail' => null,
472
+                    'shareWith' => '[email protected]',
473
+                ],
474
+            ],
475
+        ];
476
+        $this->mailer->expects($this->exactly(2))
477
+            ->method('createEMailTemplate')
478
+            ->willReturnCallback(function () use (&$calls) {
479
+                $expected = array_shift($calls);
480
+                $this->assertEquals($expected, func_get_args());
481
+                return $this->createMock(IEMailTemplate::class);
482
+            });
483
+
484
+        // Main email notification is sent as well as the password
485
+        // to the recipient because the password is set.
486
+        $this->mailer->expects($this->exactly(2))->method('send');
487
+        $instance->expects($this->never())->method('sendPasswordToOwner');
488
+
489
+        $this->assertSame($expectedShare, $instance->create($share));
490
+        $instance->sendMailNotification($share);
491
+    }
492
+
493
+    public function testCreateSendPasswordByTalkWithEnforcedPasswordProtectionWithPermanentPassword(): void {
494
+        $expectedShare = $this->createMock(IShare::class);
495
+
496
+        // The owner of the share.
497
+        $owner = $this->createMock(IUser::class);
498
+        $this->userManager->expects($this->any())->method('get')->with('owner')->willReturn($owner);
499
+        $owner->expects($this->any())->method('getEMailAddress')->willReturn('[email protected]');
500
+        $owner->expects($this->any())->method('getDisplayName')->willReturn('owner');
501
+
502
+        $node = $this->createMock(File::class);
503
+        $node->expects($this->any())->method('getName')->willReturn('filename');
504
+
505
+        $share = $this->createMock(IShare::class);
506
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
507
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(true);
508
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
509
+        $share->expects($this->any())->method('getNode')->willReturn($node);
510
+        $share->expects($this->any())->method('getId')->willReturn(42);
511
+        $share->expects($this->any())->method('getNote')->willReturn('');
512
+        $share->expects($this->any())->method('getToken')->willReturn('token');
513
+
514
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
515
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
516
+            ->willReturn('https://example.com/file.txt');
517
+
518
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity']);
519
+
520
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
521
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
522
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
523
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'autogeneratedPassword']);
524
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'autogeneratedPassword'])->willReturn($expectedShare);
525
+
526
+        $share->expects($this->exactly(4))->method('getPassword')->willReturnOnConsecutiveCalls(null, 'autogeneratedPassword', 'autogeneratedPassword', 'autogeneratedPassword');
527
+        $this->hasher->expects($this->once())->method('hash')->with('autogeneratedPassword')->willReturn('autogeneratedPasswordHashed');
528
+        $share->expects($this->once())->method('setPassword')->with('autogeneratedPasswordHashed');
529
+
530
+        // The autogenerated password should be mailed to the owner of the share.
531
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(true);
532
+        $this->config->expects($this->any())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
533
+        $this->settingsManager->expects($this->any())->method('sendPasswordByMail')->willReturn(true);
534
+        $instance->expects($this->once())->method('autoGeneratePassword')->with($share)->willReturn('autogeneratedPassword');
535
+
536
+        $message = $this->createMock(IMessage::class);
537
+        $setToCalls = [
538
+            [['[email protected]']],
539
+            [['[email protected]' => 'owner']],
540
+        ];
541
+        $message->expects($this->exactly(2))
542
+            ->method('setTo')
543
+            ->willReturnCallback(function () use (&$setToCalls, $message) {
544
+                $expected = array_shift($setToCalls);
545
+                $this->assertEquals($expected, func_get_args());
546
+                return $message;
547
+            });
548
+        $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
549
+
550
+        $calls = [
551
+            [
552
+                'sharebymail.RecipientNotification',
553
+                [
554
+                    'filename' => 'filename',
555
+                    'link' => 'https://example.com/file.txt',
556
+                    'initiator' => 'owner',
557
+                    'expiration' => null,
558
+                    'shareWith' => '[email protected]',
559
+                    'note' => '',
560
+                ],
561
+            ],
562
+            [
563
+                'sharebymail.OwnerPasswordNotification',
564
+                [
565
+                    'filename' => 'filename',
566
+                    'password' => 'autogeneratedPassword',
567
+                    'initiator' => 'owner',
568
+                    'initiatorEmail' => '[email protected]',
569
+                    'shareWith' => '[email protected]',
570
+                ],
571
+            ],
572
+        ];
573
+        $this->mailer->expects($this->exactly(2))
574
+            ->method('createEMailTemplate')
575
+            ->willReturnCallback(function () use (&$calls) {
576
+                $expected = array_shift($calls);
577
+                $this->assertEquals($expected, func_get_args());
578
+                return $this->createMock(IEMailTemplate::class);
579
+            });
580
+
581
+        // Main email notification is sent as well as the password to owner
582
+        // because the password is set and SendPasswordByTalk is enabled.
583
+        $this->mailer->expects($this->exactly(2))->method('send');
584
+
585
+        $this->assertSame($expectedShare, $instance->create($share));
586
+        $instance->sendMailNotification($share);
587
+    }
588
+
589
+    // If attributes is set to multiple emails, use them as BCC
590
+    public function sendNotificationToMultipleEmails() {
591
+        $expectedShare = $this->createMock(IShare::class);
592
+
593
+        $node = $this->createMock(File::class);
594
+        $node->expects($this->any())->method('getName')->willReturn('filename');
595
+
596
+        $share = $this->createMock(IShare::class);
597
+        $share->expects($this->any())->method('getSharedWith')->willReturn('');
598
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn(false);
599
+        $share->expects($this->any())->method('getSharedBy')->willReturn('owner');
600
+        $share->expects($this->any())->method('getNode')->willReturn($node);
601
+        $share->expects($this->any())->method('getId')->willReturn(42);
602
+        $share->expects($this->any())->method('getNote')->willReturn('');
603
+        $share->expects($this->any())->method('getToken')->willReturn('token');
604
+
605
+        $attributes = $this->createMock(IAttributes::class);
606
+        $share->expects($this->any())->method('getAttributes')->willReturn($attributes);
607
+        $attributes->expects($this->any())->method('getAttribute')->with('shareWith', 'emails')->willReturn([
608
+            '[email protected]',
609
+            '[email protected]',
610
+            '[email protected]',
611
+        ]);
612
+
613
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject', 'createShareActivity', 'autoGeneratePassword', 'createPasswordSendActivity', 'sendEmail', 'sendPassword', 'sendPasswordToOwner']);
614
+
615
+        $instance->expects($this->once())->method('getSharedWith')->willReturn([]);
616
+        $instance->expects($this->once())->method('createMailShare')->with($share)->willReturn(42);
617
+        $instance->expects($this->once())->method('createShareActivity')->with($share);
618
+        $instance->expects($this->once())->method('getRawShare')->with(42)->willReturn(['rawShare', 'password' => 'password']);
619
+        $instance->expects($this->once())->method('createShareObject')->with(['rawShare', 'password' => 'password'])->willReturn($expectedShare);
620
+        $share->expects($this->any())->method('getNode')->willReturn($node);
621
+
622
+        $share->expects($this->any())->method('getPassword')->willReturn('password');
623
+        $this->hasher->expects($this->once())->method('hash')->with('password')->willReturn('passwordHashed');
624
+        $share->expects($this->once())->method('setPassword')->with('passwordHashed');
625
+
626
+        // The given password (but not the autogenerated password) should not be
627
+        // mailed to the receiver of the share because permanent passwords are not enforced.
628
+        $this->shareManager->expects($this->any())->method('shareApiLinkEnforcePassword')->willReturn(false);
629
+        $this->config->expects($this->once())->method('getSystemValue')->with('sharing.enable_mail_link_password_expiration')->willReturn(false);
630
+        $instance->expects($this->never())->method('autoGeneratePassword');
631
+
632
+        // A password is set but no password sent via talk has been requested
633
+        $instance->expects($this->once())->method('sendEmail')
634
+            ->with($share, ['[email protected]', '[email protected]', '[email protected]']);
635
+        $instance->expects($this->once())->method('sendPassword')->with($share, 'password');
636
+        $instance->expects($this->never())->method('sendPasswordToOwner');
637
+
638
+
639
+        $message = $this->createMock(IMessage::class);
640
+        $message->expects($this->never())->method('setTo');
641
+        $message->expects($this->exactly(2))->method('setBcc')->with(['[email protected]', '[email protected]', '[email protected]']);
642
+        $this->mailer->expects($this->exactly(2))->method('createMessage')->willReturn($message);
643
+
644
+        // Main email notification is sent as well as the password
645
+        // to recipients because the password is set.
646
+        $this->mailer->expects($this->exactly(2))->method('send');
647
+
648
+        $this->assertSame($expectedShare, $instance->create($share));
649
+        $instance->sendMailNotification($share);
650
+    }
651
+
652
+    public function testCreateFailed(): void {
653
+        $this->expectException(\Exception::class);
654
+
655
+        $this->share->expects($this->once())->method('getSharedWith')->willReturn('user1');
656
+        $node = $this->createMock('OCP\Files\Node');
657
+        $node->expects($this->any())->method('getName')->willReturn('fileName');
658
+        $this->share->expects($this->any())->method('getNode')->willReturn($node);
659
+
660
+        $instance = $this->getInstance(['getSharedWith', 'createMailShare', 'getRawShare', 'createShareObject']);
661
+
662
+        $instance->expects($this->once())->method('getSharedWith')->willReturn(['found']);
663
+        $instance->expects($this->never())->method('createMailShare');
664
+        $instance->expects($this->never())->method('getRawShare');
665
+        $instance->expects($this->never())->method('createShareObject');
666
+
667
+        $this->assertSame('shareObject',
668
+            $instance->create($this->share)
669
+        );
670
+    }
671
+
672
+    public function testCreateMailShare(): void {
673
+        $this->share->expects($this->any())->method('getToken')->willReturn('token');
674
+        $this->share->expects($this->once())->method('setToken')->with('token');
675
+        $this->share->expects($this->any())->method('getSharedBy')->willReturn('[email protected]');
676
+        $this->share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
677
+        $this->share->expects($this->any())->method('getNote')->willReturn('Check this!');
678
+        $this->share->expects($this->any())->method('getMailSend')->willReturn(true);
679
+
680
+        $node = $this->createMock('OCP\Files\Node');
681
+        $node->expects($this->any())->method('getName')->willReturn('fileName');
682
+        $this->share->expects($this->any())->method('getNode')->willReturn($node);
683
+
684
+        $instance = $this->getInstance(['generateToken', 'addShareToDB', 'sendMailNotification']);
685
+
686
+        $instance->expects($this->once())->method('generateToken')->willReturn('token');
687
+        $instance->expects($this->once())->method('addShareToDB')->willReturn(42);
688
+
689
+        // The manager handle the mail sending
690
+        $instance->expects($this->never())->method('sendMailNotification');
691
+
692
+        $this->assertSame(42,
693
+            $this->invokePrivate($instance, 'createMailShare', [$this->share])
694
+        );
695
+    }
696
+
697
+    public function testGenerateToken(): void {
698
+        $instance = $this->getInstance();
699
+
700
+        $this->secureRandom->expects($this->once())->method('generate')->willReturn('token');
701
+
702
+        $this->assertSame('token',
703
+            $this->invokePrivate($instance, 'generateToken')
704
+        );
705
+    }
706
+
707
+    public function testAddShareToDB(): void {
708
+        $itemSource = 11;
709
+        $itemType = 'file';
710
+        $shareWith = '[email protected]';
711
+        $sharedBy = 'user1';
712
+        $uidOwner = 'user2';
713
+        $permissions = 1;
714
+        $token = 'token';
715
+        $password = 'password';
716
+        $sendPasswordByTalk = true;
717
+        $hideDownload = true;
718
+        $label = 'label';
719
+        $expiration = new \DateTime();
720
+        $passwordExpirationTime = new \DateTime();
721
+
722
+
723
+        $instance = $this->getInstance();
724
+        $id = $this->invokePrivate(
725
+            $instance,
726
+            'addShareToDB',
727
+            [
728
+                $itemSource,
729
+                $itemType,
730
+                $shareWith,
731
+                $sharedBy,
732
+                $uidOwner,
733
+                $permissions,
734
+                $token,
735
+                $password,
736
+                $passwordExpirationTime,
737
+                $sendPasswordByTalk,
738
+                $hideDownload,
739
+                $label,
740
+                $expiration
741
+            ]
742
+        );
743
+
744
+        $qb = $this->connection->getQueryBuilder();
745
+        $qb->select('*')
746
+            ->from('share')
747
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
748
+
749
+        $qResult = $qb->execute();
750
+        $result = $qResult->fetchAll();
751
+        $qResult->closeCursor();
752
+
753
+        $this->assertSame(1, count($result));
754
+
755
+        $this->assertSame($itemSource, (int)$result[0]['item_source']);
756
+        $this->assertSame($itemType, $result[0]['item_type']);
757
+        $this->assertSame($shareWith, $result[0]['share_with']);
758
+        $this->assertSame($sharedBy, $result[0]['uid_initiator']);
759
+        $this->assertSame($uidOwner, $result[0]['uid_owner']);
760
+        $this->assertSame($permissions, (int)$result[0]['permissions']);
761
+        $this->assertSame($token, $result[0]['token']);
762
+        $this->assertSame($password, $result[0]['password']);
763
+        $this->assertSame($passwordExpirationTime->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['password_expiration_time'])->getTimestamp());
764
+        $this->assertSame($sendPasswordByTalk, (bool)$result[0]['password_by_talk']);
765
+        $this->assertSame($hideDownload, (bool)$result[0]['hide_download']);
766
+        $this->assertSame($label, $result[0]['label']);
767
+        $this->assertSame($expiration->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['expiration'])->getTimestamp());
768
+    }
769
+
770
+    public function testUpdate(): void {
771
+        $itemSource = 11;
772
+        $itemType = 'file';
773
+        $shareWith = '[email protected]';
774
+        $sharedBy = 'user1';
775
+        $uidOwner = 'user2';
776
+        $permissions = 1;
777
+        $token = 'token';
778
+        $note = 'personal note';
779
+
780
+
781
+        $instance = $this->getInstance();
782
+
783
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note);
784
+
785
+        $this->share->expects($this->once())->method('getPermissions')->willReturn($permissions + 1);
786
+        $this->share->expects($this->once())->method('getShareOwner')->willReturn($uidOwner);
787
+        $this->share->expects($this->once())->method('getSharedBy')->willReturn($sharedBy);
788
+        $this->share->expects($this->any())->method('getNote')->willReturn($note);
789
+        $this->share->expects($this->atLeastOnce())->method('getId')->willReturn($id);
790
+        $this->share->expects($this->atLeastOnce())->method('getNodeId')->willReturn($itemSource);
791
+        $this->share->expects($this->once())->method('getSharedWith')->willReturn($shareWith);
792
+
793
+        $this->assertSame($this->share,
794
+            $instance->update($this->share)
795
+        );
796
+
797
+        $qb = $this->connection->getQueryBuilder();
798
+        $qb->select('*')
799
+            ->from('share')
800
+            ->where($qb->expr()->eq('id', $qb->createNamedParameter($id)));
801
+
802
+        $qResult = $qb->execute();
803
+        $result = $qResult->fetchAll();
804
+        $qResult->closeCursor();
805
+
806
+        $this->assertSame(1, count($result));
807
+
808
+        $this->assertSame($itemSource, (int)$result[0]['item_source']);
809
+        $this->assertSame($itemType, $result[0]['item_type']);
810
+        $this->assertSame($shareWith, $result[0]['share_with']);
811
+        $this->assertSame($sharedBy, $result[0]['uid_initiator']);
812
+        $this->assertSame($uidOwner, $result[0]['uid_owner']);
813
+        $this->assertSame($permissions + 1, (int)$result[0]['permissions']);
814
+        $this->assertSame($token, $result[0]['token']);
815
+        $this->assertSame($note, $result[0]['note']);
816
+    }
817
+
818
+    public static function dataUpdateSendPassword(): array {
819
+        return [
820
+            ['password', 'hashed', 'hashed new', false, false, true],
821
+            ['', 'hashed', 'hashed new', false, false, false],
822
+            [null, 'hashed', 'hashed new', false, false, false],
823
+            ['password', 'hashed', 'hashed', false, false, false],
824
+            ['password', 'hashed', 'hashed new', false, true, false],
825
+            ['password', 'hashed', 'hashed new', true, false, true],
826
+            ['password', 'hashed', 'hashed', true, false, true],
827
+        ];
828
+    }
829
+
830
+    #[\PHPUnit\Framework\Attributes\DataProvider('dataUpdateSendPassword')]
831
+    public function testUpdateSendPassword(?string $plainTextPassword, string $originalPassword, string $newPassword, bool $originalSendPasswordByTalk, bool $newSendPasswordByTalk, bool $sendMail): void {
832
+        $node = $this->createMock(File::class);
833
+        $node->expects($this->any())->method('getName')->willReturn('filename');
834
+
835
+        $this->settingsManager->method('sendPasswordByMail')->willReturn(true);
836
+
837
+        $originalShare = $this->createMock(IShare::class);
838
+        $originalShare->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
839
+        $originalShare->expects($this->any())->method('getNode')->willReturn($node);
840
+        $originalShare->expects($this->any())->method('getId')->willReturn(42);
841
+        $originalShare->expects($this->any())->method('getPassword')->willReturn($originalPassword);
842
+        $originalShare->expects($this->any())->method('getSendPasswordByTalk')->willReturn($originalSendPasswordByTalk);
843
+
844
+        $share = $this->createMock(IShare::class);
845
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
846
+        $share->expects($this->any())->method('getNode')->willReturn($node);
847
+        $share->expects($this->any())->method('getId')->willReturn(42);
848
+        $share->expects($this->any())->method('getPassword')->willReturn($newPassword);
849
+        $share->expects($this->any())->method('getSendPasswordByTalk')->willReturn($newSendPasswordByTalk);
850
+
851
+        if ($sendMail) {
852
+            $this->mailer->expects($this->once())->method('createEMailTemplate')->with('sharebymail.RecipientPasswordNotification', [
853
+                'filename' => 'filename',
854
+                'password' => $plainTextPassword,
855
+                'initiator' => null,
856
+                'initiatorEmail' => null,
857
+                'shareWith' => '[email protected]',
858
+            ]);
859
+            $this->mailer->expects($this->once())->method('send');
860
+        } else {
861
+            $this->mailer->expects($this->never())->method('send');
862
+        }
863
+
864
+        $instance = $this->getInstance(['getShareById', 'createPasswordSendActivity']);
865
+        $instance->expects($this->once())->method('getShareById')->willReturn($originalShare);
866
+
867
+        $this->assertSame($share,
868
+            $instance->update($share, $plainTextPassword)
869
+        );
870
+    }
871
+
872
+    public function testDelete(): void {
873
+        $instance = $this->getInstance(['removeShareFromTable', 'createShareActivity']);
874
+        $this->share->expects($this->once())->method('getId')->willReturn(42);
875
+        $instance->expects($this->once())->method('removeShareFromTable')->with(42);
876
+        $instance->expects($this->once())->method('createShareActivity')->with($this->share, 'unshare');
877
+        $instance->delete($this->share);
878
+    }
879
+
880
+    public function testGetShareById(): void {
881
+        $instance = $this->getInstance(['createShareObject']);
882
+
883
+        $itemSource = 11;
884
+        $itemType = 'file';
885
+        $shareWith = '[email protected]';
886
+        $sharedBy = 'user1';
887
+        $uidOwner = 'user2';
888
+        $permissions = 1;
889
+        $token = 'token';
890
+
891
+        $this->createDummyShare($itemType, $itemSource, $shareWith, 'user1wrong', 'user2wrong', $permissions, $token);
892
+        $id2 = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
893
+
894
+        $instance->expects($this->once())->method('createShareObject')
895
+            ->willReturnCallback(
896
+                function ($data) use ($uidOwner, $sharedBy, $id2) {
897
+                    $this->assertSame($uidOwner, $data['uid_owner']);
898
+                    $this->assertSame($sharedBy, $data['uid_initiator']);
899
+                    $this->assertSame($id2, (int)$data['id']);
900
+                    return $this->share;
901
+                }
902
+            );
903
+
904
+        $result = $instance->getShareById($id2);
905
+
906
+        $this->assertInstanceOf('OCP\Share\IShare', $result);
907
+    }
908
+
909
+
910
+    public function testGetShareByIdFailed(): void {
911
+        $this->expectException(ShareNotFound::class);
912
+
913
+        $instance = $this->getInstance(['createShareObject']);
914
+
915
+        $itemSource = 11;
916
+        $itemType = 'file';
917
+        $shareWith = '[email protected]';
918
+        $sharedBy = 'user1';
919
+        $uidOwner = 'user2';
920
+        $permissions = 1;
921
+        $token = 'token';
922
+
923
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
924
+
925
+        $instance->getShareById($id + 1);
926
+    }
927
+
928
+    public function testGetShareByPath(): void {
929
+        $itemSource = 11;
930
+        $itemType = 'file';
931
+        $shareWith = '[email protected]';
932
+        $sharedBy = 'user1';
933
+        $uidOwner = 'user2';
934
+        $permissions = 1;
935
+        $token = 'token';
936
+
937
+        $node = $this->createMock(Node::class);
938
+        $node->expects($this->once())->method('getId')->willReturn($itemSource);
939
+
940
+
941
+        $instance = $this->getInstance(['createShareObject']);
942
+
943
+        $this->createDummyShare($itemType, 111, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
944
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
945
+
946
+        $instance->expects($this->once())->method('createShareObject')
947
+            ->willReturnCallback(
948
+                function ($data) use ($uidOwner, $sharedBy, $id) {
949
+                    $this->assertSame($uidOwner, $data['uid_owner']);
950
+                    $this->assertSame($sharedBy, $data['uid_initiator']);
951
+                    $this->assertSame($id, (int)$data['id']);
952
+                    return $this->share;
953
+                }
954
+            );
955
+
956
+        $result = $instance->getSharesByPath($node);
957
+
958
+        $this->assertTrue(is_array($result));
959
+        $this->assertSame(1, count($result));
960
+        $this->assertInstanceOf('OCP\Share\IShare', $result[0]);
961
+    }
962
+
963
+    public function testGetShareByToken(): void {
964
+        $itemSource = 11;
965
+        $itemType = 'file';
966
+        $shareWith = '[email protected]';
967
+        $sharedBy = 'user1';
968
+        $uidOwner = 'user2';
969
+        $permissions = 1;
970
+        $token = 'token';
971 971
 
972
-		$instance = $this->getInstance(['createShareObject']);
972
+        $instance = $this->getInstance(['createShareObject']);
973 973
 
974
-		$idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
975
-		$idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, '', IShare::TYPE_LINK);
974
+        $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
975
+        $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, '', IShare::TYPE_LINK);
976 976
 
977
-		$this->assertTrue($idMail !== $idPublic);
977
+        $this->assertTrue($idMail !== $idPublic);
978 978
 
979
-		$instance->expects($this->once())->method('createShareObject')
980
-			->willReturnCallback(
981
-				function ($data) use ($idMail) {
982
-					$this->assertSame($idMail, (int)$data['id']);
983
-					return $this->share;
984
-				}
985
-			);
979
+        $instance->expects($this->once())->method('createShareObject')
980
+            ->willReturnCallback(
981
+                function ($data) use ($idMail) {
982
+                    $this->assertSame($idMail, (int)$data['id']);
983
+                    return $this->share;
984
+                }
985
+            );
986 986
 
987
-		$result = $instance->getShareByToken('token');
987
+        $result = $instance->getShareByToken('token');
988 988
 
989
-		$this->assertInstanceOf('OCP\Share\IShare', $result);
990
-	}
989
+        $this->assertInstanceOf('OCP\Share\IShare', $result);
990
+    }
991 991
 
992 992
 
993
-	public function testGetShareByTokenFailed(): void {
994
-		$this->expectException(ShareNotFound::class);
993
+    public function testGetShareByTokenFailed(): void {
994
+        $this->expectException(ShareNotFound::class);
995 995
 
996 996
 
997
-		$itemSource = 11;
998
-		$itemType = 'file';
999
-		$shareWith = '[email protected]';
1000
-		$sharedBy = 'user1';
1001
-		$uidOwner = 'user2';
1002
-		$permissions = 1;
1003
-		$token = 'token';
997
+        $itemSource = 11;
998
+        $itemType = 'file';
999
+        $shareWith = '[email protected]';
1000
+        $sharedBy = 'user1';
1001
+        $uidOwner = 'user2';
1002
+        $permissions = 1;
1003
+        $token = 'token';
1004 1004
 
1005
-		$instance = $this->getInstance(['createShareObject']);
1005
+        $instance = $this->getInstance(['createShareObject']);
1006 1006
 
1007
-		$idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1008
-		$idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, 'token2', '', IShare::TYPE_LINK);
1007
+        $idMail = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1008
+        $idPublic = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, 'token2', '', IShare::TYPE_LINK);
1009 1009
 
1010
-		$this->assertTrue($idMail !== $idPublic);
1010
+        $this->assertTrue($idMail !== $idPublic);
1011 1011
 
1012
-		$this->assertInstanceOf('OCP\Share\IShare',
1013
-			$instance->getShareByToken('token2')
1014
-		);
1015
-	}
1012
+        $this->assertInstanceOf('OCP\Share\IShare',
1013
+            $instance->getShareByToken('token2')
1014
+        );
1015
+    }
1016 1016
 
1017
-	public function testRemoveShareFromTable(): void {
1018
-		$itemSource = 11;
1019
-		$itemType = 'file';
1020
-		$shareWith = '[email protected]';
1021
-		$sharedBy = 'user1';
1022
-		$uidOwner = 'user2';
1023
-		$permissions = 1;
1024
-		$token = 'token';
1017
+    public function testRemoveShareFromTable(): void {
1018
+        $itemSource = 11;
1019
+        $itemType = 'file';
1020
+        $shareWith = '[email protected]';
1021
+        $sharedBy = 'user1';
1022
+        $uidOwner = 'user2';
1023
+        $permissions = 1;
1024
+        $token = 'token';
1025 1025
 
1026
-		$instance = $this->getInstance();
1026
+        $instance = $this->getInstance();
1027 1027
 
1028
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1028
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1029 1029
 
1030
-		$query = $this->connection->getQueryBuilder();
1031
-		$query->select('*')->from('share')
1032
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
1030
+        $query = $this->connection->getQueryBuilder();
1031
+        $query->select('*')->from('share')
1032
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
1033 1033
 
1034
-		$result = $query->execute();
1035
-		$before = $result->fetchAll();
1036
-		$result->closeCursor();
1034
+        $result = $query->execute();
1035
+        $before = $result->fetchAll();
1036
+        $result->closeCursor();
1037 1037
 
1038
-		$this->assertTrue(is_array($before));
1039
-		$this->assertSame(1, count($before));
1038
+        $this->assertTrue(is_array($before));
1039
+        $this->assertSame(1, count($before));
1040 1040
 
1041
-		$this->invokePrivate($instance, 'removeShareFromTable', [$id]);
1041
+        $this->invokePrivate($instance, 'removeShareFromTable', [$id]);
1042 1042
 
1043
-		$query = $this->connection->getQueryBuilder();
1044
-		$query->select('*')->from('share')
1045
-			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
1043
+        $query = $this->connection->getQueryBuilder();
1044
+        $query->select('*')->from('share')
1045
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id)));
1046 1046
 
1047
-		$result = $query->execute();
1048
-		$after = $result->fetchAll();
1049
-		$result->closeCursor();
1047
+        $result = $query->execute();
1048
+        $after = $result->fetchAll();
1049
+        $result->closeCursor();
1050 1050
 
1051
-		$this->assertTrue(is_array($after));
1052
-		$this->assertEmpty($after);
1053
-	}
1051
+        $this->assertTrue(is_array($after));
1052
+        $this->assertEmpty($after);
1053
+    }
1054 1054
 
1055
-	public function testUserDeleted(): void {
1056
-		$itemSource = 11;
1057
-		$itemType = 'file';
1058
-		$shareWith = '[email protected]';
1059
-		$sharedBy = 'user1';
1060
-		$uidOwner = 'user2';
1061
-		$permissions = 1;
1062
-		$token = 'token';
1055
+    public function testUserDeleted(): void {
1056
+        $itemSource = 11;
1057
+        $itemType = 'file';
1058
+        $shareWith = '[email protected]';
1059
+        $sharedBy = 'user1';
1060
+        $uidOwner = 'user2';
1061
+        $permissions = 1;
1062
+        $token = 'token';
1063 1063
 
1064
-		$this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1065
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, 'user2Wrong', $permissions, $token);
1064
+        $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1065
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, 'user2Wrong', $permissions, $token);
1066 1066
 
1067
-		$query = $this->connection->getQueryBuilder();
1068
-		$query->select('*')->from('share');
1067
+        $query = $this->connection->getQueryBuilder();
1068
+        $query->select('*')->from('share');
1069 1069
 
1070
-		$result = $query->execute();
1071
-		$before = $result->fetchAll();
1072
-		$result->closeCursor();
1070
+        $result = $query->execute();
1071
+        $before = $result->fetchAll();
1072
+        $result->closeCursor();
1073 1073
 
1074
-		$this->assertTrue(is_array($before));
1075
-		$this->assertSame(2, count($before));
1074
+        $this->assertTrue(is_array($before));
1075
+        $this->assertSame(2, count($before));
1076 1076
 
1077 1077
 
1078
-		$instance = $this->getInstance();
1078
+        $instance = $this->getInstance();
1079 1079
 
1080
-		$instance->userDeleted($uidOwner, IShare::TYPE_EMAIL);
1080
+        $instance->userDeleted($uidOwner, IShare::TYPE_EMAIL);
1081 1081
 
1082
-		$query = $this->connection->getQueryBuilder();
1083
-		$query->select('*')->from('share');
1082
+        $query = $this->connection->getQueryBuilder();
1083
+        $query->select('*')->from('share');
1084 1084
 
1085
-		$result = $query->execute();
1086
-		$after = $result->fetchAll();
1087
-		$result->closeCursor();
1085
+        $result = $query->execute();
1086
+        $after = $result->fetchAll();
1087
+        $result->closeCursor();
1088 1088
 
1089
-		$this->assertTrue(is_array($after));
1090
-		$this->assertSame(1, count($after));
1091
-		$this->assertSame($id, (int)$after[0]['id']);
1092
-	}
1089
+        $this->assertTrue(is_array($after));
1090
+        $this->assertSame(1, count($after));
1091
+        $this->assertSame($id, (int)$after[0]['id']);
1092
+    }
1093 1093
 
1094
-	public function testGetRawShare(): void {
1095
-		$itemSource = 11;
1096
-		$itemType = 'file';
1097
-		$shareWith = '[email protected]';
1098
-		$sharedBy = 'user1';
1099
-		$uidOwner = 'user2';
1100
-		$permissions = 1;
1101
-		$token = 'token';
1094
+    public function testGetRawShare(): void {
1095
+        $itemSource = 11;
1096
+        $itemType = 'file';
1097
+        $shareWith = '[email protected]';
1098
+        $sharedBy = 'user1';
1099
+        $uidOwner = 'user2';
1100
+        $permissions = 1;
1101
+        $token = 'token';
1102 1102
 
1103
-		$instance = $this->getInstance();
1103
+        $instance = $this->getInstance();
1104 1104
 
1105
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1105
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1106 1106
 
1107
-		$result = $this->invokePrivate($instance, 'getRawShare', [$id]);
1108
-
1109
-		$this->assertTrue(is_array($result));
1110
-		$this->assertSame($itemSource, (int)$result['item_source']);
1111
-		$this->assertSame($itemType, $result['item_type']);
1112
-		$this->assertSame($shareWith, $result['share_with']);
1113
-		$this->assertSame($sharedBy, $result['uid_initiator']);
1114
-		$this->assertSame($uidOwner, $result['uid_owner']);
1115
-		$this->assertSame($permissions, (int)$result['permissions']);
1116
-		$this->assertSame($token, $result['token']);
1117
-	}
1107
+        $result = $this->invokePrivate($instance, 'getRawShare', [$id]);
1108
+
1109
+        $this->assertTrue(is_array($result));
1110
+        $this->assertSame($itemSource, (int)$result['item_source']);
1111
+        $this->assertSame($itemType, $result['item_type']);
1112
+        $this->assertSame($shareWith, $result['share_with']);
1113
+        $this->assertSame($sharedBy, $result['uid_initiator']);
1114
+        $this->assertSame($uidOwner, $result['uid_owner']);
1115
+        $this->assertSame($permissions, (int)$result['permissions']);
1116
+        $this->assertSame($token, $result['token']);
1117
+    }
1118 1118
 
1119 1119
 
1120
-	public function testGetRawShareFailed(): void {
1121
-		$this->expectException(ShareNotFound::class);
1120
+    public function testGetRawShareFailed(): void {
1121
+        $this->expectException(ShareNotFound::class);
1122 1122
 
1123
-		$itemSource = 11;
1124
-		$itemType = 'file';
1125
-		$shareWith = '[email protected]';
1126
-		$sharedBy = 'user1';
1127
-		$uidOwner = 'user2';
1128
-		$permissions = 1;
1129
-		$token = 'token';
1130
-
1131
-		$instance = $this->getInstance();
1123
+        $itemSource = 11;
1124
+        $itemType = 'file';
1125
+        $shareWith = '[email protected]';
1126
+        $sharedBy = 'user1';
1127
+        $uidOwner = 'user2';
1128
+        $permissions = 1;
1129
+        $token = 'token';
1130
+
1131
+        $instance = $this->getInstance();
1132 1132
 
1133
-		$id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1134
-
1135
-		$this->invokePrivate($instance, 'getRawShare', [$id + 1]);
1136
-	}
1133
+        $id = $this->createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token);
1134
+
1135
+        $this->invokePrivate($instance, 'getRawShare', [$id + 1]);
1136
+    }
1137 1137
 
1138
-	private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note = '', $shareType = IShare::TYPE_EMAIL) {
1139
-		$qb = $this->connection->getQueryBuilder();
1140
-		$qb->insert('share')
1141
-			->setValue('share_type', $qb->createNamedParameter($shareType))
1142
-			->setValue('item_type', $qb->createNamedParameter($itemType))
1143
-			->setValue('item_source', $qb->createNamedParameter($itemSource))
1144
-			->setValue('file_source', $qb->createNamedParameter($itemSource))
1145
-			->setValue('share_with', $qb->createNamedParameter($shareWith))
1146
-			->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
1147
-			->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
1148
-			->setValue('permissions', $qb->createNamedParameter($permissions))
1149
-			->setValue('token', $qb->createNamedParameter($token))
1150
-			->setValue('note', $qb->createNamedParameter($note))
1151
-			->setValue('stime', $qb->createNamedParameter(time()));
1152
-
1153
-		/*
1138
+    private function createDummyShare($itemType, $itemSource, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $note = '', $shareType = IShare::TYPE_EMAIL) {
1139
+        $qb = $this->connection->getQueryBuilder();
1140
+        $qb->insert('share')
1141
+            ->setValue('share_type', $qb->createNamedParameter($shareType))
1142
+            ->setValue('item_type', $qb->createNamedParameter($itemType))
1143
+            ->setValue('item_source', $qb->createNamedParameter($itemSource))
1144
+            ->setValue('file_source', $qb->createNamedParameter($itemSource))
1145
+            ->setValue('share_with', $qb->createNamedParameter($shareWith))
1146
+            ->setValue('uid_owner', $qb->createNamedParameter($uidOwner))
1147
+            ->setValue('uid_initiator', $qb->createNamedParameter($sharedBy))
1148
+            ->setValue('permissions', $qb->createNamedParameter($permissions))
1149
+            ->setValue('token', $qb->createNamedParameter($token))
1150
+            ->setValue('note', $qb->createNamedParameter($note))
1151
+            ->setValue('stime', $qb->createNamedParameter(time()));
1152
+
1153
+        /*
1154 1154
 		 * Added to fix https://github.com/owncloud/core/issues/22215
1155 1155
 		 * Can be removed once we get rid of ajax/share.php
1156 1156
 		 */
1157
-		$qb->setValue('file_target', $qb->createNamedParameter(''));
1158
-
1159
-		$qb->execute();
1160
-		$id = $qb->getLastInsertId();
1161
-
1162
-		return (int)$id;
1163
-	}
1164
-
1165
-	public function testGetSharesInFolder(): void {
1166
-		$userManager = Server::get(IUserManager::class);
1167
-		$rootFolder = Server::get(IRootFolder::class);
1168
-
1169
-		$this->shareManager->expects($this->any())
1170
-			->method('newShare')
1171
-			->willReturn(new Share($rootFolder, $userManager));
1172
-
1173
-		$provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1174
-
1175
-		$u1 = $userManager->createUser('testFed', md5((string)time()));
1176
-		$u2 = $userManager->createUser('testFed2', md5((string)time()));
1177
-
1178
-		$folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1179
-		$file1 = $folder1->newFile('bar1');
1180
-		$file2 = $folder1->newFile('bar2');
1181
-
1182
-		$share1 = $this->shareManager->newShare();
1183
-		$share1->setSharedWith('[email protected]')
1184
-			->setSharedBy($u1->getUID())
1185
-			->setShareOwner($u1->getUID())
1186
-			->setPermissions(Constants::PERMISSION_READ)
1187
-			->setNode($file1);
1188
-		$provider->create($share1);
1189
-
1190
-		$share2 = $this->shareManager->newShare();
1191
-		$share2->setSharedWith('[email protected]')
1192
-			->setSharedBy($u2->getUID())
1193
-			->setShareOwner($u1->getUID())
1194
-			->setPermissions(Constants::PERMISSION_READ)
1195
-			->setNode($file2);
1196
-		$provider->create($share2);
1197
-
1198
-		$result = $provider->getSharesInFolder($u1->getUID(), $folder1, false);
1199
-		$this->assertCount(1, $result);
1200
-		$this->assertCount(1, $result[$file1->getId()]);
1201
-
1202
-		$result = $provider->getSharesInFolder($u1->getUID(), $folder1, true);
1203
-		$this->assertCount(2, $result);
1204
-		$this->assertCount(1, $result[$file1->getId()]);
1205
-		$this->assertCount(1, $result[$file2->getId()]);
1206
-
1207
-		$u1->delete();
1208
-		$u2->delete();
1209
-	}
1210
-
1211
-	public function testGetAccessList(): void {
1212
-		$userManager = Server::get(IUserManager::class);
1213
-		$rootFolder = Server::get(IRootFolder::class);
1214
-
1215
-		$this->shareManager->expects($this->any())
1216
-			->method('newShare')
1217
-			->willReturn(new Share($rootFolder, $userManager));
1218
-
1219
-		$provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1220
-
1221
-		$u1 = $userManager->createUser('testFed', md5((string)time()));
1222
-		$u2 = $userManager->createUser('testFed2', md5((string)time()));
1223
-
1224
-		$folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1225
-
1226
-		$accessList = $provider->getAccessList([$folder], true);
1227
-		$this->assertArrayHasKey('public', $accessList);
1228
-		$this->assertFalse($accessList['public']);
1229
-		$accessList = $provider->getAccessList([$folder], false);
1230
-		$this->assertArrayHasKey('public', $accessList);
1231
-		$this->assertFalse($accessList['public']);
1232
-
1233
-		$share1 = $this->shareManager->newShare();
1234
-		$share1->setSharedWith('[email protected]')
1235
-			->setSharedBy($u1->getUID())
1236
-			->setShareOwner($u1->getUID())
1237
-			->setPermissions(Constants::PERMISSION_READ)
1238
-			->setNode($folder);
1239
-		$share1 = $provider->create($share1);
1240
-
1241
-		$share2 = $this->shareManager->newShare();
1242
-		$share2->setSharedWith('[email protected]')
1243
-			->setSharedBy($u2->getUID())
1244
-			->setShareOwner($u1->getUID())
1245
-			->setPermissions(Constants::PERMISSION_READ)
1246
-			->setNode($folder);
1247
-		$share2 = $provider->create($share2);
1248
-
1249
-		$accessList = $provider->getAccessList([$folder], true);
1250
-		$this->assertArrayHasKey('public', $accessList);
1251
-		$this->assertTrue($accessList['public']);
1252
-		$accessList = $provider->getAccessList([$folder], false);
1253
-		$this->assertArrayHasKey('public', $accessList);
1254
-		$this->assertTrue($accessList['public']);
1255
-
1256
-		$provider->delete($share2);
1257
-
1258
-		$accessList = $provider->getAccessList([$folder], true);
1259
-		$this->assertArrayHasKey('public', $accessList);
1260
-		$this->assertTrue($accessList['public']);
1261
-		$accessList = $provider->getAccessList([$folder], false);
1262
-		$this->assertArrayHasKey('public', $accessList);
1263
-		$this->assertTrue($accessList['public']);
1264
-
1265
-		$provider->delete($share1);
1266
-
1267
-		$accessList = $provider->getAccessList([$folder], true);
1268
-		$this->assertArrayHasKey('public', $accessList);
1269
-		$this->assertFalse($accessList['public']);
1270
-		$accessList = $provider->getAccessList([$folder], false);
1271
-		$this->assertArrayHasKey('public', $accessList);
1272
-		$this->assertFalse($accessList['public']);
1273
-
1274
-		$u1->delete();
1275
-		$u2->delete();
1276
-	}
1277
-
1278
-	public function testSendMailNotificationWithSameUserAndUserEmail(): void {
1279
-		$provider = $this->getInstance();
1280
-		$user = $this->createMock(IUser::class);
1281
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1282
-		$this->userManager
1283
-			->expects($this->once())
1284
-			->method('get')
1285
-			->with('OwnerUser')
1286
-			->willReturn($user);
1287
-		$user
1288
-			->expects($this->once())
1289
-			->method('getDisplayName')
1290
-			->willReturn('Mrs. Owner User');
1291
-		$message = $this->createMock(Message::class);
1292
-		$this->mailer
1293
-			->expects($this->once())
1294
-			->method('createMessage')
1295
-			->willReturn($message);
1296
-		$template = $this->createMock(IEMailTemplate::class);
1297
-		$this->mailer
1298
-			->expects($this->once())
1299
-			->method('createEMailTemplate')
1300
-			->willReturn($template);
1301
-		$template
1302
-			->expects($this->once())
1303
-			->method('addHeader');
1304
-		$template
1305
-			->expects($this->once())
1306
-			->method('addHeading')
1307
-			->with('Mrs. Owner User shared file.txt with you');
1308
-		$template
1309
-			->expects($this->once())
1310
-			->method('addBodyButton')
1311
-			->with(
1312
-				'Open file.txt',
1313
-				'https://example.com/file.txt'
1314
-			);
1315
-		$message
1316
-			->expects($this->once())
1317
-			->method('setTo')
1318
-			->with(['[email protected]']);
1319
-		$this->defaults
1320
-			->expects($this->once())
1321
-			->method('getName')
1322
-			->willReturn('UnitTestCloud');
1323
-		$message
1324
-			->expects($this->once())
1325
-			->method('setFrom')
1326
-			->with([
1327
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1328
-			]);
1329
-		$user
1330
-			->expects($this->once())
1331
-			->method('getEMailAddress')
1332
-			->willReturn('[email protected]');
1333
-		$message
1334
-			->expects($this->once())
1335
-			->method('setReplyTo')
1336
-			->with(['[email protected]' => 'Mrs. Owner User']);
1337
-		$this->defaults
1338
-			->expects($this->exactly(2))
1339
-			->method('getSlogan')
1340
-			->willReturn('Testing like 1990');
1341
-		$template
1342
-			->expects($this->once())
1343
-			->method('addFooter')
1344
-			->with('UnitTestCloud - Testing like 1990');
1345
-		$template
1346
-			->expects($this->once())
1347
-			->method('setSubject')
1348
-			->with('Mrs. Owner User shared file.txt with you');
1349
-		$message
1350
-			->expects($this->once())
1351
-			->method('useTemplate')
1352
-			->with($template);
1353
-
1354
-		$this->mailer
1355
-			->expects($this->once())
1356
-			->method('send')
1357
-			->with($message);
1358
-
1359
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1360
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1361
-			->willReturn('https://example.com/file.txt');
1362
-
1363
-		$node = $this->createMock(File::class);
1364
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1365
-
1366
-		$share = $this->createMock(IShare::class);
1367
-		$share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1368
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1369
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1370
-		$share->expects($this->any())->method('getId')->willReturn(42);
1371
-		$share->expects($this->any())->method('getNote')->willReturn('');
1372
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1373
-
1374
-		self::invokePrivate(
1375
-			$provider,
1376
-			'sendMailNotification',
1377
-			[$share]
1378
-		);
1379
-	}
1380
-
1381
-	public function testSendMailNotificationWithSameUserAndUserEmailAndNote(): void {
1382
-		$provider = $this->getInstance();
1383
-		$user = $this->createMock(IUser::class);
1384
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1385
-		$this->userManager
1386
-			->expects($this->once())
1387
-			->method('get')
1388
-			->with('OwnerUser')
1389
-			->willReturn($user);
1390
-		$user
1391
-			->expects($this->once())
1392
-			->method('getDisplayName')
1393
-			->willReturn('Mrs. Owner User');
1394
-		$message = $this->createMock(Message::class);
1395
-		$this->mailer
1396
-			->expects($this->once())
1397
-			->method('createMessage')
1398
-			->willReturn($message);
1399
-		$template = $this->createMock(IEMailTemplate::class);
1400
-		$this->mailer
1401
-			->expects($this->once())
1402
-			->method('createEMailTemplate')
1403
-			->willReturn($template);
1404
-		$template
1405
-			->expects($this->once())
1406
-			->method('addHeader');
1407
-		$template
1408
-			->expects($this->once())
1409
-			->method('addHeading')
1410
-			->with('Mrs. Owner User shared file.txt with you');
1411
-
1412
-		$this->urlGenerator->expects($this->once())->method('imagePath')
1413
-			->with('core', 'caldav/description.png')
1414
-			->willReturn('core/img/caldav/description.png');
1415
-		$this->urlGenerator->expects($this->once())->method('getAbsoluteURL')
1416
-			->with('core/img/caldav/description.png')
1417
-			->willReturn('https://example.com/core/img/caldav/description.png');
1418
-		$template
1419
-			->expects($this->once())
1420
-			->method('addBodyListItem')
1421
-			->with(
1422
-				'This is a note to the recipient',
1423
-				'Note:',
1424
-				'https://example.com/core/img/caldav/description.png',
1425
-				'This is a note to the recipient'
1426
-			);
1427
-		$template
1428
-			->expects($this->once())
1429
-			->method('addBodyButton')
1430
-			->with(
1431
-				'Open file.txt',
1432
-				'https://example.com/file.txt'
1433
-			);
1434
-		$message
1435
-			->expects($this->once())
1436
-			->method('setTo')
1437
-			->with(['[email protected]']);
1438
-		$this->defaults
1439
-			->expects($this->once())
1440
-			->method('getName')
1441
-			->willReturn('UnitTestCloud');
1442
-		$message
1443
-			->expects($this->once())
1444
-			->method('setFrom')
1445
-			->with([
1446
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1447
-			]);
1448
-		$user
1449
-			->expects($this->once())
1450
-			->method('getEMailAddress')
1451
-			->willReturn('[email protected]');
1452
-		$message
1453
-			->expects($this->once())
1454
-			->method('setReplyTo')
1455
-			->with(['[email protected]' => 'Mrs. Owner User']);
1456
-		$this->defaults
1457
-			->expects($this->exactly(2))
1458
-			->method('getSlogan')
1459
-			->willReturn('Testing like 1990');
1460
-		$template
1461
-			->expects($this->once())
1462
-			->method('addFooter')
1463
-			->with('UnitTestCloud - Testing like 1990');
1464
-		$template
1465
-			->expects($this->once())
1466
-			->method('setSubject')
1467
-			->with('Mrs. Owner User shared file.txt with you');
1468
-		$message
1469
-			->expects($this->once())
1470
-			->method('useTemplate')
1471
-			->with($template);
1472
-
1473
-		$this->mailer
1474
-			->expects($this->once())
1475
-			->method('send')
1476
-			->with($message);
1477
-
1478
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1479
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1480
-			->willReturn('https://example.com/file.txt');
1481
-
1482
-		$node = $this->createMock(File::class);
1483
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1484
-
1485
-		$share = $this->createMock(IShare::class);
1486
-		$share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1487
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1488
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1489
-		$share->expects($this->any())->method('getId')->willReturn(42);
1490
-		$share->expects($this->any())->method('getNote')->willReturn('This is a note to the recipient');
1491
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1492
-
1493
-		self::invokePrivate(
1494
-			$provider,
1495
-			'sendMailNotification',
1496
-			[$share]
1497
-		);
1498
-	}
1499
-
1500
-	public function testSendMailNotificationWithSameUserAndUserEmailAndExpiration(): void {
1501
-		$provider = $this->getInstance();
1502
-		$user = $this->createMock(IUser::class);
1503
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1504
-		$this->userManager
1505
-			->expects($this->once())
1506
-			->method('get')
1507
-			->with('OwnerUser')
1508
-			->willReturn($user);
1509
-		$user
1510
-			->expects($this->once())
1511
-			->method('getDisplayName')
1512
-			->willReturn('Mrs. Owner User');
1513
-		$message = $this->createMock(Message::class);
1514
-		$this->mailer
1515
-			->expects($this->once())
1516
-			->method('createMessage')
1517
-			->willReturn($message);
1518
-		$template = $this->createMock(IEMailTemplate::class);
1519
-		$this->mailer
1520
-			->expects($this->once())
1521
-			->method('createEMailTemplate')
1522
-			->willReturn($template);
1523
-		$template
1524
-			->expects($this->once())
1525
-			->method('addHeader');
1526
-		$template
1527
-			->expects($this->once())
1528
-			->method('addHeading')
1529
-			->with('Mrs. Owner User shared file.txt with you');
1530
-
1531
-		$expiration = new DateTime('2001-01-01');
1532
-		$this->l->expects($this->once())
1533
-			->method('l')
1534
-			->with('date', $expiration, ['width' => 'medium'])
1535
-			->willReturn('2001-01-01');
1536
-		$this->urlGenerator->expects($this->once())->method('imagePath')
1537
-			->with('core', 'caldav/time.png')
1538
-			->willReturn('core/img/caldav/time.png');
1539
-		$this->urlGenerator->expects($this->once())->method('getAbsoluteURL')
1540
-			->with('core/img/caldav/time.png')
1541
-			->willReturn('https://example.com/core/img/caldav/time.png');
1542
-		$template
1543
-			->expects($this->once())
1544
-			->method('addBodyListItem')
1545
-			->with(
1546
-				'This share is valid until 2001-01-01 at midnight',
1547
-				'Expiration:',
1548
-				'https://example.com/core/img/caldav/time.png',
1549
-			);
1550
-
1551
-		$template
1552
-			->expects($this->once())
1553
-			->method('addBodyButton')
1554
-			->with(
1555
-				'Open file.txt',
1556
-				'https://example.com/file.txt'
1557
-			);
1558
-		$message
1559
-			->expects($this->once())
1560
-			->method('setTo')
1561
-			->with(['[email protected]']);
1562
-		$this->defaults
1563
-			->expects($this->once())
1564
-			->method('getName')
1565
-			->willReturn('UnitTestCloud');
1566
-		$message
1567
-			->expects($this->once())
1568
-			->method('setFrom')
1569
-			->with([
1570
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1571
-			]);
1572
-		$user
1573
-			->expects($this->once())
1574
-			->method('getEMailAddress')
1575
-			->willReturn('[email protected]');
1576
-		$message
1577
-			->expects($this->once())
1578
-			->method('setReplyTo')
1579
-			->with(['[email protected]' => 'Mrs. Owner User']);
1580
-		$this->defaults
1581
-			->expects($this->exactly(2))
1582
-			->method('getSlogan')
1583
-			->willReturn('Testing like 1990');
1584
-		$template
1585
-			->expects($this->once())
1586
-			->method('addFooter')
1587
-			->with('UnitTestCloud - Testing like 1990');
1588
-		$template
1589
-			->expects($this->once())
1590
-			->method('setSubject')
1591
-			->with('Mrs. Owner User shared file.txt with you');
1592
-		$message
1593
-			->expects($this->once())
1594
-			->method('useTemplate')
1595
-			->with($template);
1596
-
1597
-		$this->mailer
1598
-			->expects($this->once())
1599
-			->method('send')
1600
-			->with($message);
1601
-
1602
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1603
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1604
-			->willReturn('https://example.com/file.txt');
1605
-
1606
-		$node = $this->createMock(File::class);
1607
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1608
-
1609
-		$share = $this->createMock(IShare::class);
1610
-		$share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1611
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1612
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1613
-		$share->expects($this->any())->method('getId')->willReturn(42);
1614
-		$share->expects($this->any())->method('getNote')->willReturn('');
1615
-		$share->expects($this->any())->method('getExpirationDate')->willReturn($expiration);
1616
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1617
-
1618
-		self::invokePrivate(
1619
-			$provider,
1620
-			'sendMailNotification',
1621
-			[$share]
1622
-		);
1623
-	}
1624
-
1625
-	public function testSendMailNotificationWithDifferentUserAndNoUserEmail(): void {
1626
-		$provider = $this->getInstance();
1627
-		$initiatorUser = $this->createMock(IUser::class);
1628
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1629
-		$this->userManager
1630
-			->expects($this->once())
1631
-			->method('get')
1632
-			->with('InitiatorUser')
1633
-			->willReturn($initiatorUser);
1634
-		$initiatorUser
1635
-			->expects($this->once())
1636
-			->method('getDisplayName')
1637
-			->willReturn('Mr. Initiator User');
1638
-		$message = $this->createMock(Message::class);
1639
-		$this->mailer
1640
-			->expects($this->once())
1641
-			->method('createMessage')
1642
-			->willReturn($message);
1643
-		$template = $this->createMock(IEMailTemplate::class);
1644
-		$this->mailer
1645
-			->expects($this->once())
1646
-			->method('createEMailTemplate')
1647
-			->willReturn($template);
1648
-		$template
1649
-			->expects($this->once())
1650
-			->method('addHeader');
1651
-		$template
1652
-			->expects($this->once())
1653
-			->method('addHeading')
1654
-			->with('Mr. Initiator User shared file.txt with you');
1655
-		$template
1656
-			->expects($this->once())
1657
-			->method('addBodyButton')
1658
-			->with(
1659
-				'Open file.txt',
1660
-				'https://example.com/file.txt'
1661
-			);
1662
-		$message
1663
-			->expects($this->once())
1664
-			->method('setTo')
1665
-			->with(['[email protected]']);
1666
-		$this->defaults
1667
-			->expects($this->once())
1668
-			->method('getName')
1669
-			->willReturn('UnitTestCloud');
1670
-		$message
1671
-			->expects($this->once())
1672
-			->method('setFrom')
1673
-			->with([
1674
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'Mr. Initiator User via UnitTestCloud'
1675
-			]);
1676
-		$message
1677
-			->expects($this->never())
1678
-			->method('setReplyTo');
1679
-		$template
1680
-			->expects($this->once())
1681
-			->method('addFooter')
1682
-			->with('');
1683
-		$template
1684
-			->expects($this->once())
1685
-			->method('setSubject')
1686
-			->with('Mr. Initiator User shared file.txt with you');
1687
-		$message
1688
-			->expects($this->once())
1689
-			->method('useTemplate')
1690
-			->with($template);
1691
-
1692
-		$this->mailer
1693
-			->expects($this->once())
1694
-			->method('send')
1695
-			->with($message);
1696
-
1697
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1698
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1699
-			->willReturn('https://example.com/file.txt');
1700
-
1701
-		$node = $this->createMock(File::class);
1702
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1703
-
1704
-		$share = $this->createMock(IShare::class);
1705
-		$share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser');
1706
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1707
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1708
-		$share->expects($this->any())->method('getId')->willReturn(42);
1709
-		$share->expects($this->any())->method('getNote')->willReturn('');
1710
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1711
-
1712
-		self::invokePrivate(
1713
-			$provider,
1714
-			'sendMailNotification',
1715
-			[$share]
1716
-		);
1717
-	}
1718
-
1719
-	public function testSendMailNotificationWithSameUserAndUserEmailAndReplyToDesactivate(): void {
1720
-		$provider = $this->getInstance();
1721
-		$user = $this->createMock(IUser::class);
1722
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false);
1723
-		$this->userManager
1724
-			->expects($this->once())
1725
-			->method('get')
1726
-			->with('OwnerUser')
1727
-			->willReturn($user);
1728
-		$user
1729
-			->expects($this->once())
1730
-			->method('getDisplayName')
1731
-			->willReturn('Mrs. Owner User');
1732
-		$message = $this->createMock(Message::class);
1733
-		$this->mailer
1734
-			->expects($this->once())
1735
-			->method('createMessage')
1736
-			->willReturn($message);
1737
-		$template = $this->createMock(IEMailTemplate::class);
1738
-		$this->mailer
1739
-			->expects($this->once())
1740
-			->method('createEMailTemplate')
1741
-			->willReturn($template);
1742
-		$template
1743
-			->expects($this->once())
1744
-			->method('addHeader');
1745
-		$template
1746
-			->expects($this->once())
1747
-			->method('addHeading')
1748
-			->with('Mrs. Owner User shared file.txt with you');
1749
-		$template
1750
-			->expects($this->once())
1751
-			->method('addBodyButton')
1752
-			->with(
1753
-				'Open file.txt',
1754
-				'https://example.com/file.txt'
1755
-			);
1756
-		$message
1757
-			->expects($this->once())
1758
-			->method('setTo')
1759
-			->with(['[email protected]']);
1760
-		$this->defaults
1761
-			->expects($this->once())
1762
-			->method('getName')
1763
-			->willReturn('UnitTestCloud');
1764
-		$message
1765
-			->expects($this->once())
1766
-			->method('setFrom')
1767
-			->with([
1768
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud'
1769
-			]);
1770
-		// Since replyToInitiator is false, we never get the initiator email address
1771
-		$user
1772
-			->expects($this->never())
1773
-			->method('getEMailAddress');
1774
-		$message
1775
-			->expects($this->never())
1776
-			->method('setReplyTo');
1777
-		$template
1778
-			->expects($this->once())
1779
-			->method('addFooter')
1780
-			->with('');
1781
-		$template
1782
-			->expects($this->once())
1783
-			->method('setSubject')
1784
-			->with('Mrs. Owner User shared file.txt with you');
1785
-		$message
1786
-			->expects($this->once())
1787
-			->method('useTemplate')
1788
-			->with($template);
1789
-
1790
-		$this->mailer
1791
-			->expects($this->once())
1792
-			->method('send')
1793
-			->with($message);
1794
-
1795
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1796
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1797
-			->willReturn('https://example.com/file.txt');
1798
-
1799
-		$node = $this->createMock(File::class);
1800
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1801
-
1802
-		$share = $this->createMock(IShare::class);
1803
-		$share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1804
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1805
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1806
-		$share->expects($this->any())->method('getId')->willReturn(42);
1807
-		$share->expects($this->any())->method('getNote')->willReturn('');
1808
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1809
-
1810
-		self::invokePrivate(
1811
-			$provider,
1812
-			'sendMailNotification',
1813
-			[$share]
1814
-		);
1815
-	}
1816
-
1817
-	public function testSendMailNotificationWithDifferentUserAndNoUserEmailAndReplyToDesactivate(): void {
1818
-		$provider = $this->getInstance();
1819
-		$initiatorUser = $this->createMock(IUser::class);
1820
-		$this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false);
1821
-		$this->userManager
1822
-			->expects($this->once())
1823
-			->method('get')
1824
-			->with('InitiatorUser')
1825
-			->willReturn($initiatorUser);
1826
-		$initiatorUser
1827
-			->expects($this->once())
1828
-			->method('getDisplayName')
1829
-			->willReturn('Mr. Initiator User');
1830
-		$message = $this->createMock(Message::class);
1831
-		$this->mailer
1832
-			->expects($this->once())
1833
-			->method('createMessage')
1834
-			->willReturn($message);
1835
-		$template = $this->createMock(IEMailTemplate::class);
1836
-		$this->mailer
1837
-			->expects($this->once())
1838
-			->method('createEMailTemplate')
1839
-			->willReturn($template);
1840
-		$template
1841
-			->expects($this->once())
1842
-			->method('addHeader');
1843
-		$template
1844
-			->expects($this->once())
1845
-			->method('addHeading')
1846
-			->with('Mr. Initiator User shared file.txt with you');
1847
-		$template
1848
-			->expects($this->once())
1849
-			->method('addBodyButton')
1850
-			->with(
1851
-				'Open file.txt',
1852
-				'https://example.com/file.txt'
1853
-			);
1854
-		$message
1855
-			->expects($this->once())
1856
-			->method('setTo')
1857
-			->with(['[email protected]']);
1858
-		$this->defaults
1859
-			->expects($this->once())
1860
-			->method('getName')
1861
-			->willReturn('UnitTestCloud');
1862
-		$message
1863
-			->expects($this->once())
1864
-			->method('setFrom')
1865
-			->with([
1866
-				Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud'
1867
-			]);
1868
-		$message
1869
-			->expects($this->never())
1870
-			->method('setReplyTo');
1871
-		$template
1872
-			->expects($this->once())
1873
-			->method('addFooter')
1874
-			->with('');
1875
-		$template
1876
-			->expects($this->once())
1877
-			->method('setSubject')
1878
-			->with('Mr. Initiator User shared file.txt with you');
1879
-		$message
1880
-			->expects($this->once())
1881
-			->method('useTemplate')
1882
-			->with($template);
1883
-
1884
-		$this->mailer
1885
-			->expects($this->once())
1886
-			->method('send')
1887
-			->with($message);
1888
-
1889
-		$this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1890
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1891
-			->willReturn('https://example.com/file.txt');
1892
-
1893
-		$node = $this->createMock(File::class);
1894
-		$node->expects($this->any())->method('getName')->willReturn('file.txt');
1895
-
1896
-		$share = $this->createMock(IShare::class);
1897
-		$share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser');
1898
-		$share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1899
-		$share->expects($this->any())->method('getNode')->willReturn($node);
1900
-		$share->expects($this->any())->method('getId')->willReturn(42);
1901
-		$share->expects($this->any())->method('getNote')->willReturn('');
1902
-		$share->expects($this->any())->method('getToken')->willReturn('token');
1903
-
1904
-		self::invokePrivate(
1905
-			$provider,
1906
-			'sendMailNotification',
1907
-			[$share]
1908
-		);
1909
-	}
1157
+        $qb->setValue('file_target', $qb->createNamedParameter(''));
1158
+
1159
+        $qb->execute();
1160
+        $id = $qb->getLastInsertId();
1161
+
1162
+        return (int)$id;
1163
+    }
1164
+
1165
+    public function testGetSharesInFolder(): void {
1166
+        $userManager = Server::get(IUserManager::class);
1167
+        $rootFolder = Server::get(IRootFolder::class);
1168
+
1169
+        $this->shareManager->expects($this->any())
1170
+            ->method('newShare')
1171
+            ->willReturn(new Share($rootFolder, $userManager));
1172
+
1173
+        $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1174
+
1175
+        $u1 = $userManager->createUser('testFed', md5((string)time()));
1176
+        $u2 = $userManager->createUser('testFed2', md5((string)time()));
1177
+
1178
+        $folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1179
+        $file1 = $folder1->newFile('bar1');
1180
+        $file2 = $folder1->newFile('bar2');
1181
+
1182
+        $share1 = $this->shareManager->newShare();
1183
+        $share1->setSharedWith('[email protected]')
1184
+            ->setSharedBy($u1->getUID())
1185
+            ->setShareOwner($u1->getUID())
1186
+            ->setPermissions(Constants::PERMISSION_READ)
1187
+            ->setNode($file1);
1188
+        $provider->create($share1);
1189
+
1190
+        $share2 = $this->shareManager->newShare();
1191
+        $share2->setSharedWith('[email protected]')
1192
+            ->setSharedBy($u2->getUID())
1193
+            ->setShareOwner($u1->getUID())
1194
+            ->setPermissions(Constants::PERMISSION_READ)
1195
+            ->setNode($file2);
1196
+        $provider->create($share2);
1197
+
1198
+        $result = $provider->getSharesInFolder($u1->getUID(), $folder1, false);
1199
+        $this->assertCount(1, $result);
1200
+        $this->assertCount(1, $result[$file1->getId()]);
1201
+
1202
+        $result = $provider->getSharesInFolder($u1->getUID(), $folder1, true);
1203
+        $this->assertCount(2, $result);
1204
+        $this->assertCount(1, $result[$file1->getId()]);
1205
+        $this->assertCount(1, $result[$file2->getId()]);
1206
+
1207
+        $u1->delete();
1208
+        $u2->delete();
1209
+    }
1210
+
1211
+    public function testGetAccessList(): void {
1212
+        $userManager = Server::get(IUserManager::class);
1213
+        $rootFolder = Server::get(IRootFolder::class);
1214
+
1215
+        $this->shareManager->expects($this->any())
1216
+            ->method('newShare')
1217
+            ->willReturn(new Share($rootFolder, $userManager));
1218
+
1219
+        $provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1220
+
1221
+        $u1 = $userManager->createUser('testFed', md5((string)time()));
1222
+        $u2 = $userManager->createUser('testFed2', md5((string)time()));
1223
+
1224
+        $folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1225
+
1226
+        $accessList = $provider->getAccessList([$folder], true);
1227
+        $this->assertArrayHasKey('public', $accessList);
1228
+        $this->assertFalse($accessList['public']);
1229
+        $accessList = $provider->getAccessList([$folder], false);
1230
+        $this->assertArrayHasKey('public', $accessList);
1231
+        $this->assertFalse($accessList['public']);
1232
+
1233
+        $share1 = $this->shareManager->newShare();
1234
+        $share1->setSharedWith('[email protected]')
1235
+            ->setSharedBy($u1->getUID())
1236
+            ->setShareOwner($u1->getUID())
1237
+            ->setPermissions(Constants::PERMISSION_READ)
1238
+            ->setNode($folder);
1239
+        $share1 = $provider->create($share1);
1240
+
1241
+        $share2 = $this->shareManager->newShare();
1242
+        $share2->setSharedWith('[email protected]')
1243
+            ->setSharedBy($u2->getUID())
1244
+            ->setShareOwner($u1->getUID())
1245
+            ->setPermissions(Constants::PERMISSION_READ)
1246
+            ->setNode($folder);
1247
+        $share2 = $provider->create($share2);
1248
+
1249
+        $accessList = $provider->getAccessList([$folder], true);
1250
+        $this->assertArrayHasKey('public', $accessList);
1251
+        $this->assertTrue($accessList['public']);
1252
+        $accessList = $provider->getAccessList([$folder], false);
1253
+        $this->assertArrayHasKey('public', $accessList);
1254
+        $this->assertTrue($accessList['public']);
1255
+
1256
+        $provider->delete($share2);
1257
+
1258
+        $accessList = $provider->getAccessList([$folder], true);
1259
+        $this->assertArrayHasKey('public', $accessList);
1260
+        $this->assertTrue($accessList['public']);
1261
+        $accessList = $provider->getAccessList([$folder], false);
1262
+        $this->assertArrayHasKey('public', $accessList);
1263
+        $this->assertTrue($accessList['public']);
1264
+
1265
+        $provider->delete($share1);
1266
+
1267
+        $accessList = $provider->getAccessList([$folder], true);
1268
+        $this->assertArrayHasKey('public', $accessList);
1269
+        $this->assertFalse($accessList['public']);
1270
+        $accessList = $provider->getAccessList([$folder], false);
1271
+        $this->assertArrayHasKey('public', $accessList);
1272
+        $this->assertFalse($accessList['public']);
1273
+
1274
+        $u1->delete();
1275
+        $u2->delete();
1276
+    }
1277
+
1278
+    public function testSendMailNotificationWithSameUserAndUserEmail(): void {
1279
+        $provider = $this->getInstance();
1280
+        $user = $this->createMock(IUser::class);
1281
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1282
+        $this->userManager
1283
+            ->expects($this->once())
1284
+            ->method('get')
1285
+            ->with('OwnerUser')
1286
+            ->willReturn($user);
1287
+        $user
1288
+            ->expects($this->once())
1289
+            ->method('getDisplayName')
1290
+            ->willReturn('Mrs. Owner User');
1291
+        $message = $this->createMock(Message::class);
1292
+        $this->mailer
1293
+            ->expects($this->once())
1294
+            ->method('createMessage')
1295
+            ->willReturn($message);
1296
+        $template = $this->createMock(IEMailTemplate::class);
1297
+        $this->mailer
1298
+            ->expects($this->once())
1299
+            ->method('createEMailTemplate')
1300
+            ->willReturn($template);
1301
+        $template
1302
+            ->expects($this->once())
1303
+            ->method('addHeader');
1304
+        $template
1305
+            ->expects($this->once())
1306
+            ->method('addHeading')
1307
+            ->with('Mrs. Owner User shared file.txt with you');
1308
+        $template
1309
+            ->expects($this->once())
1310
+            ->method('addBodyButton')
1311
+            ->with(
1312
+                'Open file.txt',
1313
+                'https://example.com/file.txt'
1314
+            );
1315
+        $message
1316
+            ->expects($this->once())
1317
+            ->method('setTo')
1318
+            ->with(['[email protected]']);
1319
+        $this->defaults
1320
+            ->expects($this->once())
1321
+            ->method('getName')
1322
+            ->willReturn('UnitTestCloud');
1323
+        $message
1324
+            ->expects($this->once())
1325
+            ->method('setFrom')
1326
+            ->with([
1327
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1328
+            ]);
1329
+        $user
1330
+            ->expects($this->once())
1331
+            ->method('getEMailAddress')
1332
+            ->willReturn('[email protected]');
1333
+        $message
1334
+            ->expects($this->once())
1335
+            ->method('setReplyTo')
1336
+            ->with(['[email protected]' => 'Mrs. Owner User']);
1337
+        $this->defaults
1338
+            ->expects($this->exactly(2))
1339
+            ->method('getSlogan')
1340
+            ->willReturn('Testing like 1990');
1341
+        $template
1342
+            ->expects($this->once())
1343
+            ->method('addFooter')
1344
+            ->with('UnitTestCloud - Testing like 1990');
1345
+        $template
1346
+            ->expects($this->once())
1347
+            ->method('setSubject')
1348
+            ->with('Mrs. Owner User shared file.txt with you');
1349
+        $message
1350
+            ->expects($this->once())
1351
+            ->method('useTemplate')
1352
+            ->with($template);
1353
+
1354
+        $this->mailer
1355
+            ->expects($this->once())
1356
+            ->method('send')
1357
+            ->with($message);
1358
+
1359
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1360
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1361
+            ->willReturn('https://example.com/file.txt');
1362
+
1363
+        $node = $this->createMock(File::class);
1364
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1365
+
1366
+        $share = $this->createMock(IShare::class);
1367
+        $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1368
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1369
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1370
+        $share->expects($this->any())->method('getId')->willReturn(42);
1371
+        $share->expects($this->any())->method('getNote')->willReturn('');
1372
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1373
+
1374
+        self::invokePrivate(
1375
+            $provider,
1376
+            'sendMailNotification',
1377
+            [$share]
1378
+        );
1379
+    }
1380
+
1381
+    public function testSendMailNotificationWithSameUserAndUserEmailAndNote(): void {
1382
+        $provider = $this->getInstance();
1383
+        $user = $this->createMock(IUser::class);
1384
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1385
+        $this->userManager
1386
+            ->expects($this->once())
1387
+            ->method('get')
1388
+            ->with('OwnerUser')
1389
+            ->willReturn($user);
1390
+        $user
1391
+            ->expects($this->once())
1392
+            ->method('getDisplayName')
1393
+            ->willReturn('Mrs. Owner User');
1394
+        $message = $this->createMock(Message::class);
1395
+        $this->mailer
1396
+            ->expects($this->once())
1397
+            ->method('createMessage')
1398
+            ->willReturn($message);
1399
+        $template = $this->createMock(IEMailTemplate::class);
1400
+        $this->mailer
1401
+            ->expects($this->once())
1402
+            ->method('createEMailTemplate')
1403
+            ->willReturn($template);
1404
+        $template
1405
+            ->expects($this->once())
1406
+            ->method('addHeader');
1407
+        $template
1408
+            ->expects($this->once())
1409
+            ->method('addHeading')
1410
+            ->with('Mrs. Owner User shared file.txt with you');
1411
+
1412
+        $this->urlGenerator->expects($this->once())->method('imagePath')
1413
+            ->with('core', 'caldav/description.png')
1414
+            ->willReturn('core/img/caldav/description.png');
1415
+        $this->urlGenerator->expects($this->once())->method('getAbsoluteURL')
1416
+            ->with('core/img/caldav/description.png')
1417
+            ->willReturn('https://example.com/core/img/caldav/description.png');
1418
+        $template
1419
+            ->expects($this->once())
1420
+            ->method('addBodyListItem')
1421
+            ->with(
1422
+                'This is a note to the recipient',
1423
+                'Note:',
1424
+                'https://example.com/core/img/caldav/description.png',
1425
+                'This is a note to the recipient'
1426
+            );
1427
+        $template
1428
+            ->expects($this->once())
1429
+            ->method('addBodyButton')
1430
+            ->with(
1431
+                'Open file.txt',
1432
+                'https://example.com/file.txt'
1433
+            );
1434
+        $message
1435
+            ->expects($this->once())
1436
+            ->method('setTo')
1437
+            ->with(['[email protected]']);
1438
+        $this->defaults
1439
+            ->expects($this->once())
1440
+            ->method('getName')
1441
+            ->willReturn('UnitTestCloud');
1442
+        $message
1443
+            ->expects($this->once())
1444
+            ->method('setFrom')
1445
+            ->with([
1446
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1447
+            ]);
1448
+        $user
1449
+            ->expects($this->once())
1450
+            ->method('getEMailAddress')
1451
+            ->willReturn('[email protected]');
1452
+        $message
1453
+            ->expects($this->once())
1454
+            ->method('setReplyTo')
1455
+            ->with(['[email protected]' => 'Mrs. Owner User']);
1456
+        $this->defaults
1457
+            ->expects($this->exactly(2))
1458
+            ->method('getSlogan')
1459
+            ->willReturn('Testing like 1990');
1460
+        $template
1461
+            ->expects($this->once())
1462
+            ->method('addFooter')
1463
+            ->with('UnitTestCloud - Testing like 1990');
1464
+        $template
1465
+            ->expects($this->once())
1466
+            ->method('setSubject')
1467
+            ->with('Mrs. Owner User shared file.txt with you');
1468
+        $message
1469
+            ->expects($this->once())
1470
+            ->method('useTemplate')
1471
+            ->with($template);
1472
+
1473
+        $this->mailer
1474
+            ->expects($this->once())
1475
+            ->method('send')
1476
+            ->with($message);
1477
+
1478
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1479
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1480
+            ->willReturn('https://example.com/file.txt');
1481
+
1482
+        $node = $this->createMock(File::class);
1483
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1484
+
1485
+        $share = $this->createMock(IShare::class);
1486
+        $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1487
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1488
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1489
+        $share->expects($this->any())->method('getId')->willReturn(42);
1490
+        $share->expects($this->any())->method('getNote')->willReturn('This is a note to the recipient');
1491
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1492
+
1493
+        self::invokePrivate(
1494
+            $provider,
1495
+            'sendMailNotification',
1496
+            [$share]
1497
+        );
1498
+    }
1499
+
1500
+    public function testSendMailNotificationWithSameUserAndUserEmailAndExpiration(): void {
1501
+        $provider = $this->getInstance();
1502
+        $user = $this->createMock(IUser::class);
1503
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1504
+        $this->userManager
1505
+            ->expects($this->once())
1506
+            ->method('get')
1507
+            ->with('OwnerUser')
1508
+            ->willReturn($user);
1509
+        $user
1510
+            ->expects($this->once())
1511
+            ->method('getDisplayName')
1512
+            ->willReturn('Mrs. Owner User');
1513
+        $message = $this->createMock(Message::class);
1514
+        $this->mailer
1515
+            ->expects($this->once())
1516
+            ->method('createMessage')
1517
+            ->willReturn($message);
1518
+        $template = $this->createMock(IEMailTemplate::class);
1519
+        $this->mailer
1520
+            ->expects($this->once())
1521
+            ->method('createEMailTemplate')
1522
+            ->willReturn($template);
1523
+        $template
1524
+            ->expects($this->once())
1525
+            ->method('addHeader');
1526
+        $template
1527
+            ->expects($this->once())
1528
+            ->method('addHeading')
1529
+            ->with('Mrs. Owner User shared file.txt with you');
1530
+
1531
+        $expiration = new DateTime('2001-01-01');
1532
+        $this->l->expects($this->once())
1533
+            ->method('l')
1534
+            ->with('date', $expiration, ['width' => 'medium'])
1535
+            ->willReturn('2001-01-01');
1536
+        $this->urlGenerator->expects($this->once())->method('imagePath')
1537
+            ->with('core', 'caldav/time.png')
1538
+            ->willReturn('core/img/caldav/time.png');
1539
+        $this->urlGenerator->expects($this->once())->method('getAbsoluteURL')
1540
+            ->with('core/img/caldav/time.png')
1541
+            ->willReturn('https://example.com/core/img/caldav/time.png');
1542
+        $template
1543
+            ->expects($this->once())
1544
+            ->method('addBodyListItem')
1545
+            ->with(
1546
+                'This share is valid until 2001-01-01 at midnight',
1547
+                'Expiration:',
1548
+                'https://example.com/core/img/caldav/time.png',
1549
+            );
1550
+
1551
+        $template
1552
+            ->expects($this->once())
1553
+            ->method('addBodyButton')
1554
+            ->with(
1555
+                'Open file.txt',
1556
+                'https://example.com/file.txt'
1557
+            );
1558
+        $message
1559
+            ->expects($this->once())
1560
+            ->method('setTo')
1561
+            ->with(['[email protected]']);
1562
+        $this->defaults
1563
+            ->expects($this->once())
1564
+            ->method('getName')
1565
+            ->willReturn('UnitTestCloud');
1566
+        $message
1567
+            ->expects($this->once())
1568
+            ->method('setFrom')
1569
+            ->with([
1570
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'Mrs. Owner User via UnitTestCloud'
1571
+            ]);
1572
+        $user
1573
+            ->expects($this->once())
1574
+            ->method('getEMailAddress')
1575
+            ->willReturn('[email protected]');
1576
+        $message
1577
+            ->expects($this->once())
1578
+            ->method('setReplyTo')
1579
+            ->with(['[email protected]' => 'Mrs. Owner User']);
1580
+        $this->defaults
1581
+            ->expects($this->exactly(2))
1582
+            ->method('getSlogan')
1583
+            ->willReturn('Testing like 1990');
1584
+        $template
1585
+            ->expects($this->once())
1586
+            ->method('addFooter')
1587
+            ->with('UnitTestCloud - Testing like 1990');
1588
+        $template
1589
+            ->expects($this->once())
1590
+            ->method('setSubject')
1591
+            ->with('Mrs. Owner User shared file.txt with you');
1592
+        $message
1593
+            ->expects($this->once())
1594
+            ->method('useTemplate')
1595
+            ->with($template);
1596
+
1597
+        $this->mailer
1598
+            ->expects($this->once())
1599
+            ->method('send')
1600
+            ->with($message);
1601
+
1602
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1603
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1604
+            ->willReturn('https://example.com/file.txt');
1605
+
1606
+        $node = $this->createMock(File::class);
1607
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1608
+
1609
+        $share = $this->createMock(IShare::class);
1610
+        $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1611
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1612
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1613
+        $share->expects($this->any())->method('getId')->willReturn(42);
1614
+        $share->expects($this->any())->method('getNote')->willReturn('');
1615
+        $share->expects($this->any())->method('getExpirationDate')->willReturn($expiration);
1616
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1617
+
1618
+        self::invokePrivate(
1619
+            $provider,
1620
+            'sendMailNotification',
1621
+            [$share]
1622
+        );
1623
+    }
1624
+
1625
+    public function testSendMailNotificationWithDifferentUserAndNoUserEmail(): void {
1626
+        $provider = $this->getInstance();
1627
+        $initiatorUser = $this->createMock(IUser::class);
1628
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(true);
1629
+        $this->userManager
1630
+            ->expects($this->once())
1631
+            ->method('get')
1632
+            ->with('InitiatorUser')
1633
+            ->willReturn($initiatorUser);
1634
+        $initiatorUser
1635
+            ->expects($this->once())
1636
+            ->method('getDisplayName')
1637
+            ->willReturn('Mr. Initiator User');
1638
+        $message = $this->createMock(Message::class);
1639
+        $this->mailer
1640
+            ->expects($this->once())
1641
+            ->method('createMessage')
1642
+            ->willReturn($message);
1643
+        $template = $this->createMock(IEMailTemplate::class);
1644
+        $this->mailer
1645
+            ->expects($this->once())
1646
+            ->method('createEMailTemplate')
1647
+            ->willReturn($template);
1648
+        $template
1649
+            ->expects($this->once())
1650
+            ->method('addHeader');
1651
+        $template
1652
+            ->expects($this->once())
1653
+            ->method('addHeading')
1654
+            ->with('Mr. Initiator User shared file.txt with you');
1655
+        $template
1656
+            ->expects($this->once())
1657
+            ->method('addBodyButton')
1658
+            ->with(
1659
+                'Open file.txt',
1660
+                'https://example.com/file.txt'
1661
+            );
1662
+        $message
1663
+            ->expects($this->once())
1664
+            ->method('setTo')
1665
+            ->with(['[email protected]']);
1666
+        $this->defaults
1667
+            ->expects($this->once())
1668
+            ->method('getName')
1669
+            ->willReturn('UnitTestCloud');
1670
+        $message
1671
+            ->expects($this->once())
1672
+            ->method('setFrom')
1673
+            ->with([
1674
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'Mr. Initiator User via UnitTestCloud'
1675
+            ]);
1676
+        $message
1677
+            ->expects($this->never())
1678
+            ->method('setReplyTo');
1679
+        $template
1680
+            ->expects($this->once())
1681
+            ->method('addFooter')
1682
+            ->with('');
1683
+        $template
1684
+            ->expects($this->once())
1685
+            ->method('setSubject')
1686
+            ->with('Mr. Initiator User shared file.txt with you');
1687
+        $message
1688
+            ->expects($this->once())
1689
+            ->method('useTemplate')
1690
+            ->with($template);
1691
+
1692
+        $this->mailer
1693
+            ->expects($this->once())
1694
+            ->method('send')
1695
+            ->with($message);
1696
+
1697
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1698
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1699
+            ->willReturn('https://example.com/file.txt');
1700
+
1701
+        $node = $this->createMock(File::class);
1702
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1703
+
1704
+        $share = $this->createMock(IShare::class);
1705
+        $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser');
1706
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1707
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1708
+        $share->expects($this->any())->method('getId')->willReturn(42);
1709
+        $share->expects($this->any())->method('getNote')->willReturn('');
1710
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1711
+
1712
+        self::invokePrivate(
1713
+            $provider,
1714
+            'sendMailNotification',
1715
+            [$share]
1716
+        );
1717
+    }
1718
+
1719
+    public function testSendMailNotificationWithSameUserAndUserEmailAndReplyToDesactivate(): void {
1720
+        $provider = $this->getInstance();
1721
+        $user = $this->createMock(IUser::class);
1722
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false);
1723
+        $this->userManager
1724
+            ->expects($this->once())
1725
+            ->method('get')
1726
+            ->with('OwnerUser')
1727
+            ->willReturn($user);
1728
+        $user
1729
+            ->expects($this->once())
1730
+            ->method('getDisplayName')
1731
+            ->willReturn('Mrs. Owner User');
1732
+        $message = $this->createMock(Message::class);
1733
+        $this->mailer
1734
+            ->expects($this->once())
1735
+            ->method('createMessage')
1736
+            ->willReturn($message);
1737
+        $template = $this->createMock(IEMailTemplate::class);
1738
+        $this->mailer
1739
+            ->expects($this->once())
1740
+            ->method('createEMailTemplate')
1741
+            ->willReturn($template);
1742
+        $template
1743
+            ->expects($this->once())
1744
+            ->method('addHeader');
1745
+        $template
1746
+            ->expects($this->once())
1747
+            ->method('addHeading')
1748
+            ->with('Mrs. Owner User shared file.txt with you');
1749
+        $template
1750
+            ->expects($this->once())
1751
+            ->method('addBodyButton')
1752
+            ->with(
1753
+                'Open file.txt',
1754
+                'https://example.com/file.txt'
1755
+            );
1756
+        $message
1757
+            ->expects($this->once())
1758
+            ->method('setTo')
1759
+            ->with(['[email protected]']);
1760
+        $this->defaults
1761
+            ->expects($this->once())
1762
+            ->method('getName')
1763
+            ->willReturn('UnitTestCloud');
1764
+        $message
1765
+            ->expects($this->once())
1766
+            ->method('setFrom')
1767
+            ->with([
1768
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud'
1769
+            ]);
1770
+        // Since replyToInitiator is false, we never get the initiator email address
1771
+        $user
1772
+            ->expects($this->never())
1773
+            ->method('getEMailAddress');
1774
+        $message
1775
+            ->expects($this->never())
1776
+            ->method('setReplyTo');
1777
+        $template
1778
+            ->expects($this->once())
1779
+            ->method('addFooter')
1780
+            ->with('');
1781
+        $template
1782
+            ->expects($this->once())
1783
+            ->method('setSubject')
1784
+            ->with('Mrs. Owner User shared file.txt with you');
1785
+        $message
1786
+            ->expects($this->once())
1787
+            ->method('useTemplate')
1788
+            ->with($template);
1789
+
1790
+        $this->mailer
1791
+            ->expects($this->once())
1792
+            ->method('send')
1793
+            ->with($message);
1794
+
1795
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1796
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1797
+            ->willReturn('https://example.com/file.txt');
1798
+
1799
+        $node = $this->createMock(File::class);
1800
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1801
+
1802
+        $share = $this->createMock(IShare::class);
1803
+        $share->expects($this->any())->method('getSharedBy')->willReturn('OwnerUser');
1804
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1805
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1806
+        $share->expects($this->any())->method('getId')->willReturn(42);
1807
+        $share->expects($this->any())->method('getNote')->willReturn('');
1808
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1809
+
1810
+        self::invokePrivate(
1811
+            $provider,
1812
+            'sendMailNotification',
1813
+            [$share]
1814
+        );
1815
+    }
1816
+
1817
+    public function testSendMailNotificationWithDifferentUserAndNoUserEmailAndReplyToDesactivate(): void {
1818
+        $provider = $this->getInstance();
1819
+        $initiatorUser = $this->createMock(IUser::class);
1820
+        $this->settingsManager->expects($this->any())->method('replyToInitiator')->willReturn(false);
1821
+        $this->userManager
1822
+            ->expects($this->once())
1823
+            ->method('get')
1824
+            ->with('InitiatorUser')
1825
+            ->willReturn($initiatorUser);
1826
+        $initiatorUser
1827
+            ->expects($this->once())
1828
+            ->method('getDisplayName')
1829
+            ->willReturn('Mr. Initiator User');
1830
+        $message = $this->createMock(Message::class);
1831
+        $this->mailer
1832
+            ->expects($this->once())
1833
+            ->method('createMessage')
1834
+            ->willReturn($message);
1835
+        $template = $this->createMock(IEMailTemplate::class);
1836
+        $this->mailer
1837
+            ->expects($this->once())
1838
+            ->method('createEMailTemplate')
1839
+            ->willReturn($template);
1840
+        $template
1841
+            ->expects($this->once())
1842
+            ->method('addHeader');
1843
+        $template
1844
+            ->expects($this->once())
1845
+            ->method('addHeading')
1846
+            ->with('Mr. Initiator User shared file.txt with you');
1847
+        $template
1848
+            ->expects($this->once())
1849
+            ->method('addBodyButton')
1850
+            ->with(
1851
+                'Open file.txt',
1852
+                'https://example.com/file.txt'
1853
+            );
1854
+        $message
1855
+            ->expects($this->once())
1856
+            ->method('setTo')
1857
+            ->with(['[email protected]']);
1858
+        $this->defaults
1859
+            ->expects($this->once())
1860
+            ->method('getName')
1861
+            ->willReturn('UnitTestCloud');
1862
+        $message
1863
+            ->expects($this->once())
1864
+            ->method('setFrom')
1865
+            ->with([
1866
+                Util::getDefaultEmailAddress('UnitTestCloud') => 'UnitTestCloud'
1867
+            ]);
1868
+        $message
1869
+            ->expects($this->never())
1870
+            ->method('setReplyTo');
1871
+        $template
1872
+            ->expects($this->once())
1873
+            ->method('addFooter')
1874
+            ->with('');
1875
+        $template
1876
+            ->expects($this->once())
1877
+            ->method('setSubject')
1878
+            ->with('Mr. Initiator User shared file.txt with you');
1879
+        $message
1880
+            ->expects($this->once())
1881
+            ->method('useTemplate')
1882
+            ->with($template);
1883
+
1884
+        $this->mailer
1885
+            ->expects($this->once())
1886
+            ->method('send')
1887
+            ->with($message);
1888
+
1889
+        $this->urlGenerator->expects($this->once())->method('linkToRouteAbsolute')
1890
+            ->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
1891
+            ->willReturn('https://example.com/file.txt');
1892
+
1893
+        $node = $this->createMock(File::class);
1894
+        $node->expects($this->any())->method('getName')->willReturn('file.txt');
1895
+
1896
+        $share = $this->createMock(IShare::class);
1897
+        $share->expects($this->any())->method('getSharedBy')->willReturn('InitiatorUser');
1898
+        $share->expects($this->any())->method('getSharedWith')->willReturn('[email protected]');
1899
+        $share->expects($this->any())->method('getNode')->willReturn($node);
1900
+        $share->expects($this->any())->method('getId')->willReturn(42);
1901
+        $share->expects($this->any())->method('getNote')->willReturn('');
1902
+        $share->expects($this->any())->method('getToken')->willReturn('token');
1903
+
1904
+        self::invokePrivate(
1905
+            $provider,
1906
+            'sendMailNotification',
1907
+            [$share]
1908
+        );
1909
+    }
1910 1910
 }
Please login to merge, or discard this patch.
apps/dav/lib/Server.php 1 patch
Indentation   +335 added lines, -335 removed lines patch added patch discarded remove patch
@@ -105,340 +105,340 @@
 block discarded – undo
105 105
 use SearchDAV\DAV\SearchPlugin;
106 106
 
107 107
 class Server {
108
-	public Connector\Sabre\Server $server;
109
-	private IProfiler $profiler;
110
-
111
-	public function __construct(
112
-		private IRequest $request,
113
-		private string $baseUri,
114
-	) {
115
-		$debugEnabled = \OCP\Server::get(IConfig::class)->getSystemValue('debug', false);
116
-		$this->profiler = \OCP\Server::get(IProfiler::class);
117
-		if ($this->profiler->isEnabled()) {
118
-			/** @var IEventLogger $eventLogger */
119
-			$eventLogger = \OCP\Server::get(IEventLogger::class);
120
-			$eventLogger->start('runtime', 'DAV Runtime');
121
-		}
122
-
123
-		$logger = \OCP\Server::get(LoggerInterface::class);
124
-		$eventDispatcher = \OCP\Server::get(IEventDispatcher::class);
125
-
126
-		$root = new RootCollection();
127
-		$this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root));
128
-		$this->server->setLogger($logger);
129
-
130
-		// Add maintenance plugin
131
-		$this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav')));
132
-
133
-		$this->server->addPlugin(new AppleQuirksPlugin());
134
-
135
-		// Backends
136
-		$authBackend = new Auth(
137
-			\OCP\Server::get(ISession::class),
138
-			\OCP\Server::get(IUserSession::class),
139
-			\OCP\Server::get(IRequest::class),
140
-			\OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
141
-			\OCP\Server::get(IThrottler::class)
142
-		);
143
-
144
-		// Set URL explicitly due to reverse-proxy situations
145
-		$this->server->httpRequest->setUrl($this->request->getRequestUri());
146
-		$this->server->setBaseUri($this->baseUri);
147
-
148
-		$this->server->addPlugin(new ProfilerPlugin($this->request));
149
-		$this->server->addPlugin(new BlockLegacyClientPlugin(
150
-			\OCP\Server::get(IConfig::class),
151
-			\OCP\Server::get(ThemingDefaults::class),
152
-		));
153
-		$this->server->addPlugin(new AnonymousOptionsPlugin());
154
-		$authPlugin = new Plugin();
155
-		$authPlugin->addBackend(new PublicAuth());
156
-		$this->server->addPlugin($authPlugin);
157
-
158
-		// allow setup of additional auth backends
159
-		$event = new SabrePluginEvent($this->server);
160
-		$eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event);
161
-
162
-		$newAuthEvent = new SabrePluginAuthInitEvent($this->server);
163
-		$eventDispatcher->dispatchTyped($newAuthEvent);
164
-
165
-		$bearerAuthBackend = new BearerAuth(
166
-			\OCP\Server::get(IUserSession::class),
167
-			\OCP\Server::get(ISession::class),
168
-			\OCP\Server::get(IRequest::class),
169
-			\OCP\Server::get(IConfig::class),
170
-		);
171
-		$authPlugin->addBackend($bearerAuthBackend);
172
-		// because we are throwing exceptions this plugin has to be the last one
173
-		$authPlugin->addBackend($authBackend);
174
-
175
-		// debugging
176
-		if ($debugEnabled) {
177
-			$this->server->debugEnabled = true;
178
-			$this->server->addPlugin(new PropFindMonitorPlugin());
179
-			$this->server->addPlugin(new \Sabre\DAV\Browser\Plugin());
180
-		} else {
181
-			$this->server->addPlugin(new DummyGetResponsePlugin());
182
-		}
183
-
184
-		$this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger));
185
-		$this->server->addPlugin(new LockPlugin());
186
-		$this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
187
-
188
-		// acl
189
-		$acl = new DavAclPlugin();
190
-		$acl->principalCollectionSet = [
191
-			'principals/users',
192
-			'principals/groups',
193
-			'principals/calendar-resources',
194
-			'principals/calendar-rooms',
195
-		];
196
-		$this->server->addPlugin($acl);
197
-
198
-		// calendar plugins
199
-		if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) {
200
-			$this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
201
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
202
-			$this->server->addPlugin(new ICSExportPlugin(\OCP\Server::get(IConfig::class), $logger));
203
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OCP\Server::get(IConfig::class), \OCP\Server::get(LoggerInterface::class), \OCP\Server::get(DefaultCalendarValidator::class)));
204
-
205
-			$this->server->addPlugin(\OCP\Server::get(\OCA\DAV\CalDAV\Trashbin\Plugin::class));
206
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($this->request));
207
-			if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'allow_calendar_link_subscriptions', 'yes') === 'yes') {
208
-				$this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
209
-			}
210
-
211
-			$this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
212
-			$this->server->addPlugin(new PublishPlugin(
213
-				\OCP\Server::get(IConfig::class),
214
-				\OCP\Server::get(IURLGenerator::class)
215
-			));
216
-
217
-			$this->server->addPlugin(\OCP\Server::get(RateLimitingPlugin::class));
218
-			$this->server->addPlugin(\OCP\Server::get(CalDavValidatePlugin::class));
219
-		}
220
-
221
-		// addressbook plugins
222
-		if ($this->requestIsForSubtree(['addressbooks', 'principals'])) {
223
-			$this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
224
-			$this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
225
-			$this->server->addPlugin(new VCFExportPlugin());
226
-			$this->server->addPlugin(new MultiGetExportPlugin());
227
-			$this->server->addPlugin(new HasPhotoPlugin());
228
-			$this->server->addPlugin(new ImageExportPlugin(\OCP\Server::get(PhotoCache::class)));
229
-
230
-			$this->server->addPlugin(\OCP\Server::get(CardDavRateLimitingPlugin::class));
231
-			$this->server->addPlugin(\OCP\Server::get(CardDavValidatePlugin::class));
232
-		}
233
-
234
-		// system tags plugins
235
-		$this->server->addPlugin(\OCP\Server::get(SystemTagPlugin::class));
236
-
237
-		// comments plugin
238
-		$this->server->addPlugin(new CommentsPlugin(
239
-			\OCP\Server::get(ICommentsManager::class),
240
-			\OCP\Server::get(IUserSession::class)
241
-		));
242
-
243
-		// performance improvement plugins
244
-		$this->server->addPlugin(new CopyEtagHeaderPlugin());
245
-		$this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class)));
246
-		$this->server->addPlugin(new UploadAutoMkcolPlugin());
247
-		$this->server->addPlugin(new ChunkingV2Plugin(\OCP\Server::get(ICacheFactory::class)));
248
-		$this->server->addPlugin(new ChunkingPlugin());
249
-		$this->server->addPlugin(new ZipFolderPlugin(
250
-			$this->server->tree,
251
-			$logger,
252
-			$eventDispatcher,
253
-			\OCP\Server::get(IDateTimeZone::class),
254
-		));
255
-		$this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class));
256
-		$this->server->addPlugin(new PropFindPreloadNotifyPlugin());
257
-
258
-		// allow setup of additional plugins
259
-		$eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
260
-		$typedEvent = new SabrePluginAddEvent($this->server);
261
-		$eventDispatcher->dispatchTyped($typedEvent);
262
-
263
-		// Some WebDAV clients do require Class 2 WebDAV support (locking), since
264
-		// we do not provide locking we emulate it using a fake locking plugin.
265
-		if ($this->request->isUserAgent([
266
-			'/WebDAVFS/',
267
-			'/OneNote/',
268
-			'/^Microsoft-WebDAV/',// Microsoft-WebDAV-MiniRedir/6.1.7601
269
-		])) {
270
-			$this->server->addPlugin(new FakeLockerPlugin());
271
-		}
272
-
273
-		if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
274
-			$this->server->addPlugin(new BrowserErrorPagePlugin());
275
-		}
276
-
277
-		$lazySearchBackend = new LazySearchBackend();
278
-		$this->server->addPlugin(new SearchPlugin($lazySearchBackend));
279
-
280
-		// wait with registering these until auth is handled and the filesystem is setup
281
-		$this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger): void {
282
-			// Allow view-only plugin for webdav requests
283
-			$this->server->addPlugin(new ViewOnlyPlugin(
284
-				\OC::$server->getUserFolder(),
285
-			));
286
-
287
-			// custom properties plugin must be the last one
288
-			$userSession = \OCP\Server::get(IUserSession::class);
289
-			$user = $userSession->getUser();
290
-			if ($user !== null) {
291
-				$view = Filesystem::getView();
292
-				$config = \OCP\Server::get(IConfig::class);
293
-				$this->server->addPlugin(
294
-					new FilesPlugin(
295
-						$this->server->tree,
296
-						$config,
297
-						$this->request,
298
-						\OCP\Server::get(IPreview::class),
299
-						\OCP\Server::get(IUserSession::class),
300
-						\OCP\Server::get(IFilenameValidator::class),
301
-						\OCP\Server::get(IAccountManager::class),
302
-						false,
303
-						$config->getSystemValueBool('debug', false) === false,
304
-					)
305
-				);
306
-				$this->server->addPlugin(new ChecksumUpdatePlugin());
307
-
308
-				$this->server->addPlugin(
309
-					new \Sabre\DAV\PropertyStorage\Plugin(
310
-						new CustomPropertiesBackend(
311
-							$this->server,
312
-							$this->server->tree,
313
-							\OCP\Server::get(IDBConnection::class),
314
-							\OCP\Server::get(IUserSession::class)->getUser(),
315
-							\OCP\Server::get(PropertyMapper::class),
316
-							\OCP\Server::get(DefaultCalendarValidator::class),
317
-						)
318
-					)
319
-				);
320
-				if ($view !== null) {
321
-					$this->server->addPlugin(
322
-						new QuotaPlugin($view));
323
-				}
324
-				$this->server->addPlugin(
325
-					new TagsPlugin(
326
-						$this->server->tree, \OCP\Server::get(ITagManager::class), \OCP\Server::get(IEventDispatcher::class), \OCP\Server::get(IUserSession::class)
327
-					)
328
-				);
329
-
330
-				// TODO: switch to LazyUserFolder
331
-				$userFolder = \OC::$server->getUserFolder();
332
-				$shareManager = \OCP\Server::get(\OCP\Share\IManager::class);
333
-				$this->server->addPlugin(new SharesPlugin(
334
-					$this->server->tree,
335
-					$userSession,
336
-					$userFolder,
337
-					$shareManager,
338
-				));
339
-				$this->server->addPlugin(new CommentPropertiesPlugin(
340
-					\OCP\Server::get(ICommentsManager::class),
341
-					$userSession
342
-				));
343
-				if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
344
-					$this->server->addPlugin(new IMipPlugin(
345
-						\OCP\Server::get(IAppConfig::class),
346
-						\OCP\Server::get(IMailer::class),
347
-						\OCP\Server::get(LoggerInterface::class),
348
-						\OCP\Server::get(ITimeFactory::class),
349
-						\OCP\Server::get(Defaults::class),
350
-						$userSession,
351
-						\OCP\Server::get(IMipService::class),
352
-						\OCP\Server::get(EventComparisonService::class),
353
-						\OCP\Server::get(\OCP\Mail\Provider\IManager::class),
354
-						\OCP\Server::get(\OCP\Mail\IEmailValidator::class),
355
-					));
356
-				}
357
-				$this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin());
358
-				if ($view !== null) {
359
-					$this->server->addPlugin(new FilesReportPlugin(
360
-						$this->server->tree,
361
-						$view,
362
-						\OCP\Server::get(ISystemTagManager::class),
363
-						\OCP\Server::get(ISystemTagObjectMapper::class),
364
-						\OCP\Server::get(ITagManager::class),
365
-						$userSession,
366
-						\OCP\Server::get(IGroupManager::class),
367
-						$userFolder,
368
-						\OCP\Server::get(IAppManager::class)
369
-					));
370
-					$lazySearchBackend->setBackend(new FileSearchBackend(
371
-						$this->server,
372
-						$this->server->tree,
373
-						$user,
374
-						\OCP\Server::get(IRootFolder::class),
375
-						$shareManager,
376
-						$view,
377
-						\OCP\Server::get(IFilesMetadataManager::class)
378
-					));
379
-					$this->server->addPlugin(
380
-						new BulkUploadPlugin(
381
-							$userFolder,
382
-							$logger
383
-						)
384
-					);
385
-				}
386
-				$this->server->addPlugin(new EnablePlugin(
387
-					\OCP\Server::get(IConfig::class),
388
-					\OCP\Server::get(BirthdayService::class),
389
-					$user
390
-				));
391
-				$this->server->addPlugin(new AppleProvisioningPlugin(
392
-					\OCP\Server::get(IUserSession::class),
393
-					\OCP\Server::get(IURLGenerator::class),
394
-					\OCP\Server::get(ThemingDefaults::class),
395
-					\OCP\Server::get(IRequest::class),
396
-					\OC::$server->getL10N('dav'),
397
-					function () {
398
-						return UUIDUtil::getUUID();
399
-					}
400
-				));
401
-			}
402
-
403
-			// register plugins from apps
404
-			$pluginManager = new PluginManager(
405
-				\OC::$server,
406
-				\OCP\Server::get(IAppManager::class)
407
-			);
408
-			foreach ($pluginManager->getAppPlugins() as $appPlugin) {
409
-				$this->server->addPlugin($appPlugin);
410
-			}
411
-			foreach ($pluginManager->getAppCollections() as $appCollection) {
412
-				$root->addChild($appCollection);
413
-			}
414
-		});
415
-
416
-		$this->server->addPlugin(
417
-			new PropfindCompressionPlugin()
418
-		);
419
-	}
420
-
421
-	public function exec() {
422
-		/** @var IEventLogger $eventLogger */
423
-		$eventLogger = \OCP\Server::get(IEventLogger::class);
424
-		$eventLogger->start('dav_server_exec', '');
425
-		$this->server->start();
426
-		$eventLogger->end('dav_server_exec');
427
-		if ($this->profiler->isEnabled()) {
428
-			$eventLogger->end('runtime');
429
-			$profile = $this->profiler->collect(\OCP\Server::get(IRequest::class), new Response());
430
-			$this->profiler->saveProfile($profile);
431
-		}
432
-	}
433
-
434
-	private function requestIsForSubtree(array $subTrees): bool {
435
-		foreach ($subTrees as $subTree) {
436
-			$subTree = trim($subTree, ' /');
437
-			if (str_starts_with($this->server->getRequestUri(), $subTree . '/')) {
438
-				return true;
439
-			}
440
-		}
441
-		return false;
442
-	}
108
+    public Connector\Sabre\Server $server;
109
+    private IProfiler $profiler;
110
+
111
+    public function __construct(
112
+        private IRequest $request,
113
+        private string $baseUri,
114
+    ) {
115
+        $debugEnabled = \OCP\Server::get(IConfig::class)->getSystemValue('debug', false);
116
+        $this->profiler = \OCP\Server::get(IProfiler::class);
117
+        if ($this->profiler->isEnabled()) {
118
+            /** @var IEventLogger $eventLogger */
119
+            $eventLogger = \OCP\Server::get(IEventLogger::class);
120
+            $eventLogger->start('runtime', 'DAV Runtime');
121
+        }
122
+
123
+        $logger = \OCP\Server::get(LoggerInterface::class);
124
+        $eventDispatcher = \OCP\Server::get(IEventDispatcher::class);
125
+
126
+        $root = new RootCollection();
127
+        $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root));
128
+        $this->server->setLogger($logger);
129
+
130
+        // Add maintenance plugin
131
+        $this->server->addPlugin(new MaintenancePlugin(\OCP\Server::get(IConfig::class), \OC::$server->getL10N('dav')));
132
+
133
+        $this->server->addPlugin(new AppleQuirksPlugin());
134
+
135
+        // Backends
136
+        $authBackend = new Auth(
137
+            \OCP\Server::get(ISession::class),
138
+            \OCP\Server::get(IUserSession::class),
139
+            \OCP\Server::get(IRequest::class),
140
+            \OCP\Server::get(\OC\Authentication\TwoFactorAuth\Manager::class),
141
+            \OCP\Server::get(IThrottler::class)
142
+        );
143
+
144
+        // Set URL explicitly due to reverse-proxy situations
145
+        $this->server->httpRequest->setUrl($this->request->getRequestUri());
146
+        $this->server->setBaseUri($this->baseUri);
147
+
148
+        $this->server->addPlugin(new ProfilerPlugin($this->request));
149
+        $this->server->addPlugin(new BlockLegacyClientPlugin(
150
+            \OCP\Server::get(IConfig::class),
151
+            \OCP\Server::get(ThemingDefaults::class),
152
+        ));
153
+        $this->server->addPlugin(new AnonymousOptionsPlugin());
154
+        $authPlugin = new Plugin();
155
+        $authPlugin->addBackend(new PublicAuth());
156
+        $this->server->addPlugin($authPlugin);
157
+
158
+        // allow setup of additional auth backends
159
+        $event = new SabrePluginEvent($this->server);
160
+        $eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::authInit', $event);
161
+
162
+        $newAuthEvent = new SabrePluginAuthInitEvent($this->server);
163
+        $eventDispatcher->dispatchTyped($newAuthEvent);
164
+
165
+        $bearerAuthBackend = new BearerAuth(
166
+            \OCP\Server::get(IUserSession::class),
167
+            \OCP\Server::get(ISession::class),
168
+            \OCP\Server::get(IRequest::class),
169
+            \OCP\Server::get(IConfig::class),
170
+        );
171
+        $authPlugin->addBackend($bearerAuthBackend);
172
+        // because we are throwing exceptions this plugin has to be the last one
173
+        $authPlugin->addBackend($authBackend);
174
+
175
+        // debugging
176
+        if ($debugEnabled) {
177
+            $this->server->debugEnabled = true;
178
+            $this->server->addPlugin(new PropFindMonitorPlugin());
179
+            $this->server->addPlugin(new \Sabre\DAV\Browser\Plugin());
180
+        } else {
181
+            $this->server->addPlugin(new DummyGetResponsePlugin());
182
+        }
183
+
184
+        $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger));
185
+        $this->server->addPlugin(new LockPlugin());
186
+        $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin());
187
+
188
+        // acl
189
+        $acl = new DavAclPlugin();
190
+        $acl->principalCollectionSet = [
191
+            'principals/users',
192
+            'principals/groups',
193
+            'principals/calendar-resources',
194
+            'principals/calendar-rooms',
195
+        ];
196
+        $this->server->addPlugin($acl);
197
+
198
+        // calendar plugins
199
+        if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) {
200
+            $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
201
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
202
+            $this->server->addPlugin(new ICSExportPlugin(\OCP\Server::get(IConfig::class), $logger));
203
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OCP\Server::get(IConfig::class), \OCP\Server::get(LoggerInterface::class), \OCP\Server::get(DefaultCalendarValidator::class)));
204
+
205
+            $this->server->addPlugin(\OCP\Server::get(\OCA\DAV\CalDAV\Trashbin\Plugin::class));
206
+            $this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($this->request));
207
+            if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'allow_calendar_link_subscriptions', 'yes') === 'yes') {
208
+                $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
209
+            }
210
+
211
+            $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
212
+            $this->server->addPlugin(new PublishPlugin(
213
+                \OCP\Server::get(IConfig::class),
214
+                \OCP\Server::get(IURLGenerator::class)
215
+            ));
216
+
217
+            $this->server->addPlugin(\OCP\Server::get(RateLimitingPlugin::class));
218
+            $this->server->addPlugin(\OCP\Server::get(CalDavValidatePlugin::class));
219
+        }
220
+
221
+        // addressbook plugins
222
+        if ($this->requestIsForSubtree(['addressbooks', 'principals'])) {
223
+            $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OCP\Server::get(IRequest::class), \OCP\Server::get(IConfig::class)));
224
+            $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin());
225
+            $this->server->addPlugin(new VCFExportPlugin());
226
+            $this->server->addPlugin(new MultiGetExportPlugin());
227
+            $this->server->addPlugin(new HasPhotoPlugin());
228
+            $this->server->addPlugin(new ImageExportPlugin(\OCP\Server::get(PhotoCache::class)));
229
+
230
+            $this->server->addPlugin(\OCP\Server::get(CardDavRateLimitingPlugin::class));
231
+            $this->server->addPlugin(\OCP\Server::get(CardDavValidatePlugin::class));
232
+        }
233
+
234
+        // system tags plugins
235
+        $this->server->addPlugin(\OCP\Server::get(SystemTagPlugin::class));
236
+
237
+        // comments plugin
238
+        $this->server->addPlugin(new CommentsPlugin(
239
+            \OCP\Server::get(ICommentsManager::class),
240
+            \OCP\Server::get(IUserSession::class)
241
+        ));
242
+
243
+        // performance improvement plugins
244
+        $this->server->addPlugin(new CopyEtagHeaderPlugin());
245
+        $this->server->addPlugin(new RequestIdHeaderPlugin(\OCP\Server::get(IRequest::class)));
246
+        $this->server->addPlugin(new UploadAutoMkcolPlugin());
247
+        $this->server->addPlugin(new ChunkingV2Plugin(\OCP\Server::get(ICacheFactory::class)));
248
+        $this->server->addPlugin(new ChunkingPlugin());
249
+        $this->server->addPlugin(new ZipFolderPlugin(
250
+            $this->server->tree,
251
+            $logger,
252
+            $eventDispatcher,
253
+            \OCP\Server::get(IDateTimeZone::class),
254
+        ));
255
+        $this->server->addPlugin(\OCP\Server::get(PaginatePlugin::class));
256
+        $this->server->addPlugin(new PropFindPreloadNotifyPlugin());
257
+
258
+        // allow setup of additional plugins
259
+        $eventDispatcher->dispatch('OCA\DAV\Connector\Sabre::addPlugin', $event);
260
+        $typedEvent = new SabrePluginAddEvent($this->server);
261
+        $eventDispatcher->dispatchTyped($typedEvent);
262
+
263
+        // Some WebDAV clients do require Class 2 WebDAV support (locking), since
264
+        // we do not provide locking we emulate it using a fake locking plugin.
265
+        if ($this->request->isUserAgent([
266
+            '/WebDAVFS/',
267
+            '/OneNote/',
268
+            '/^Microsoft-WebDAV/',// Microsoft-WebDAV-MiniRedir/6.1.7601
269
+        ])) {
270
+            $this->server->addPlugin(new FakeLockerPlugin());
271
+        }
272
+
273
+        if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
274
+            $this->server->addPlugin(new BrowserErrorPagePlugin());
275
+        }
276
+
277
+        $lazySearchBackend = new LazySearchBackend();
278
+        $this->server->addPlugin(new SearchPlugin($lazySearchBackend));
279
+
280
+        // wait with registering these until auth is handled and the filesystem is setup
281
+        $this->server->on('beforeMethod:*', function () use ($root, $lazySearchBackend, $logger): void {
282
+            // Allow view-only plugin for webdav requests
283
+            $this->server->addPlugin(new ViewOnlyPlugin(
284
+                \OC::$server->getUserFolder(),
285
+            ));
286
+
287
+            // custom properties plugin must be the last one
288
+            $userSession = \OCP\Server::get(IUserSession::class);
289
+            $user = $userSession->getUser();
290
+            if ($user !== null) {
291
+                $view = Filesystem::getView();
292
+                $config = \OCP\Server::get(IConfig::class);
293
+                $this->server->addPlugin(
294
+                    new FilesPlugin(
295
+                        $this->server->tree,
296
+                        $config,
297
+                        $this->request,
298
+                        \OCP\Server::get(IPreview::class),
299
+                        \OCP\Server::get(IUserSession::class),
300
+                        \OCP\Server::get(IFilenameValidator::class),
301
+                        \OCP\Server::get(IAccountManager::class),
302
+                        false,
303
+                        $config->getSystemValueBool('debug', false) === false,
304
+                    )
305
+                );
306
+                $this->server->addPlugin(new ChecksumUpdatePlugin());
307
+
308
+                $this->server->addPlugin(
309
+                    new \Sabre\DAV\PropertyStorage\Plugin(
310
+                        new CustomPropertiesBackend(
311
+                            $this->server,
312
+                            $this->server->tree,
313
+                            \OCP\Server::get(IDBConnection::class),
314
+                            \OCP\Server::get(IUserSession::class)->getUser(),
315
+                            \OCP\Server::get(PropertyMapper::class),
316
+                            \OCP\Server::get(DefaultCalendarValidator::class),
317
+                        )
318
+                    )
319
+                );
320
+                if ($view !== null) {
321
+                    $this->server->addPlugin(
322
+                        new QuotaPlugin($view));
323
+                }
324
+                $this->server->addPlugin(
325
+                    new TagsPlugin(
326
+                        $this->server->tree, \OCP\Server::get(ITagManager::class), \OCP\Server::get(IEventDispatcher::class), \OCP\Server::get(IUserSession::class)
327
+                    )
328
+                );
329
+
330
+                // TODO: switch to LazyUserFolder
331
+                $userFolder = \OC::$server->getUserFolder();
332
+                $shareManager = \OCP\Server::get(\OCP\Share\IManager::class);
333
+                $this->server->addPlugin(new SharesPlugin(
334
+                    $this->server->tree,
335
+                    $userSession,
336
+                    $userFolder,
337
+                    $shareManager,
338
+                ));
339
+                $this->server->addPlugin(new CommentPropertiesPlugin(
340
+                    \OCP\Server::get(ICommentsManager::class),
341
+                    $userSession
342
+                ));
343
+                if (\OCP\Server::get(IConfig::class)->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
344
+                    $this->server->addPlugin(new IMipPlugin(
345
+                        \OCP\Server::get(IAppConfig::class),
346
+                        \OCP\Server::get(IMailer::class),
347
+                        \OCP\Server::get(LoggerInterface::class),
348
+                        \OCP\Server::get(ITimeFactory::class),
349
+                        \OCP\Server::get(Defaults::class),
350
+                        $userSession,
351
+                        \OCP\Server::get(IMipService::class),
352
+                        \OCP\Server::get(EventComparisonService::class),
353
+                        \OCP\Server::get(\OCP\Mail\Provider\IManager::class),
354
+                        \OCP\Server::get(\OCP\Mail\IEmailValidator::class),
355
+                    ));
356
+                }
357
+                $this->server->addPlugin(new \OCA\DAV\CalDAV\Search\SearchPlugin());
358
+                if ($view !== null) {
359
+                    $this->server->addPlugin(new FilesReportPlugin(
360
+                        $this->server->tree,
361
+                        $view,
362
+                        \OCP\Server::get(ISystemTagManager::class),
363
+                        \OCP\Server::get(ISystemTagObjectMapper::class),
364
+                        \OCP\Server::get(ITagManager::class),
365
+                        $userSession,
366
+                        \OCP\Server::get(IGroupManager::class),
367
+                        $userFolder,
368
+                        \OCP\Server::get(IAppManager::class)
369
+                    ));
370
+                    $lazySearchBackend->setBackend(new FileSearchBackend(
371
+                        $this->server,
372
+                        $this->server->tree,
373
+                        $user,
374
+                        \OCP\Server::get(IRootFolder::class),
375
+                        $shareManager,
376
+                        $view,
377
+                        \OCP\Server::get(IFilesMetadataManager::class)
378
+                    ));
379
+                    $this->server->addPlugin(
380
+                        new BulkUploadPlugin(
381
+                            $userFolder,
382
+                            $logger
383
+                        )
384
+                    );
385
+                }
386
+                $this->server->addPlugin(new EnablePlugin(
387
+                    \OCP\Server::get(IConfig::class),
388
+                    \OCP\Server::get(BirthdayService::class),
389
+                    $user
390
+                ));
391
+                $this->server->addPlugin(new AppleProvisioningPlugin(
392
+                    \OCP\Server::get(IUserSession::class),
393
+                    \OCP\Server::get(IURLGenerator::class),
394
+                    \OCP\Server::get(ThemingDefaults::class),
395
+                    \OCP\Server::get(IRequest::class),
396
+                    \OC::$server->getL10N('dav'),
397
+                    function () {
398
+                        return UUIDUtil::getUUID();
399
+                    }
400
+                ));
401
+            }
402
+
403
+            // register plugins from apps
404
+            $pluginManager = new PluginManager(
405
+                \OC::$server,
406
+                \OCP\Server::get(IAppManager::class)
407
+            );
408
+            foreach ($pluginManager->getAppPlugins() as $appPlugin) {
409
+                $this->server->addPlugin($appPlugin);
410
+            }
411
+            foreach ($pluginManager->getAppCollections() as $appCollection) {
412
+                $root->addChild($appCollection);
413
+            }
414
+        });
415
+
416
+        $this->server->addPlugin(
417
+            new PropfindCompressionPlugin()
418
+        );
419
+    }
420
+
421
+    public function exec() {
422
+        /** @var IEventLogger $eventLogger */
423
+        $eventLogger = \OCP\Server::get(IEventLogger::class);
424
+        $eventLogger->start('dav_server_exec', '');
425
+        $this->server->start();
426
+        $eventLogger->end('dav_server_exec');
427
+        if ($this->profiler->isEnabled()) {
428
+            $eventLogger->end('runtime');
429
+            $profile = $this->profiler->collect(\OCP\Server::get(IRequest::class), new Response());
430
+            $this->profiler->saveProfile($profile);
431
+        }
432
+    }
433
+
434
+    private function requestIsForSubtree(array $subTrees): bool {
435
+        foreach ($subTrees as $subTree) {
436
+            $subTree = trim($subTree, ' /');
437
+            if (str_starts_with($this->server->getRequestUri(), $subTree . '/')) {
438
+                return true;
439
+            }
440
+        }
441
+        return false;
442
+    }
443 443
 
444 444
 }
Please login to merge, or discard this patch.
apps/dav/lib/Listener/CalendarContactInteractionListener.php 1 patch
Indentation   +109 added lines, -109 removed lines patch added patch discarded remove patch
@@ -30,113 +30,113 @@
 block discarded – undo
30 30
 
31 31
 /** @template-implements IEventListener<CalendarObjectCreatedEvent|CalendarObjectUpdatedEvent|CalendarShareUpdatedEvent> */
32 32
 class CalendarContactInteractionListener implements IEventListener {
33
-	private const URI_USERS = 'principals/users/';
34
-
35
-	public function __construct(
36
-		private IEventDispatcher $dispatcher,
37
-		private IUserSession $userSession,
38
-		private Principal $principalConnector,
39
-		private IEmailValidator $emailValidator,
40
-		private LoggerInterface $logger,
41
-	) {
42
-	}
43
-
44
-	public function handle(Event $event): void {
45
-		if (($user = $this->userSession->getUser()) === null) {
46
-			// Without user context we can't do anything
47
-			return;
48
-		}
49
-
50
-		if ($event instanceof CalendarObjectCreatedEvent || $event instanceof CalendarObjectUpdatedEvent) {
51
-			// users: href => principal:principals/users/admin
52
-			foreach ($event->getShares() as $share) {
53
-				if (!isset($share['href'])) {
54
-					continue;
55
-				}
56
-				$this->emitFromUri($share['href'], $user);
57
-			}
58
-
59
-			// emit interaction for email attendees as well
60
-			if (isset($event->getObjectData()['calendardata'])) {
61
-				try {
62
-					$calendar = Reader::read($event->getObjectData()['calendardata']);
63
-					if ($calendar->VEVENT) {
64
-						foreach ($calendar->VEVENT as $calendarEvent) {
65
-							$this->emitFromObject($calendarEvent, $user);
66
-						}
67
-					}
68
-				} catch (Throwable $e) {
69
-					$this->logger->warning('Could not read calendar data for interaction events: ' . $e->getMessage(), [
70
-						'exception' => $e,
71
-					]);
72
-				}
73
-			}
74
-		}
75
-
76
-		if ($event instanceof CalendarShareUpdatedEvent && !empty($event->getAdded())) {
77
-			// group: href => principal:principals/groups/admin
78
-			// users: href => principal:principals/users/admin
79
-			foreach ($event->getAdded() as $added) {
80
-				if (!isset($added['href'])) {
81
-					// Nothing to work with
82
-					continue;
83
-				}
84
-				$this->emitFromUri($added['href'], $user);
85
-			}
86
-		}
87
-	}
88
-
89
-	private function emitFromUri(string $uri, IUser $user): void {
90
-		$principal = $this->principalConnector->findByUri(
91
-			$uri,
92
-			$this->principalConnector->getPrincipalPrefix()
93
-		);
94
-		if ($principal === null) {
95
-			// Invalid principal
96
-			return;
97
-		}
98
-		if (!str_starts_with($principal, self::URI_USERS)) {
99
-			// Not a user principal
100
-			return;
101
-		}
102
-
103
-		$uid = substr($principal, strlen(self::URI_USERS));
104
-		$this->dispatcher->dispatchTyped(
105
-			(new ContactInteractedWithEvent($user))->setUid($uid)
106
-		);
107
-	}
108
-
109
-	private function emitFromObject(VEvent $vevent, IUser $user): void {
110
-		if (!$vevent->ATTENDEE) {
111
-			// Nothing left to do
112
-			return;
113
-		}
114
-
115
-		foreach ($vevent->ATTENDEE as $attendee) {
116
-			if (!($attendee instanceof Property)) {
117
-				continue;
118
-			}
119
-
120
-			$cuType = $attendee->offsetGet('CUTYPE');
121
-			if ($cuType instanceof Parameter && $cuType->getValue() !== 'INDIVIDUAL') {
122
-				// Don't care about those
123
-				continue;
124
-			}
125
-
126
-			$mailTo = $attendee->getValue();
127
-			if (!str_starts_with($mailTo, 'mailto:')) {
128
-				// Doesn't look like an email
129
-				continue;
130
-			}
131
-			$email = substr($mailTo, strlen('mailto:'));
132
-			if (!$this->emailValidator->isValid($email)) {
133
-				// This really isn't a valid email
134
-				continue;
135
-			}
136
-
137
-			$this->dispatcher->dispatchTyped(
138
-				(new ContactInteractedWithEvent($user))->setEmail($email)
139
-			);
140
-		}
141
-	}
33
+    private const URI_USERS = 'principals/users/';
34
+
35
+    public function __construct(
36
+        private IEventDispatcher $dispatcher,
37
+        private IUserSession $userSession,
38
+        private Principal $principalConnector,
39
+        private IEmailValidator $emailValidator,
40
+        private LoggerInterface $logger,
41
+    ) {
42
+    }
43
+
44
+    public function handle(Event $event): void {
45
+        if (($user = $this->userSession->getUser()) === null) {
46
+            // Without user context we can't do anything
47
+            return;
48
+        }
49
+
50
+        if ($event instanceof CalendarObjectCreatedEvent || $event instanceof CalendarObjectUpdatedEvent) {
51
+            // users: href => principal:principals/users/admin
52
+            foreach ($event->getShares() as $share) {
53
+                if (!isset($share['href'])) {
54
+                    continue;
55
+                }
56
+                $this->emitFromUri($share['href'], $user);
57
+            }
58
+
59
+            // emit interaction for email attendees as well
60
+            if (isset($event->getObjectData()['calendardata'])) {
61
+                try {
62
+                    $calendar = Reader::read($event->getObjectData()['calendardata']);
63
+                    if ($calendar->VEVENT) {
64
+                        foreach ($calendar->VEVENT as $calendarEvent) {
65
+                            $this->emitFromObject($calendarEvent, $user);
66
+                        }
67
+                    }
68
+                } catch (Throwable $e) {
69
+                    $this->logger->warning('Could not read calendar data for interaction events: ' . $e->getMessage(), [
70
+                        'exception' => $e,
71
+                    ]);
72
+                }
73
+            }
74
+        }
75
+
76
+        if ($event instanceof CalendarShareUpdatedEvent && !empty($event->getAdded())) {
77
+            // group: href => principal:principals/groups/admin
78
+            // users: href => principal:principals/users/admin
79
+            foreach ($event->getAdded() as $added) {
80
+                if (!isset($added['href'])) {
81
+                    // Nothing to work with
82
+                    continue;
83
+                }
84
+                $this->emitFromUri($added['href'], $user);
85
+            }
86
+        }
87
+    }
88
+
89
+    private function emitFromUri(string $uri, IUser $user): void {
90
+        $principal = $this->principalConnector->findByUri(
91
+            $uri,
92
+            $this->principalConnector->getPrincipalPrefix()
93
+        );
94
+        if ($principal === null) {
95
+            // Invalid principal
96
+            return;
97
+        }
98
+        if (!str_starts_with($principal, self::URI_USERS)) {
99
+            // Not a user principal
100
+            return;
101
+        }
102
+
103
+        $uid = substr($principal, strlen(self::URI_USERS));
104
+        $this->dispatcher->dispatchTyped(
105
+            (new ContactInteractedWithEvent($user))->setUid($uid)
106
+        );
107
+    }
108
+
109
+    private function emitFromObject(VEvent $vevent, IUser $user): void {
110
+        if (!$vevent->ATTENDEE) {
111
+            // Nothing left to do
112
+            return;
113
+        }
114
+
115
+        foreach ($vevent->ATTENDEE as $attendee) {
116
+            if (!($attendee instanceof Property)) {
117
+                continue;
118
+            }
119
+
120
+            $cuType = $attendee->offsetGet('CUTYPE');
121
+            if ($cuType instanceof Parameter && $cuType->getValue() !== 'INDIVIDUAL') {
122
+                // Don't care about those
123
+                continue;
124
+            }
125
+
126
+            $mailTo = $attendee->getValue();
127
+            if (!str_starts_with($mailTo, 'mailto:')) {
128
+                // Doesn't look like an email
129
+                continue;
130
+            }
131
+            $email = substr($mailTo, strlen('mailto:'));
132
+            if (!$this->emailValidator->isValid($email)) {
133
+                // This really isn't a valid email
134
+                continue;
135
+            }
136
+
137
+            $this->dispatcher->dispatchTyped(
138
+                (new ContactInteractedWithEvent($user))->setEmail($email)
139
+            );
140
+        }
141
+    }
142 142
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Schedule/IMipPlugin.php 1 patch
Indentation   +271 added lines, -271 removed lines patch added patch discarded remove patch
@@ -47,176 +47,176 @@  discard block
 block discarded – undo
47 47
  */
48 48
 class IMipPlugin extends SabreIMipPlugin {
49 49
 
50
-	private ?VCalendar $vCalendar = null;
51
-	public const MAX_DATE = '2038-01-01';
52
-	public const METHOD_REQUEST = 'request';
53
-	public const METHOD_REPLY = 'reply';
54
-	public const METHOD_CANCEL = 'cancel';
55
-	public const IMIP_INDENT = 15;
56
-
57
-	public function __construct(
58
-		private IAppConfig $config,
59
-		private IMailer $mailer,
60
-		private LoggerInterface $logger,
61
-		private ITimeFactory $timeFactory,
62
-		private Defaults $defaults,
63
-		private IUserSession $userSession,
64
-		private IMipService $imipService,
65
-		private EventComparisonService $eventComparisonService,
66
-		private IMailManager $mailManager,
67
-		private IEmailValidator $emailValidator,
68
-	) {
69
-		parent::__construct('');
70
-	}
71
-
72
-	public function initialize(DAV\Server $server): void {
73
-		parent::initialize($server);
74
-		$server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
75
-	}
76
-
77
-	/**
78
-	 * Check quota before writing content
79
-	 *
80
-	 * @param string $uri target file URI
81
-	 * @param INode $node Sabre Node
82
-	 * @param resource $data data
83
-	 * @param bool $modified modified
84
-	 */
85
-	public function beforeWriteContent($uri, INode $node, $data, $modified): void {
86
-		if (!$node instanceof CalendarObject) {
87
-			return;
88
-		}
89
-		/** @var VCalendar $vCalendar */
90
-		$vCalendar = Reader::read($node->get());
91
-		$this->setVCalendar($vCalendar);
92
-	}
93
-
94
-	/**
95
-	 * Event handler for the 'schedule' event.
96
-	 *
97
-	 * @param Message $iTipMessage
98
-	 * @return void
99
-	 */
100
-	public function schedule(Message $iTipMessage) {
101
-
102
-		// Not sending any emails if the system considers the update insignificant
103
-		if (!$iTipMessage->significantChange) {
104
-			if (!$iTipMessage->scheduleStatus) {
105
-				$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
106
-			}
107
-			return;
108
-		}
109
-
110
-		if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto'
111
-			|| parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
112
-			return;
113
-		}
114
-
115
-		// don't send out mails for events that already took place
116
-		$lastOccurrence = $this->imipService->getLastOccurrence($iTipMessage->message);
117
-		$currentTime = $this->timeFactory->getTime();
118
-		if ($lastOccurrence < $currentTime) {
119
-			return;
120
-		}
121
-
122
-		// Strip off mailto:
123
-		$recipient = substr($iTipMessage->recipient, 7);
124
-		if (!$this->emailValidator->isValid($recipient)) {
125
-			// Nothing to send if the recipient doesn't have a valid email address
126
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
127
-			return;
128
-		}
129
-		$recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
130
-
131
-		$newEvents = $iTipMessage->message;
132
-		$oldEvents = $this->getVCalendar();
133
-
134
-		$modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
135
-		/** @var VEvent $vEvent */
136
-		$vEvent = array_pop($modified['new']);
137
-		/** @var VEvent $oldVevent */
138
-		$oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
139
-		$isModified = isset($oldVevent);
140
-
141
-		// No changed events after all - this shouldn't happen if there is significant change yet here we are
142
-		// The scheduling status is debatable
143
-		if (empty($vEvent)) {
144
-			$this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
145
-			$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
146
-			return;
147
-		}
148
-
149
-		// we (should) have one event component left
150
-		// as the ITip\Broker creates one iTip message per change
151
-		// and triggers the "schedule" event once per message
152
-		// we also might not have an old event as this could be a new
153
-		// invitation, or a new recurrence exception
154
-		$attendee = $this->imipService->getCurrentAttendee($iTipMessage);
155
-		if ($attendee === null) {
156
-			$uid = $vEvent->UID ?? 'no UID found';
157
-			$this->logger->debug('Could not find recipient ' . $recipient . ' as attendee for event with UID ' . $uid);
158
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
159
-			return;
160
-		}
161
-		// Don't send emails to rooms, resources and circles
162
-		if ($this->imipService->isRoomOrResource($attendee)
163
-				|| $this->imipService->isCircle($attendee)) {
164
-			$this->logger->debug('No invitation sent as recipient is room, resource or circle', [
165
-				'attendee' => $recipient,
166
-			]);
167
-			$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
168
-			return;
169
-		}
170
-		$this->imipService->setL10n($attendee);
171
-
172
-		// Build the sender name.
173
-		// Due to a bug in sabre, the senderName property for an iTIP message can actually also be a VObject Property
174
-		// If the iTIP message senderName is null or empty use the user session name as the senderName
175
-		if (($iTipMessage->senderName instanceof Parameter) && !empty(trim($iTipMessage->senderName->getValue()))) {
176
-			$senderName = trim($iTipMessage->senderName->getValue());
177
-		} elseif (is_string($iTipMessage->senderName) && !empty(trim($iTipMessage->senderName))) {
178
-			$senderName = trim($iTipMessage->senderName);
179
-		} elseif ($this->userSession->getUser() !== null) {
180
-			$senderName = trim($this->userSession->getUser()->getDisplayName());
181
-		} else {
182
-			$senderName = '';
183
-		}
184
-
185
-		$sender = substr($iTipMessage->sender, 7);
186
-
187
-		$replyingAttendee = null;
188
-		switch (strtolower($iTipMessage->method)) {
189
-			case self::METHOD_REPLY:
190
-				$method = self::METHOD_REPLY;
191
-				$data = $this->imipService->buildReplyBodyData($vEvent);
192
-				$replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
193
-				break;
194
-			case self::METHOD_CANCEL:
195
-				$method = self::METHOD_CANCEL;
196
-				$data = $this->imipService->buildCancelledBodyData($vEvent);
197
-				break;
198
-			default:
199
-				$method = self::METHOD_REQUEST;
200
-				$data = $this->imipService->buildBodyData($vEvent, $oldVevent);
201
-				break;
202
-		}
203
-
204
-		$data['attendee_name'] = ($recipientName ?: $recipient);
205
-		$data['invitee_name'] = ($senderName ?: $sender);
206
-
207
-		$fromEMail = Util::getDefaultEmailAddress('invitations-noreply');
208
-		$fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
209
-
210
-		$template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
211
-		$template->addHeader();
212
-
213
-		$this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified, $replyingAttendee);
214
-		$this->imipService->addBulletList($template, $vEvent, $data);
215
-
216
-		// Only add response buttons to invitation requests: Fix Issue #11230
217
-		if (strcasecmp($method, self::METHOD_REQUEST) === 0 && $this->imipService->getAttendeeRsvpOrReqForParticipant($attendee)) {
218
-
219
-			/*
50
+    private ?VCalendar $vCalendar = null;
51
+    public const MAX_DATE = '2038-01-01';
52
+    public const METHOD_REQUEST = 'request';
53
+    public const METHOD_REPLY = 'reply';
54
+    public const METHOD_CANCEL = 'cancel';
55
+    public const IMIP_INDENT = 15;
56
+
57
+    public function __construct(
58
+        private IAppConfig $config,
59
+        private IMailer $mailer,
60
+        private LoggerInterface $logger,
61
+        private ITimeFactory $timeFactory,
62
+        private Defaults $defaults,
63
+        private IUserSession $userSession,
64
+        private IMipService $imipService,
65
+        private EventComparisonService $eventComparisonService,
66
+        private IMailManager $mailManager,
67
+        private IEmailValidator $emailValidator,
68
+    ) {
69
+        parent::__construct('');
70
+    }
71
+
72
+    public function initialize(DAV\Server $server): void {
73
+        parent::initialize($server);
74
+        $server->on('beforeWriteContent', [$this, 'beforeWriteContent'], 10);
75
+    }
76
+
77
+    /**
78
+     * Check quota before writing content
79
+     *
80
+     * @param string $uri target file URI
81
+     * @param INode $node Sabre Node
82
+     * @param resource $data data
83
+     * @param bool $modified modified
84
+     */
85
+    public function beforeWriteContent($uri, INode $node, $data, $modified): void {
86
+        if (!$node instanceof CalendarObject) {
87
+            return;
88
+        }
89
+        /** @var VCalendar $vCalendar */
90
+        $vCalendar = Reader::read($node->get());
91
+        $this->setVCalendar($vCalendar);
92
+    }
93
+
94
+    /**
95
+     * Event handler for the 'schedule' event.
96
+     *
97
+     * @param Message $iTipMessage
98
+     * @return void
99
+     */
100
+    public function schedule(Message $iTipMessage) {
101
+
102
+        // Not sending any emails if the system considers the update insignificant
103
+        if (!$iTipMessage->significantChange) {
104
+            if (!$iTipMessage->scheduleStatus) {
105
+                $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
106
+            }
107
+            return;
108
+        }
109
+
110
+        if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto'
111
+            || parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') {
112
+            return;
113
+        }
114
+
115
+        // don't send out mails for events that already took place
116
+        $lastOccurrence = $this->imipService->getLastOccurrence($iTipMessage->message);
117
+        $currentTime = $this->timeFactory->getTime();
118
+        if ($lastOccurrence < $currentTime) {
119
+            return;
120
+        }
121
+
122
+        // Strip off mailto:
123
+        $recipient = substr($iTipMessage->recipient, 7);
124
+        if (!$this->emailValidator->isValid($recipient)) {
125
+            // Nothing to send if the recipient doesn't have a valid email address
126
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
127
+            return;
128
+        }
129
+        $recipientName = $iTipMessage->recipientName ? (string)$iTipMessage->recipientName : null;
130
+
131
+        $newEvents = $iTipMessage->message;
132
+        $oldEvents = $this->getVCalendar();
133
+
134
+        $modified = $this->eventComparisonService->findModified($newEvents, $oldEvents);
135
+        /** @var VEvent $vEvent */
136
+        $vEvent = array_pop($modified['new']);
137
+        /** @var VEvent $oldVevent */
138
+        $oldVevent = !empty($modified['old']) && is_array($modified['old']) ? array_pop($modified['old']) : null;
139
+        $isModified = isset($oldVevent);
140
+
141
+        // No changed events after all - this shouldn't happen if there is significant change yet here we are
142
+        // The scheduling status is debatable
143
+        if (empty($vEvent)) {
144
+            $this->logger->warning('iTip message said the change was significant but comparison did not detect any updated VEvents');
145
+            $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
146
+            return;
147
+        }
148
+
149
+        // we (should) have one event component left
150
+        // as the ITip\Broker creates one iTip message per change
151
+        // and triggers the "schedule" event once per message
152
+        // we also might not have an old event as this could be a new
153
+        // invitation, or a new recurrence exception
154
+        $attendee = $this->imipService->getCurrentAttendee($iTipMessage);
155
+        if ($attendee === null) {
156
+            $uid = $vEvent->UID ?? 'no UID found';
157
+            $this->logger->debug('Could not find recipient ' . $recipient . ' as attendee for event with UID ' . $uid);
158
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
159
+            return;
160
+        }
161
+        // Don't send emails to rooms, resources and circles
162
+        if ($this->imipService->isRoomOrResource($attendee)
163
+                || $this->imipService->isCircle($attendee)) {
164
+            $this->logger->debug('No invitation sent as recipient is room, resource or circle', [
165
+                'attendee' => $recipient,
166
+            ]);
167
+            $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
168
+            return;
169
+        }
170
+        $this->imipService->setL10n($attendee);
171
+
172
+        // Build the sender name.
173
+        // Due to a bug in sabre, the senderName property for an iTIP message can actually also be a VObject Property
174
+        // If the iTIP message senderName is null or empty use the user session name as the senderName
175
+        if (($iTipMessage->senderName instanceof Parameter) && !empty(trim($iTipMessage->senderName->getValue()))) {
176
+            $senderName = trim($iTipMessage->senderName->getValue());
177
+        } elseif (is_string($iTipMessage->senderName) && !empty(trim($iTipMessage->senderName))) {
178
+            $senderName = trim($iTipMessage->senderName);
179
+        } elseif ($this->userSession->getUser() !== null) {
180
+            $senderName = trim($this->userSession->getUser()->getDisplayName());
181
+        } else {
182
+            $senderName = '';
183
+        }
184
+
185
+        $sender = substr($iTipMessage->sender, 7);
186
+
187
+        $replyingAttendee = null;
188
+        switch (strtolower($iTipMessage->method)) {
189
+            case self::METHOD_REPLY:
190
+                $method = self::METHOD_REPLY;
191
+                $data = $this->imipService->buildReplyBodyData($vEvent);
192
+                $replyingAttendee = $this->imipService->getReplyingAttendee($iTipMessage);
193
+                break;
194
+            case self::METHOD_CANCEL:
195
+                $method = self::METHOD_CANCEL;
196
+                $data = $this->imipService->buildCancelledBodyData($vEvent);
197
+                break;
198
+            default:
199
+                $method = self::METHOD_REQUEST;
200
+                $data = $this->imipService->buildBodyData($vEvent, $oldVevent);
201
+                break;
202
+        }
203
+
204
+        $data['attendee_name'] = ($recipientName ?: $recipient);
205
+        $data['invitee_name'] = ($senderName ?: $sender);
206
+
207
+        $fromEMail = Util::getDefaultEmailAddress('invitations-noreply');
208
+        $fromName = $this->imipService->getFrom($senderName, $this->defaults->getName());
209
+
210
+        $template = $this->mailer->createEMailTemplate('dav.calendarInvite.' . $method, $data);
211
+        $template->addHeader();
212
+
213
+        $this->imipService->addSubjectAndHeading($template, $method, $data['invitee_name'], $data['meeting_title'], $isModified, $replyingAttendee);
214
+        $this->imipService->addBulletList($template, $vEvent, $data);
215
+
216
+        // Only add response buttons to invitation requests: Fix Issue #11230
217
+        if (strcasecmp($method, self::METHOD_REQUEST) === 0 && $this->imipService->getAttendeeRsvpOrReqForParticipant($attendee)) {
218
+
219
+            /*
220 220
 			** Only offer invitation accept/reject buttons, which link back to the
221 221
 			** nextcloud server, to recipients who can access the nextcloud server via
222 222
 			** their internet/intranet.  Issue #12156
@@ -235,106 +235,106 @@  discard block
 block discarded – undo
235 235
 			** To suppress URLs entirely, set invitation_link_recipients to boolean "no".
236 236
 			*/
237 237
 
238
-			$recipientDomain = substr(strrchr($recipient, '@'), 1);
239
-			$invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getValueString('dav', 'invitation_link_recipients', 'yes'))));
240
-
241
-			if (strcmp('yes', $invitationLinkRecipients[0]) === 0
242
-				|| in_array(strtolower($recipient), $invitationLinkRecipients)
243
-				|| in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
244
-				$token = $this->imipService->createInvitationToken($iTipMessage, $vEvent, $lastOccurrence);
245
-				$this->imipService->addResponseButtons($template, $token);
246
-				$this->imipService->addMoreOptionsButton($template, $token);
247
-			}
248
-		}
249
-
250
-		$template->addFooter();
251
-		// convert iTip Message to string
252
-		$itip_msg = $iTipMessage->message->serialize();
253
-
254
-		$mailService = null;
255
-
256
-		try {
257
-			if ($this->config->getValueBool('core', 'mail_providers_enabled', true)) {
258
-				// retrieve user object
259
-				$user = $this->userSession->getUser();
260
-				if ($user !== null) {
261
-					// retrieve appropriate service with the same address as sender
262
-					$mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
263
-				}
264
-			}
265
-
266
-			// The display name in Nextcloud can use utf-8.
267
-			// As the default charset for text/* is us-ascii, it's important to explicitly define it.
268
-			// See https://www.rfc-editor.org/rfc/rfc6047.html#section-2.4.
269
-			$contentType = 'text/calendar; method=' . $iTipMessage->method . '; charset="utf-8"';
270
-
271
-			// evaluate if a mail service was found and has sending capabilities
272
-			if ($mailService instanceof IMessageSend) {
273
-				// construct mail message and set required parameters
274
-				$message = $mailService->initiateMessage();
275
-				$message->setFrom(
276
-					(new Address($sender, $fromName))
277
-				);
278
-				$message->setTo(
279
-					(new Address($recipient, $recipientName))
280
-				);
281
-				$message->setSubject($template->renderSubject());
282
-				$message->setBodyPlain($template->renderText());
283
-				$message->setBodyHtml($template->renderHtml());
284
-				// Adding name=event.ics is a trick to make the invitation also appear
285
-				// as a file attachment in mail clients like Thunderbird or Evolution.
286
-				$message->setAttachments((new Attachment(
287
-					$itip_msg,
288
-					null,
289
-					$contentType . '; name=event.ics',
290
-					true
291
-				)));
292
-				// send message
293
-				$mailService->sendMessage($message);
294
-			} else {
295
-				// construct symfony mailer message and set required parameters
296
-				$message = $this->mailer->createMessage();
297
-				$message->setFrom([$fromEMail => $fromName]);
298
-				$message->setTo(
299
-					(($recipientName !== null) ? [$recipient => $recipientName] : [$recipient])
300
-				);
301
-				$message->setReplyTo(
302
-					(($senderName !== null) ? [$sender => $senderName] : [$sender])
303
-				);
304
-				$message->useTemplate($template);
305
-				// Using a different content type because Symfony Mailer/Mime will append the name to
306
-				// the content type header and attachInline does not allow null.
307
-				$message->attachInline(
308
-					$itip_msg,
309
-					'event.ics',
310
-					$contentType,
311
-				);
312
-				$failed = $this->mailer->send($message);
313
-			}
314
-
315
-			$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
316
-			if (!empty($failed)) {
317
-				$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
318
-				$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
319
-			}
320
-		} catch (\Exception $ex) {
321
-			$this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
322
-			$iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
323
-		}
324
-	}
325
-
326
-	/**
327
-	 * @return ?VCalendar
328
-	 */
329
-	public function getVCalendar(): ?VCalendar {
330
-		return $this->vCalendar;
331
-	}
332
-
333
-	/**
334
-	 * @param ?VCalendar $vCalendar
335
-	 */
336
-	public function setVCalendar(?VCalendar $vCalendar): void {
337
-		$this->vCalendar = $vCalendar;
338
-	}
238
+            $recipientDomain = substr(strrchr($recipient, '@'), 1);
239
+            $invitationLinkRecipients = explode(',', preg_replace('/\s+/', '', strtolower($this->config->getValueString('dav', 'invitation_link_recipients', 'yes'))));
240
+
241
+            if (strcmp('yes', $invitationLinkRecipients[0]) === 0
242
+                || in_array(strtolower($recipient), $invitationLinkRecipients)
243
+                || in_array(strtolower($recipientDomain), $invitationLinkRecipients)) {
244
+                $token = $this->imipService->createInvitationToken($iTipMessage, $vEvent, $lastOccurrence);
245
+                $this->imipService->addResponseButtons($template, $token);
246
+                $this->imipService->addMoreOptionsButton($template, $token);
247
+            }
248
+        }
249
+
250
+        $template->addFooter();
251
+        // convert iTip Message to string
252
+        $itip_msg = $iTipMessage->message->serialize();
253
+
254
+        $mailService = null;
255
+
256
+        try {
257
+            if ($this->config->getValueBool('core', 'mail_providers_enabled', true)) {
258
+                // retrieve user object
259
+                $user = $this->userSession->getUser();
260
+                if ($user !== null) {
261
+                    // retrieve appropriate service with the same address as sender
262
+                    $mailService = $this->mailManager->findServiceByAddress($user->getUID(), $sender);
263
+                }
264
+            }
265
+
266
+            // The display name in Nextcloud can use utf-8.
267
+            // As the default charset for text/* is us-ascii, it's important to explicitly define it.
268
+            // See https://www.rfc-editor.org/rfc/rfc6047.html#section-2.4.
269
+            $contentType = 'text/calendar; method=' . $iTipMessage->method . '; charset="utf-8"';
270
+
271
+            // evaluate if a mail service was found and has sending capabilities
272
+            if ($mailService instanceof IMessageSend) {
273
+                // construct mail message and set required parameters
274
+                $message = $mailService->initiateMessage();
275
+                $message->setFrom(
276
+                    (new Address($sender, $fromName))
277
+                );
278
+                $message->setTo(
279
+                    (new Address($recipient, $recipientName))
280
+                );
281
+                $message->setSubject($template->renderSubject());
282
+                $message->setBodyPlain($template->renderText());
283
+                $message->setBodyHtml($template->renderHtml());
284
+                // Adding name=event.ics is a trick to make the invitation also appear
285
+                // as a file attachment in mail clients like Thunderbird or Evolution.
286
+                $message->setAttachments((new Attachment(
287
+                    $itip_msg,
288
+                    null,
289
+                    $contentType . '; name=event.ics',
290
+                    true
291
+                )));
292
+                // send message
293
+                $mailService->sendMessage($message);
294
+            } else {
295
+                // construct symfony mailer message and set required parameters
296
+                $message = $this->mailer->createMessage();
297
+                $message->setFrom([$fromEMail => $fromName]);
298
+                $message->setTo(
299
+                    (($recipientName !== null) ? [$recipient => $recipientName] : [$recipient])
300
+                );
301
+                $message->setReplyTo(
302
+                    (($senderName !== null) ? [$sender => $senderName] : [$sender])
303
+                );
304
+                $message->useTemplate($template);
305
+                // Using a different content type because Symfony Mailer/Mime will append the name to
306
+                // the content type header and attachInline does not allow null.
307
+                $message->attachInline(
308
+                    $itip_msg,
309
+                    'event.ics',
310
+                    $contentType,
311
+                );
312
+                $failed = $this->mailer->send($message);
313
+            }
314
+
315
+            $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
316
+            if (!empty($failed)) {
317
+                $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
318
+                $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
319
+            }
320
+        } catch (\Exception $ex) {
321
+            $this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
322
+            $iTipMessage->scheduleStatus = '5.0; EMail delivery failed';
323
+        }
324
+    }
325
+
326
+    /**
327
+     * @return ?VCalendar
328
+     */
329
+    public function getVCalendar(): ?VCalendar {
330
+        return $this->vCalendar;
331
+    }
332
+
333
+    /**
334
+     * @param ?VCalendar $vCalendar
335
+     */
336
+    public function setVCalendar(?VCalendar $vCalendar): void {
337
+        $this->vCalendar = $vCalendar;
338
+    }
339 339
 
340 340
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Reminder/NotificationProvider/EmailProvider.php 1 patch
Indentation   +410 added lines, -410 removed lines patch added patch discarded remove patch
@@ -31,414 +31,414 @@
 block discarded – undo
31 31
  * @package OCA\DAV\CalDAV\Reminder\NotificationProvider
32 32
  */
33 33
 class EmailProvider extends AbstractProvider {
34
-	/** @var string */
35
-	public const NOTIFICATION_TYPE = 'EMAIL';
36
-
37
-	public function __construct(
38
-		IConfig $config,
39
-		private IMailer $mailer,
40
-		LoggerInterface $logger,
41
-		L10NFactory $l10nFactory,
42
-		IURLGenerator $urlGenerator,
43
-		private IEmailValidator $emailValidator,
44
-	) {
45
-		parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
46
-	}
47
-
48
-	/**
49
-	 * Send out notification via email
50
-	 *
51
-	 * @param VEvent $vevent
52
-	 * @param string|null $calendarDisplayName
53
-	 * @param string[] $principalEmailAddresses
54
-	 * @param array $users
55
-	 * @throws \Exception
56
-	 */
57
-	public function send(VEvent $vevent,
58
-		?string $calendarDisplayName,
59
-		array $principalEmailAddresses,
60
-		array $users = []):void {
61
-		$fallbackLanguage = $this->getFallbackLanguage();
62
-
63
-		$organizerEmailAddress = null;
64
-		if (isset($vevent->ORGANIZER)) {
65
-			$organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER);
66
-		}
67
-
68
-		$emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users);
69
-		$emailAddressesOfAttendees = [];
70
-		if (count($principalEmailAddresses) === 0
71
-			|| ($organizerEmailAddress && in_array($organizerEmailAddress, $principalEmailAddresses, true))
72
-		) {
73
-			$emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent);
74
-		}
75
-
76
-		// Quote from php.net:
77
-		// If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.
78
-		// => if there are duplicate email addresses, it will always take the system value
79
-		$emailAddresses = array_merge(
80
-			$emailAddressesOfAttendees,
81
-			$emailAddressesOfSharees
82
-		);
83
-
84
-		$sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage);
85
-		$organizer = $this->getOrganizerEMailAndNameFromEvent($vevent);
86
-
87
-		foreach ($sortedByLanguage as $lang => $emailAddresses) {
88
-			if (!$this->hasL10NForLang($lang)) {
89
-				$lang = $fallbackLanguage;
90
-			}
91
-			$l10n = $this->getL10NForLang($lang);
92
-			$fromEMail = Util::getDefaultEmailAddress('reminders-noreply');
93
-
94
-			$template = $this->mailer->createEMailTemplate('dav.calendarReminder');
95
-			$template->addHeader();
96
-			$this->addSubjectAndHeading($template, $l10n, $vevent);
97
-			$this->addBulletList($template, $l10n, $calendarDisplayName ?? $this->getCalendarDisplayNameFallback($lang), $vevent);
98
-			$template->addFooter();
99
-
100
-			foreach ($emailAddresses as $emailAddress) {
101
-				if (!$this->emailValidator->isValid($emailAddress)) {
102
-					$this->logger->error('Email address {address} for reminder notification is incorrect', ['app' => 'dav', 'address' => $emailAddress]);
103
-					continue;
104
-				}
105
-
106
-				$message = $this->mailer->createMessage();
107
-				$message->setFrom([$fromEMail]);
108
-				if ($organizer) {
109
-					$message->setReplyTo($organizer);
110
-				}
111
-				$message->setTo([$emailAddress]);
112
-				$message->useTemplate($template);
113
-				$message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED);
114
-
115
-				try {
116
-					$failed = $this->mailer->send($message);
117
-					if ($failed) {
118
-						$this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
119
-					}
120
-				} catch (\Exception $ex) {
121
-					$this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
122
-				}
123
-			}
124
-		}
125
-	}
126
-
127
-	/**
128
-	 * @param IEMailTemplate $template
129
-	 * @param IL10N $l10n
130
-	 * @param VEvent $vevent
131
-	 */
132
-	private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void {
133
-		$template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n));
134
-		$template->addHeading($this->getTitleFromVEvent($vevent, $l10n));
135
-	}
136
-
137
-	/**
138
-	 * @param IEMailTemplate $template
139
-	 * @param IL10N $l10n
140
-	 * @param string $calendarDisplayName
141
-	 * @param array $eventData
142
-	 */
143
-	private function addBulletList(IEMailTemplate $template,
144
-		IL10N $l10n,
145
-		string $calendarDisplayName,
146
-		VEvent $vevent):void {
147
-		$template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
148
-			$this->getAbsoluteImagePath('actions/info.png'));
149
-
150
-		$template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
151
-			$this->getAbsoluteImagePath('places/calendar.png'));
152
-
153
-		if (isset($vevent->LOCATION)) {
154
-			$template->addBodyListItem((string)$vevent->LOCATION, $l10n->t('Where:'),
155
-				$this->getAbsoluteImagePath('actions/address.png'));
156
-		}
157
-		if (isset($vevent->DESCRIPTION)) {
158
-			$template->addBodyListItem((string)$vevent->DESCRIPTION, $l10n->t('Description:'),
159
-				$this->getAbsoluteImagePath('actions/more.png'));
160
-		}
161
-	}
162
-
163
-	private function getAbsoluteImagePath(string $path):string {
164
-		return $this->urlGenerator->getAbsoluteURL(
165
-			$this->urlGenerator->imagePath('core', $path)
166
-		);
167
-	}
168
-
169
-	/**
170
-	 * @param VEvent $vevent
171
-	 * @return array|null
172
-	 */
173
-	private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array {
174
-		if (!$vevent->ORGANIZER) {
175
-			return null;
176
-		}
177
-
178
-		$organizer = $vevent->ORGANIZER;
179
-		if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) {
180
-			return null;
181
-		}
182
-
183
-		$organizerEMail = substr($organizer->getValue(), 7);
184
-
185
-		if (!$this->emailValidator->isValid($organizerEMail)) {
186
-			return null;
187
-		}
188
-
189
-		$name = $organizer->offsetGet('CN');
190
-		if ($name instanceof Parameter) {
191
-			return [$organizerEMail => $name];
192
-		}
193
-
194
-		return [$organizerEMail];
195
-	}
196
-
197
-	/**
198
-	 * @param array<string, array{LANG?: string}> $emails
199
-	 * @return array<string, string[]>
200
-	 */
201
-	private function sortEMailAddressesByLanguage(array $emails,
202
-		string $defaultLanguage):array {
203
-		$sortedByLanguage = [];
204
-
205
-		foreach ($emails as $emailAddress => $parameters) {
206
-			if (isset($parameters['LANG'])) {
207
-				$lang = $parameters['LANG'];
208
-			} else {
209
-				$lang = $defaultLanguage;
210
-			}
211
-
212
-			if (!isset($sortedByLanguage[$lang])) {
213
-				$sortedByLanguage[$lang] = [];
214
-			}
215
-
216
-			$sortedByLanguage[$lang][] = $emailAddress;
217
-		}
218
-
219
-		return $sortedByLanguage;
220
-	}
221
-
222
-	/**
223
-	 * @param VEvent $vevent
224
-	 * @return array<string, array{LANG?: string}>
225
-	 */
226
-	private function getAllEMailAddressesFromEvent(VEvent $vevent):array {
227
-		$emailAddresses = [];
228
-
229
-		if (isset($vevent->ATTENDEE)) {
230
-			foreach ($vevent->ATTENDEE as $attendee) {
231
-				if (!($attendee instanceof VObject\Property)) {
232
-					continue;
233
-				}
234
-
235
-				$cuType = $this->getCUTypeOfAttendee($attendee);
236
-				if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) {
237
-					// Don't send emails to things
238
-					continue;
239
-				}
240
-
241
-				$partstat = $this->getPartstatOfAttendee($attendee);
242
-				if ($partstat === 'DECLINED') {
243
-					// Don't send out emails to people who declined
244
-					continue;
245
-				}
246
-				if ($partstat === 'DELEGATED') {
247
-					$delegates = $attendee->offsetGet('DELEGATED-TO');
248
-					if (!($delegates instanceof VObject\Parameter)) {
249
-						continue;
250
-					}
251
-
252
-					$emailAddressesOfDelegates = $delegates->getParts();
253
-					foreach ($emailAddressesOfDelegates as $addressesOfDelegate) {
254
-						if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) {
255
-							$delegateEmail = substr($addressesOfDelegate, 7);
256
-							if ($this->emailValidator->isValid($delegateEmail)) {
257
-								$emailAddresses[$delegateEmail] = [];
258
-							}
259
-						}
260
-					}
261
-
262
-					continue;
263
-				}
264
-
265
-				$emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee);
266
-				if ($emailAddressOfAttendee !== null) {
267
-					$properties = [];
268
-
269
-					$langProp = $attendee->offsetGet('LANG');
270
-					if ($langProp instanceof VObject\Parameter && $langProp->getValue() !== null) {
271
-						$properties['LANG'] = $langProp->getValue();
272
-					}
273
-
274
-					$emailAddresses[$emailAddressOfAttendee] = $properties;
275
-				}
276
-			}
277
-		}
278
-
279
-		if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) {
280
-			$organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER);
281
-			if ($organizerEmailAddress !== null) {
282
-				$emailAddresses[$organizerEmailAddress] = [];
283
-			}
284
-		}
285
-
286
-		return $emailAddresses;
287
-	}
288
-
289
-	private function getCUTypeOfAttendee(VObject\Property $attendee):string {
290
-		$cuType = $attendee->offsetGet('CUTYPE');
291
-		if ($cuType instanceof VObject\Parameter) {
292
-			return strtoupper($cuType->getValue());
293
-		}
294
-
295
-		return 'INDIVIDUAL';
296
-	}
297
-
298
-	private function getPartstatOfAttendee(VObject\Property $attendee):string {
299
-		$partstat = $attendee->offsetGet('PARTSTAT');
300
-		if ($partstat instanceof VObject\Parameter) {
301
-			return strtoupper($partstat->getValue());
302
-		}
303
-
304
-		return 'NEEDS-ACTION';
305
-	}
306
-
307
-	private function hasAttendeeMailURI(VObject\Property $attendee): bool {
308
-		return stripos($attendee->getValue(), 'mailto:') === 0;
309
-	}
310
-
311
-	private function getEMailAddressOfAttendee(VObject\Property $attendee): ?string {
312
-		if (!$this->hasAttendeeMailURI($attendee)) {
313
-			return null;
314
-		}
315
-		$attendeeEMail = substr($attendee->getValue(), 7);
316
-		if (!$this->emailValidator->isValid($attendeeEMail)) {
317
-			return null;
318
-		}
319
-
320
-		return $attendeeEMail;
321
-	}
322
-
323
-	/**
324
-	 * @param IUser[] $users
325
-	 * @return array<string, array{LANG?: string}>
326
-	 */
327
-	private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array {
328
-		$emailAddresses = [];
329
-
330
-		foreach ($users as $user) {
331
-			$emailAddress = $user->getEMailAddress();
332
-			if ($emailAddress) {
333
-				$lang = $this->l10nFactory->getUserLanguage($user);
334
-				if ($lang) {
335
-					$emailAddresses[$emailAddress] = [
336
-						'LANG' => $lang,
337
-					];
338
-				} else {
339
-					$emailAddresses[$emailAddress] = [];
340
-				}
341
-			}
342
-		}
343
-
344
-		return $emailAddresses;
345
-	}
346
-
347
-	/**
348
-	 * @throws \Exception
349
-	 */
350
-	private function generateDateString(IL10N $l10n, VEvent $vevent): string {
351
-		$isAllDay = $vevent->DTSTART instanceof Property\ICalendar\Date;
352
-
353
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
354
-		/** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
355
-		/** @var \DateTimeImmutable $dtstartDt */
356
-		$dtstartDt = $vevent->DTSTART->getDateTime();
357
-		/** @var \DateTimeImmutable $dtendDt */
358
-		$dtendDt = $this->getDTEndFromEvent($vevent)->getDateTime();
359
-
360
-		$diff = $dtstartDt->diff($dtendDt);
361
-
362
-		$dtstartDt = new \DateTime($dtstartDt->format(\DateTimeInterface::ATOM));
363
-		$dtendDt = new \DateTime($dtendDt->format(\DateTimeInterface::ATOM));
364
-
365
-		if ($isAllDay) {
366
-			// One day event
367
-			if ($diff->days === 1) {
368
-				return $this->getDateString($l10n, $dtstartDt);
369
-			}
370
-
371
-			return implode(' - ', [
372
-				$this->getDateString($l10n, $dtstartDt),
373
-				$this->getDateString($l10n, $dtendDt),
374
-			]);
375
-		}
376
-
377
-		$startTimezone = $endTimezone = null;
378
-		if (!$vevent->DTSTART->isFloating()) {
379
-			$startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName();
380
-			$endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName();
381
-		}
382
-
383
-		$localeStart = implode(', ', [
384
-			$this->getWeekDayName($l10n, $dtstartDt),
385
-			$this->getDateTimeString($l10n, $dtstartDt)
386
-		]);
387
-
388
-		// always show full date with timezone if timezones are different
389
-		if ($startTimezone !== $endTimezone) {
390
-			$localeEnd = implode(', ', [
391
-				$this->getWeekDayName($l10n, $dtendDt),
392
-				$this->getDateTimeString($l10n, $dtendDt)
393
-			]);
394
-
395
-			return $localeStart
396
-				. ' (' . $startTimezone . ') '
397
-				. ' - '
398
-				. $localeEnd
399
-				. ' (' . $endTimezone . ')';
400
-		}
401
-
402
-		// Show only the time if the day is the same
403
-		$localeEnd = $this->isDayEqual($dtstartDt, $dtendDt)
404
-			? $this->getTimeString($l10n, $dtendDt)
405
-			: implode(', ', [
406
-				$this->getWeekDayName($l10n, $dtendDt),
407
-				$this->getDateTimeString($l10n, $dtendDt)
408
-			]);
409
-
410
-		return $localeStart
411
-			. ' - '
412
-			. $localeEnd
413
-			. ' (' . $startTimezone . ')';
414
-	}
415
-
416
-	private function isDayEqual(DateTime $dtStart,
417
-		DateTime $dtEnd):bool {
418
-		return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
419
-	}
420
-
421
-	private function getWeekDayName(IL10N $l10n, DateTime $dt):string {
422
-		return (string)$l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
423
-	}
424
-
425
-	private function getDateString(IL10N $l10n, DateTime $dt):string {
426
-		return (string)$l10n->l('date', $dt, ['width' => 'medium']);
427
-	}
428
-
429
-	private function getDateTimeString(IL10N $l10n, DateTime $dt):string {
430
-		return (string)$l10n->l('datetime', $dt, ['width' => 'medium|short']);
431
-	}
432
-
433
-	private function getTimeString(IL10N $l10n, DateTime $dt):string {
434
-		return (string)$l10n->l('time', $dt, ['width' => 'short']);
435
-	}
436
-
437
-	private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string {
438
-		if (isset($vevent->SUMMARY)) {
439
-			return (string)$vevent->SUMMARY;
440
-		}
441
-
442
-		return $l10n->t('Untitled event');
443
-	}
34
+    /** @var string */
35
+    public const NOTIFICATION_TYPE = 'EMAIL';
36
+
37
+    public function __construct(
38
+        IConfig $config,
39
+        private IMailer $mailer,
40
+        LoggerInterface $logger,
41
+        L10NFactory $l10nFactory,
42
+        IURLGenerator $urlGenerator,
43
+        private IEmailValidator $emailValidator,
44
+    ) {
45
+        parent::__construct($logger, $l10nFactory, $urlGenerator, $config);
46
+    }
47
+
48
+    /**
49
+     * Send out notification via email
50
+     *
51
+     * @param VEvent $vevent
52
+     * @param string|null $calendarDisplayName
53
+     * @param string[] $principalEmailAddresses
54
+     * @param array $users
55
+     * @throws \Exception
56
+     */
57
+    public function send(VEvent $vevent,
58
+        ?string $calendarDisplayName,
59
+        array $principalEmailAddresses,
60
+        array $users = []):void {
61
+        $fallbackLanguage = $this->getFallbackLanguage();
62
+
63
+        $organizerEmailAddress = null;
64
+        if (isset($vevent->ORGANIZER)) {
65
+            $organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER);
66
+        }
67
+
68
+        $emailAddressesOfSharees = $this->getEMailAddressesOfAllUsersWithWriteAccessToCalendar($users);
69
+        $emailAddressesOfAttendees = [];
70
+        if (count($principalEmailAddresses) === 0
71
+            || ($organizerEmailAddress && in_array($organizerEmailAddress, $principalEmailAddresses, true))
72
+        ) {
73
+            $emailAddressesOfAttendees = $this->getAllEMailAddressesFromEvent($vevent);
74
+        }
75
+
76
+        // Quote from php.net:
77
+        // If the input arrays have the same string keys, then the later value for that key will overwrite the previous one.
78
+        // => if there are duplicate email addresses, it will always take the system value
79
+        $emailAddresses = array_merge(
80
+            $emailAddressesOfAttendees,
81
+            $emailAddressesOfSharees
82
+        );
83
+
84
+        $sortedByLanguage = $this->sortEMailAddressesByLanguage($emailAddresses, $fallbackLanguage);
85
+        $organizer = $this->getOrganizerEMailAndNameFromEvent($vevent);
86
+
87
+        foreach ($sortedByLanguage as $lang => $emailAddresses) {
88
+            if (!$this->hasL10NForLang($lang)) {
89
+                $lang = $fallbackLanguage;
90
+            }
91
+            $l10n = $this->getL10NForLang($lang);
92
+            $fromEMail = Util::getDefaultEmailAddress('reminders-noreply');
93
+
94
+            $template = $this->mailer->createEMailTemplate('dav.calendarReminder');
95
+            $template->addHeader();
96
+            $this->addSubjectAndHeading($template, $l10n, $vevent);
97
+            $this->addBulletList($template, $l10n, $calendarDisplayName ?? $this->getCalendarDisplayNameFallback($lang), $vevent);
98
+            $template->addFooter();
99
+
100
+            foreach ($emailAddresses as $emailAddress) {
101
+                if (!$this->emailValidator->isValid($emailAddress)) {
102
+                    $this->logger->error('Email address {address} for reminder notification is incorrect', ['app' => 'dav', 'address' => $emailAddress]);
103
+                    continue;
104
+                }
105
+
106
+                $message = $this->mailer->createMessage();
107
+                $message->setFrom([$fromEMail]);
108
+                if ($organizer) {
109
+                    $message->setReplyTo($organizer);
110
+                }
111
+                $message->setTo([$emailAddress]);
112
+                $message->useTemplate($template);
113
+                $message->setAutoSubmitted(AutoSubmitted::VALUE_AUTO_GENERATED);
114
+
115
+                try {
116
+                    $failed = $this->mailer->send($message);
117
+                    if ($failed) {
118
+                        $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]);
119
+                    }
120
+                } catch (\Exception $ex) {
121
+                    $this->logger->error($ex->getMessage(), ['app' => 'dav', 'exception' => $ex]);
122
+                }
123
+            }
124
+        }
125
+    }
126
+
127
+    /**
128
+     * @param IEMailTemplate $template
129
+     * @param IL10N $l10n
130
+     * @param VEvent $vevent
131
+     */
132
+    private function addSubjectAndHeading(IEMailTemplate $template, IL10N $l10n, VEvent $vevent):void {
133
+        $template->setSubject('Notification: ' . $this->getTitleFromVEvent($vevent, $l10n));
134
+        $template->addHeading($this->getTitleFromVEvent($vevent, $l10n));
135
+    }
136
+
137
+    /**
138
+     * @param IEMailTemplate $template
139
+     * @param IL10N $l10n
140
+     * @param string $calendarDisplayName
141
+     * @param array $eventData
142
+     */
143
+    private function addBulletList(IEMailTemplate $template,
144
+        IL10N $l10n,
145
+        string $calendarDisplayName,
146
+        VEvent $vevent):void {
147
+        $template->addBodyListItem($calendarDisplayName, $l10n->t('Calendar:'),
148
+            $this->getAbsoluteImagePath('actions/info.png'));
149
+
150
+        $template->addBodyListItem($this->generateDateString($l10n, $vevent), $l10n->t('Date:'),
151
+            $this->getAbsoluteImagePath('places/calendar.png'));
152
+
153
+        if (isset($vevent->LOCATION)) {
154
+            $template->addBodyListItem((string)$vevent->LOCATION, $l10n->t('Where:'),
155
+                $this->getAbsoluteImagePath('actions/address.png'));
156
+        }
157
+        if (isset($vevent->DESCRIPTION)) {
158
+            $template->addBodyListItem((string)$vevent->DESCRIPTION, $l10n->t('Description:'),
159
+                $this->getAbsoluteImagePath('actions/more.png'));
160
+        }
161
+    }
162
+
163
+    private function getAbsoluteImagePath(string $path):string {
164
+        return $this->urlGenerator->getAbsoluteURL(
165
+            $this->urlGenerator->imagePath('core', $path)
166
+        );
167
+    }
168
+
169
+    /**
170
+     * @param VEvent $vevent
171
+     * @return array|null
172
+     */
173
+    private function getOrganizerEMailAndNameFromEvent(VEvent $vevent):?array {
174
+        if (!$vevent->ORGANIZER) {
175
+            return null;
176
+        }
177
+
178
+        $organizer = $vevent->ORGANIZER;
179
+        if (strcasecmp($organizer->getValue(), 'mailto:') !== 0) {
180
+            return null;
181
+        }
182
+
183
+        $organizerEMail = substr($organizer->getValue(), 7);
184
+
185
+        if (!$this->emailValidator->isValid($organizerEMail)) {
186
+            return null;
187
+        }
188
+
189
+        $name = $organizer->offsetGet('CN');
190
+        if ($name instanceof Parameter) {
191
+            return [$organizerEMail => $name];
192
+        }
193
+
194
+        return [$organizerEMail];
195
+    }
196
+
197
+    /**
198
+     * @param array<string, array{LANG?: string}> $emails
199
+     * @return array<string, string[]>
200
+     */
201
+    private function sortEMailAddressesByLanguage(array $emails,
202
+        string $defaultLanguage):array {
203
+        $sortedByLanguage = [];
204
+
205
+        foreach ($emails as $emailAddress => $parameters) {
206
+            if (isset($parameters['LANG'])) {
207
+                $lang = $parameters['LANG'];
208
+            } else {
209
+                $lang = $defaultLanguage;
210
+            }
211
+
212
+            if (!isset($sortedByLanguage[$lang])) {
213
+                $sortedByLanguage[$lang] = [];
214
+            }
215
+
216
+            $sortedByLanguage[$lang][] = $emailAddress;
217
+        }
218
+
219
+        return $sortedByLanguage;
220
+    }
221
+
222
+    /**
223
+     * @param VEvent $vevent
224
+     * @return array<string, array{LANG?: string}>
225
+     */
226
+    private function getAllEMailAddressesFromEvent(VEvent $vevent):array {
227
+        $emailAddresses = [];
228
+
229
+        if (isset($vevent->ATTENDEE)) {
230
+            foreach ($vevent->ATTENDEE as $attendee) {
231
+                if (!($attendee instanceof VObject\Property)) {
232
+                    continue;
233
+                }
234
+
235
+                $cuType = $this->getCUTypeOfAttendee($attendee);
236
+                if (\in_array($cuType, ['RESOURCE', 'ROOM', 'UNKNOWN'])) {
237
+                    // Don't send emails to things
238
+                    continue;
239
+                }
240
+
241
+                $partstat = $this->getPartstatOfAttendee($attendee);
242
+                if ($partstat === 'DECLINED') {
243
+                    // Don't send out emails to people who declined
244
+                    continue;
245
+                }
246
+                if ($partstat === 'DELEGATED') {
247
+                    $delegates = $attendee->offsetGet('DELEGATED-TO');
248
+                    if (!($delegates instanceof VObject\Parameter)) {
249
+                        continue;
250
+                    }
251
+
252
+                    $emailAddressesOfDelegates = $delegates->getParts();
253
+                    foreach ($emailAddressesOfDelegates as $addressesOfDelegate) {
254
+                        if (strcasecmp($addressesOfDelegate, 'mailto:') === 0) {
255
+                            $delegateEmail = substr($addressesOfDelegate, 7);
256
+                            if ($this->emailValidator->isValid($delegateEmail)) {
257
+                                $emailAddresses[$delegateEmail] = [];
258
+                            }
259
+                        }
260
+                    }
261
+
262
+                    continue;
263
+                }
264
+
265
+                $emailAddressOfAttendee = $this->getEMailAddressOfAttendee($attendee);
266
+                if ($emailAddressOfAttendee !== null) {
267
+                    $properties = [];
268
+
269
+                    $langProp = $attendee->offsetGet('LANG');
270
+                    if ($langProp instanceof VObject\Parameter && $langProp->getValue() !== null) {
271
+                        $properties['LANG'] = $langProp->getValue();
272
+                    }
273
+
274
+                    $emailAddresses[$emailAddressOfAttendee] = $properties;
275
+                }
276
+            }
277
+        }
278
+
279
+        if (isset($vevent->ORGANIZER) && $this->hasAttendeeMailURI($vevent->ORGANIZER)) {
280
+            $organizerEmailAddress = $this->getEMailAddressOfAttendee($vevent->ORGANIZER);
281
+            if ($organizerEmailAddress !== null) {
282
+                $emailAddresses[$organizerEmailAddress] = [];
283
+            }
284
+        }
285
+
286
+        return $emailAddresses;
287
+    }
288
+
289
+    private function getCUTypeOfAttendee(VObject\Property $attendee):string {
290
+        $cuType = $attendee->offsetGet('CUTYPE');
291
+        if ($cuType instanceof VObject\Parameter) {
292
+            return strtoupper($cuType->getValue());
293
+        }
294
+
295
+        return 'INDIVIDUAL';
296
+    }
297
+
298
+    private function getPartstatOfAttendee(VObject\Property $attendee):string {
299
+        $partstat = $attendee->offsetGet('PARTSTAT');
300
+        if ($partstat instanceof VObject\Parameter) {
301
+            return strtoupper($partstat->getValue());
302
+        }
303
+
304
+        return 'NEEDS-ACTION';
305
+    }
306
+
307
+    private function hasAttendeeMailURI(VObject\Property $attendee): bool {
308
+        return stripos($attendee->getValue(), 'mailto:') === 0;
309
+    }
310
+
311
+    private function getEMailAddressOfAttendee(VObject\Property $attendee): ?string {
312
+        if (!$this->hasAttendeeMailURI($attendee)) {
313
+            return null;
314
+        }
315
+        $attendeeEMail = substr($attendee->getValue(), 7);
316
+        if (!$this->emailValidator->isValid($attendeeEMail)) {
317
+            return null;
318
+        }
319
+
320
+        return $attendeeEMail;
321
+    }
322
+
323
+    /**
324
+     * @param IUser[] $users
325
+     * @return array<string, array{LANG?: string}>
326
+     */
327
+    private function getEMailAddressesOfAllUsersWithWriteAccessToCalendar(array $users):array {
328
+        $emailAddresses = [];
329
+
330
+        foreach ($users as $user) {
331
+            $emailAddress = $user->getEMailAddress();
332
+            if ($emailAddress) {
333
+                $lang = $this->l10nFactory->getUserLanguage($user);
334
+                if ($lang) {
335
+                    $emailAddresses[$emailAddress] = [
336
+                        'LANG' => $lang,
337
+                    ];
338
+                } else {
339
+                    $emailAddresses[$emailAddress] = [];
340
+                }
341
+            }
342
+        }
343
+
344
+        return $emailAddresses;
345
+    }
346
+
347
+    /**
348
+     * @throws \Exception
349
+     */
350
+    private function generateDateString(IL10N $l10n, VEvent $vevent): string {
351
+        $isAllDay = $vevent->DTSTART instanceof Property\ICalendar\Date;
352
+
353
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtstart */
354
+        /** @var Property\ICalendar\Date | Property\ICalendar\DateTime $dtend */
355
+        /** @var \DateTimeImmutable $dtstartDt */
356
+        $dtstartDt = $vevent->DTSTART->getDateTime();
357
+        /** @var \DateTimeImmutable $dtendDt */
358
+        $dtendDt = $this->getDTEndFromEvent($vevent)->getDateTime();
359
+
360
+        $diff = $dtstartDt->diff($dtendDt);
361
+
362
+        $dtstartDt = new \DateTime($dtstartDt->format(\DateTimeInterface::ATOM));
363
+        $dtendDt = new \DateTime($dtendDt->format(\DateTimeInterface::ATOM));
364
+
365
+        if ($isAllDay) {
366
+            // One day event
367
+            if ($diff->days === 1) {
368
+                return $this->getDateString($l10n, $dtstartDt);
369
+            }
370
+
371
+            return implode(' - ', [
372
+                $this->getDateString($l10n, $dtstartDt),
373
+                $this->getDateString($l10n, $dtendDt),
374
+            ]);
375
+        }
376
+
377
+        $startTimezone = $endTimezone = null;
378
+        if (!$vevent->DTSTART->isFloating()) {
379
+            $startTimezone = $vevent->DTSTART->getDateTime()->getTimezone()->getName();
380
+            $endTimezone = $this->getDTEndFromEvent($vevent)->getDateTime()->getTimezone()->getName();
381
+        }
382
+
383
+        $localeStart = implode(', ', [
384
+            $this->getWeekDayName($l10n, $dtstartDt),
385
+            $this->getDateTimeString($l10n, $dtstartDt)
386
+        ]);
387
+
388
+        // always show full date with timezone if timezones are different
389
+        if ($startTimezone !== $endTimezone) {
390
+            $localeEnd = implode(', ', [
391
+                $this->getWeekDayName($l10n, $dtendDt),
392
+                $this->getDateTimeString($l10n, $dtendDt)
393
+            ]);
394
+
395
+            return $localeStart
396
+                . ' (' . $startTimezone . ') '
397
+                . ' - '
398
+                . $localeEnd
399
+                . ' (' . $endTimezone . ')';
400
+        }
401
+
402
+        // Show only the time if the day is the same
403
+        $localeEnd = $this->isDayEqual($dtstartDt, $dtendDt)
404
+            ? $this->getTimeString($l10n, $dtendDt)
405
+            : implode(', ', [
406
+                $this->getWeekDayName($l10n, $dtendDt),
407
+                $this->getDateTimeString($l10n, $dtendDt)
408
+            ]);
409
+
410
+        return $localeStart
411
+            . ' - '
412
+            . $localeEnd
413
+            . ' (' . $startTimezone . ')';
414
+    }
415
+
416
+    private function isDayEqual(DateTime $dtStart,
417
+        DateTime $dtEnd):bool {
418
+        return $dtStart->format('Y-m-d') === $dtEnd->format('Y-m-d');
419
+    }
420
+
421
+    private function getWeekDayName(IL10N $l10n, DateTime $dt):string {
422
+        return (string)$l10n->l('weekdayName', $dt, ['width' => 'abbreviated']);
423
+    }
424
+
425
+    private function getDateString(IL10N $l10n, DateTime $dt):string {
426
+        return (string)$l10n->l('date', $dt, ['width' => 'medium']);
427
+    }
428
+
429
+    private function getDateTimeString(IL10N $l10n, DateTime $dt):string {
430
+        return (string)$l10n->l('datetime', $dt, ['width' => 'medium|short']);
431
+    }
432
+
433
+    private function getTimeString(IL10N $l10n, DateTime $dt):string {
434
+        return (string)$l10n->l('time', $dt, ['width' => 'short']);
435
+    }
436
+
437
+    private function getTitleFromVEvent(VEvent $vevent, IL10N $l10n):string {
438
+        if (isset($vevent->SUMMARY)) {
439
+            return (string)$vevent->SUMMARY;
440
+        }
441
+
442
+        return $l10n->t('Untitled event');
443
+    }
444 444
 }
Please login to merge, or discard this patch.