Completed
Push — master ( fb0b56...db3e84 )
by Daniel
27:54
created
lib/public/Util.php 2 patches
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.
Spacing   +13 added lines, -13 removed lines patch added patch discarded remove patch
@@ -261,9 +261,9 @@  discard block
 block discarded – undo
261 261
 	 */
262 262
 	public static function linkToRemote($service) {
263 263
 		$urlGenerator = \OCP\Server::get(IURLGenerator::class);
264
-		$remoteBase = $urlGenerator->linkTo('', 'remote.php') . '/' . $service;
264
+		$remoteBase = $urlGenerator->linkTo('', 'remote.php').'/'.$service;
265 265
 		return $urlGenerator->getAbsoluteURL(
266
-			$remoteBase . (($service[strlen($service) - 1] != '/') ? '/' : '')
266
+			$remoteBase.(($service[strlen($service) - 1] != '/') ? '/' : '')
267 267
 		);
268 268
 	}
269 269
 
@@ -303,7 +303,7 @@  discard block
 block discarded – undo
303 303
 		$user_part = $config->getSystemValueString('mail_from_address', $user_part);
304 304
 		$host_name = self::getServerHostName();
305 305
 		$host_name = $config->getSystemValueString('mail_domain', $host_name);
306
-		$defaultEmailAddress = $user_part . '@' . $host_name;
306
+		$defaultEmailAddress = $user_part.'@'.$host_name;
307 307
 
308 308
 		$emailValidator = \OCP\Server::get(IEmailValidator::class);
309 309
 		if ($emailValidator->isValid($defaultEmailAddress)) {
@@ -311,7 +311,7 @@  discard block
 block discarded – undo
311 311
 		}
312 312
 
313 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';
314
+		return $user_part.'@localhost.localdomain';
315 315
 	}
316 316
 
317 317
 	/**
@@ -320,9 +320,9 @@  discard block
 block discarded – undo
320 320
 	 * @return int|float int if it fits, float if it is too big
321 321
 	 * @since 26.0.0
322 322
 	 */
323
-	public static function numericToNumber(string|float|int $number): int|float {
323
+	public static function numericToNumber(string | float | int $number): int | float {
324 324
 		/* This is a hack to cast to (int|float) */
325
-		return 0 + (string)$number;
325
+		return 0 + (string) $number;
326 326
 	}
327 327
 
328 328
 	/**
@@ -331,7 +331,7 @@  discard block
 block discarded – undo
331 331
 	 * @return string a human readable file size
332 332
 	 * @since 4.0.0
333 333
 	 */
334
-	public static function humanFileSize(int|float $bytes): string {
334
+	public static function humanFileSize(int | float $bytes): string {
335 335
 		if ($bytes < 0) {
336 336
 			return '?';
337 337
 		}
@@ -367,7 +367,7 @@  discard block
 block discarded – undo
367 367
 	 * @return false|int|float a file size in bytes
368 368
 	 * @since 4.0.0
369 369
 	 */
370
-	public static function computerFileSize(string $str): false|int|float {
370
+	public static function computerFileSize(string $str): false | int | float {
371 371
 		$str = strtolower($str);
372 372
 		if (is_numeric($str)) {
373 373
 			return Util::numericToNumber($str);
@@ -387,7 +387,7 @@  discard block
 block discarded – undo
387 387
 			'p' => 1024 * 1024 * 1024 * 1024 * 1024,
388 388
 		];
389 389
 
390
-		$bytes = (float)$str;
390
+		$bytes = (float) $str;
391 391
 
392 392
 		if (preg_match('#([kmgtp]?b?)$#si', $str, $matches) && isset($bytes_array[$matches[1]])) {
393 393
 			$bytes *= $bytes_array[$matches[1]];
@@ -532,7 +532,7 @@  discard block
 block discarded – undo
532 532
 	 * @return int|float number of bytes representing
533 533
 	 * @since 5.0.0
534 534
 	 */
535
-	public static function maxUploadFilesize(string $dir, int|float|null $free = null): int|float {
535
+	public static function maxUploadFilesize(string $dir, int | float | null $free = null): int | float {
536 536
 		if (is_null($free) || $free < 0) {
537 537
 			$free = self::freeSpace($dir);
538 538
 		}
@@ -545,13 +545,13 @@  discard block
 block discarded – undo
545 545
 	 * @return int|float number of bytes representing
546 546
 	 * @since 7.0.0
547 547
 	 */
548
-	public static function freeSpace(string $dir): int|float {
548
+	public static function freeSpace(string $dir): int | float {
549 549
 		$freeSpace = \OC\Files\Filesystem::free_space($dir);
550 550
 		if ($freeSpace < \OCP\Files\FileInfo::SPACE_UNLIMITED) {
551 551
 			$freeSpace = max($freeSpace, 0);
552 552
 			return $freeSpace;
553 553
 		} else {
554
-			return (INF > 0)? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
554
+			return (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
555 555
 		}
556 556
 	}
557 557
 
@@ -561,7 +561,7 @@  discard block
 block discarded – undo
561 561
 	 * @return int|float number of bytes representing
562 562
 	 * @since 7.0.0
563 563
 	 */
564
-	public static function uploadLimit(): int|float {
564
+	public static function uploadLimit(): int | float {
565 565
 		$ini = Server::get(IniGetWrapper::class);
566 566
 		$upload_max_filesize = self::computerFileSize($ini->get('upload_max_filesize')) ?: 0;
567 567
 		$post_max_size = self::computerFileSize($ini->get('post_max_size')) ?: 0;
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 2 patches
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.
Spacing   +34 added lines, -34 removed lines patch added patch discarded remove patch
@@ -158,7 +158,7 @@  discard block
 block discarded – undo
158 158
 			[$userFolder->getRelativePath($share->getNode()->getPath()), $share->getSharedWith()],
159 159
 			$share->getSharedBy(),
160 160
 			$share->getNode()->getId(),
161
-			(string)$userFolder->getRelativePath($share->getNode()->getPath())
161
+			(string) $userFolder->getRelativePath($share->getNode()->getPath())
162 162
 		);
163 163
 
164 164
 		if ($share->getShareOwner() !== $share->getSharedBy()) {
@@ -171,7 +171,7 @@  discard block
 block discarded – undo
171 171
 				[$ownerFolder->getRelativePath($ownerPath), $share->getSharedWith(), $share->getSharedBy()],
172 172
 				$share->getShareOwner(),
173 173
 				$fileId,
174
-				(string)$ownerFolder->getRelativePath($ownerPath)
174
+				(string) $ownerFolder->getRelativePath($ownerPath)
175 175
 			);
176 176
 		}
177 177
 	}
@@ -188,7 +188,7 @@  discard block
 block discarded – undo
188 188
 				[$userFolder->getRelativePath($share->getNode()->getPath())],
189 189
 				$share->getSharedBy(),
190 190
 				$share->getNode()->getId(),
191
-				(string)$userFolder->getRelativePath($share->getNode()->getPath())
191
+				(string) $userFolder->getRelativePath($share->getNode()->getPath())
192 192
 			);
193 193
 		} else {
194 194
 			$this->publishActivity(
@@ -196,7 +196,7 @@  discard block
 block discarded – undo
196 196
 				[$userFolder->getRelativePath($share->getNode()->getPath()), $sharedWith],
197 197
 				$share->getSharedBy(),
198 198
 				$share->getNode()->getId(),
199
-				(string)$userFolder->getRelativePath($share->getNode()->getPath())
199
+				(string) $userFolder->getRelativePath($share->getNode()->getPath())
200 200
 			);
201 201
 		}
202 202
 	}
@@ -247,15 +247,15 @@  discard block
 block discarded – undo
247 247
 		$shareId = $share->getId();
248 248
 
249 249
 		$emails = $this->getSharedWithEmails($share);
250
-		$validEmails = array_filter($emails, function (string $email) {
250
+		$validEmails = array_filter($emails, function(string $email) {
251 251
 			return $this->emailValidator->isValid($email);
252 252
 		});
253 253
 
254 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),
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 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), [
258
+			$this->logger->error('Failed to send share by mail. Could not find a valid email address '.join(', ', $emails), [
259 259
 				'app' => 'sharebymail',
260 260
 				'exception' => $e,
261 261
 			]);
@@ -287,14 +287,14 @@  discard block
 block discarded – undo
287 287
 				'app' => 'sharebymail',
288 288
 				'exception' => $hintException,
289 289
 			]);
290
-			$this->removeShareFromTable((int)$shareId);
290
+			$this->removeShareFromTable((int) $shareId);
291 291
 			throw $hintException;
292 292
 		} catch (\Exception $e) {
293 293
 			$this->logger->error('Failed to send share by mail.', [
294 294
 				'app' => 'sharebymail',
295 295
 				'exception' => $e,
296 296
 			]);
297
-			$this->removeShareFromTable((int)$shareId);
297
+			$this->removeShareFromTable((int) $shareId);
298 298
 			throw new HintException(
299 299
 				'Failed to send share by mail',
300 300
 				$this->l->t('Failed to send share by email'),
@@ -347,7 +347,7 @@  discard block
 block discarded – undo
347 347
 		}
348 348
 
349 349
 		if ($expiration !== null) {
350
-			$dateString = (string)$this->l->l('date', $expiration, ['width' => 'medium']);
350
+			$dateString = (string) $this->l->l('date', $expiration, ['width' => 'medium']);
351 351
 			$emailTemplate->addBodyListItem(
352 352
 				$this->l->t('This share is valid until %s at midnight', [$dateString]),
353 353
 				$this->l->t('Expiration:'),
@@ -388,7 +388,7 @@  discard block
 block discarded – undo
388 388
 			$initiatorEmail = $initiatorUser->getEMailAddress();
389 389
 			if ($initiatorEmail !== null) {
390 390
 				$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
391
-				$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
391
+				$emailTemplate->addFooter($instanceName.($this->defaults->getSlogan() !== '' ? ' - '.$this->defaults->getSlogan() : ''));
392 392
 			} else {
393 393
 				$emailTemplate->addFooter();
394 394
 			}
@@ -399,7 +399,7 @@  discard block
 block discarded – undo
399 399
 		$message->useTemplate($emailTemplate);
400 400
 		$failedRecipients = $this->mailer->send($message);
401 401
 		if (!empty($failedRecipients)) {
402
-			$this->logger->error('Share notification mail could not be sent to: ' . implode(', ', $failedRecipients));
402
+			$this->logger->error('Share notification mail could not be sent to: '.implode(', ', $failedRecipients));
403 403
 			return;
404 404
 		}
405 405
 	}
@@ -452,7 +452,7 @@  discard block
 block discarded – undo
452 452
 		if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
453 453
 			$expirationTime = new \DateTime();
454 454
 			$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
455
-			$expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
455
+			$expirationTime = $expirationTime->add(new \DateInterval('PT'.$expirationInterval.'S'));
456 456
 			$emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
457 457
 		}
458 458
 
@@ -484,7 +484,7 @@  discard block
 block discarded – undo
484 484
 			$initiatorEmail = $initiatorUser->getEMailAddress();
485 485
 			if ($initiatorEmail !== null) {
486 486
 				$message->setReplyTo([$initiatorEmail => $initiatorDisplayName]);
487
-				$emailTemplate->addFooter($instanceName . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : ''));
487
+				$emailTemplate->addFooter($instanceName.($this->defaults->getSlogan() !== '' ? ' - '.$this->defaults->getSlogan() : ''));
488 488
 			} else {
489 489
 				$emailTemplate->addFooter();
490 490
 			}
@@ -495,7 +495,7 @@  discard block
 block discarded – undo
495 495
 		$message->useTemplate($emailTemplate);
496 496
 		$failedRecipients = $this->mailer->send($message);
497 497
 		if (!empty($failedRecipients)) {
498
-			$this->logger->error('Share password mail could not be sent to: ' . implode(', ', $failedRecipients));
498
+			$this->logger->error('Share password mail could not be sent to: '.implode(', ', $failedRecipients));
499 499
 			return false;
500 500
 		}
501 501
 
@@ -549,7 +549,7 @@  discard block
 block discarded – undo
549 549
 		$message->setFrom([Util::getDefaultEmailAddress($instanceName) => $senderName]);
550 550
 		if ($this->settingsManager->replyToInitiator() && $initiatorEmailAddress !== null) {
551 551
 			$message->setReplyTo([$initiatorEmailAddress => $initiatorDisplayName]);
552
-			$emailTemplate->addFooter($instanceName . ' - ' . $this->defaults->getSlogan());
552
+			$emailTemplate->addFooter($instanceName.' - '.$this->defaults->getSlogan());
553 553
 		} else {
554 554
 			$emailTemplate->addFooter();
555 555
 		}
@@ -599,7 +599,7 @@  discard block
 block discarded – undo
599 599
 		if ($this->config->getSystemValue('sharing.enable_mail_link_password_expiration', false) === true) {
600 600
 			$expirationTime = new \DateTime();
601 601
 			$expirationInterval = $this->config->getSystemValue('sharing.mail_link_password_expiration_interval', 3600);
602
-			$expirationTime = $expirationTime->add(new \DateInterval('PT' . $expirationInterval . 'S'));
602
+			$expirationTime = $expirationTime->add(new \DateInterval('PT'.$expirationInterval.'S'));
603 603
 			$emailTemplate->addBodyText($this->l->t('This password will expire at %s', [$expirationTime->format('r')]));
604 604
 		}
605 605
 
@@ -694,10 +694,10 @@  discard block
 block discarded – undo
694 694
 			->setValue('password_expiration_time', $qb->createNamedParameter($passwordExpirationTime, IQueryBuilder::PARAM_DATETIME_MUTABLE))
695 695
 			->setValue('password_by_talk', $qb->createNamedParameter($sendPasswordByTalk, IQueryBuilder::PARAM_BOOL))
696 696
 			->setValue('stime', $qb->createNamedParameter(time()))
697
-			->setValue('hide_download', $qb->createNamedParameter((int)$hideDownload, IQueryBuilder::PARAM_INT))
697
+			->setValue('hide_download', $qb->createNamedParameter((int) $hideDownload, IQueryBuilder::PARAM_INT))
698 698
 			->setValue('label', $qb->createNamedParameter($label))
699 699
 			->setValue('note', $qb->createNamedParameter($note))
700
-			->setValue('mail_send', $qb->createNamedParameter((int)$mailSend, IQueryBuilder::PARAM_INT));
700
+			->setValue('mail_send', $qb->createNamedParameter((int) $mailSend, IQueryBuilder::PARAM_INT));
701 701
 
702 702
 		// set share attributes
703 703
 		$shareAttributes = $this->formatShareAttributes($attributes);
@@ -723,7 +723,7 @@  discard block
 block discarded – undo
723 723
 		if ($validPassword && ($originalShare->getPassword() !== $share->getPassword()
724 724
 								|| ($originalShare->getSendPasswordByTalk() && !$share->getSendPasswordByTalk()))) {
725 725
 			$emails = $this->getSharedWithEmails($share);
726
-			$validEmails = array_filter($emails, function ($email) {
726
+			$validEmails = array_filter($emails, function($email) {
727 727
 				return $this->emailValidator->isValid($email);
728 728
 			});
729 729
 			$this->sendPassword($share, $plainTextPassword, $validEmails);
@@ -749,9 +749,9 @@  discard block
 block discarded – undo
749 749
 			->set('password_by_talk', $qb->createNamedParameter($share->getSendPasswordByTalk(), IQueryBuilder::PARAM_BOOL))
750 750
 			->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATETIME_MUTABLE))
751 751
 			->set('note', $qb->createNamedParameter($share->getNote()))
752
-			->set('hide_download', $qb->createNamedParameter((int)$share->getHideDownload(), IQueryBuilder::PARAM_INT))
752
+			->set('hide_download', $qb->createNamedParameter((int) $share->getHideDownload(), IQueryBuilder::PARAM_INT))
753 753
 			->set('attributes', $qb->createNamedParameter($shareAttributes))
754
-			->set('mail_send', $qb->createNamedParameter((int)$share->getMailSend(), IQueryBuilder::PARAM_INT))
754
+			->set('mail_send', $qb->createNamedParameter((int) $share->getMailSend(), IQueryBuilder::PARAM_INT))
755 755
 			->set('reminder_sent', $qb->createNamedParameter($share->getReminderSent(), IQueryBuilder::PARAM_BOOL))
756 756
 			->executeStatement();
757 757
 
@@ -783,7 +783,7 @@  discard block
 block discarded – undo
783 783
 		} catch (\Exception $e) {
784 784
 		}
785 785
 
786
-		$this->removeShareFromTable((int)$share->getId());
786
+		$this->removeShareFromTable((int) $share->getId());
787 787
 	}
788 788
 
789 789
 	/**
@@ -991,25 +991,25 @@  discard block
 block discarded – undo
991 991
 	 */
992 992
 	protected function createShareObject(array $data): IShare {
993 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'])
994
+		$share->setId((int) $data['id'])
995
+			->setShareType((int) $data['share_type'])
996
+			->setPermissions((int) $data['permissions'])
997 997
 			->setTarget($data['file_target'])
998
-			->setMailSend((bool)$data['mail_send'])
998
+			->setMailSend((bool) $data['mail_send'])
999 999
 			->setNote($data['note'])
1000 1000
 			->setToken($data['token']);
1001 1001
 
1002 1002
 		$shareTime = new \DateTime();
1003
-		$shareTime->setTimestamp((int)$data['stime']);
1003
+		$shareTime->setTimestamp((int) $data['stime']);
1004 1004
 		$share->setShareTime($shareTime);
1005 1005
 		$share->setSharedWith($data['share_with'] ?? '');
1006 1006
 		$share->setPassword($data['password']);
1007 1007
 		$passwordExpirationTime = \DateTime::createFromFormat('Y-m-d H:i:s', $data['password_expiration_time'] ?? '');
1008 1008
 		$share->setPasswordExpirationTime($passwordExpirationTime !== false ? $passwordExpirationTime : null);
1009 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']);
1010
+		$share->setSendPasswordByTalk((bool) $data['password_by_talk']);
1011
+		$share->setHideDownload((bool) $data['hide_download']);
1012
+		$share->setReminderSent((bool) $data['reminder_sent']);
1013 1013
 
1014 1014
 		if ($data['uid_initiator'] !== null) {
1015 1015
 			$share->setShareOwner($data['uid_owner']);
@@ -1017,7 +1017,7 @@  discard block
 block discarded – undo
1017 1017
 		} else {
1018 1018
 			//OLD SHARE
1019 1019
 			$share->setSharedBy($data['uid_owner']);
1020
-			$path = $this->getNode($share->getSharedBy(), (int)$data['file_source']);
1020
+			$path = $this->getNode($share->getSharedBy(), (int) $data['file_source']);
1021 1021
 
1022 1022
 			$owner = $path->getOwner();
1023 1023
 			$share->setShareOwner($owner->getUID());
@@ -1032,7 +1032,7 @@  discard block
 block discarded – undo
1032 1032
 
1033 1033
 		$share = $this->updateShareAttributes($share, $data['attributes']);
1034 1034
 
1035
-		$share->setNodeId((int)$data['file_source']);
1035
+		$share->setNodeId((int) $data['file_source']);
1036 1036
 		$share->setNodeType($data['item_type']);
1037 1037
 
1038 1038
 		$share->setProviderId($this->identifier());
Please login to merge, or discard this patch.
apps/sharebymail/tests/ShareByMailProviderTest.php 2 patches
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.
Spacing   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -77,7 +77,7 @@  discard block
 block discarded – undo
77 77
 
78 78
 		$this->l = $this->createMock(IL10N::class);
79 79
 		$this->l->method('t')
80
-			->willReturnCallback(function ($text, $parameters = []) {
80
+			->willReturnCallback(function($text, $parameters = []) {
81 81
 				return vsprintf($text, $parameters);
82 82
 			});
83 83
 		$this->config = $this->createMock(IConfig::class);
@@ -393,7 +393,7 @@  discard block
 block discarded – undo
393 393
 		];
394 394
 		$this->mailer->expects($this->exactly(2))
395 395
 			->method('createEMailTemplate')
396
-			->willReturnCallback(function () use (&$calls) {
396
+			->willReturnCallback(function() use (&$calls) {
397 397
 				$expected = array_shift($calls);
398 398
 				$this->assertEquals($expected, func_get_args());
399 399
 				return $this->createMock(IEMailTemplate::class);
@@ -475,7 +475,7 @@  discard block
 block discarded – undo
475 475
 		];
476 476
 		$this->mailer->expects($this->exactly(2))
477 477
 			->method('createEMailTemplate')
478
-			->willReturnCallback(function () use (&$calls) {
478
+			->willReturnCallback(function() use (&$calls) {
479 479
 				$expected = array_shift($calls);
480 480
 				$this->assertEquals($expected, func_get_args());
481 481
 				return $this->createMock(IEMailTemplate::class);
@@ -540,7 +540,7 @@  discard block
 block discarded – undo
540 540
 		];
541 541
 		$message->expects($this->exactly(2))
542 542
 			->method('setTo')
543
-			->willReturnCallback(function () use (&$setToCalls, $message) {
543
+			->willReturnCallback(function() use (&$setToCalls, $message) {
544 544
 				$expected = array_shift($setToCalls);
545 545
 				$this->assertEquals($expected, func_get_args());
546 546
 				return $message;
@@ -572,7 +572,7 @@  discard block
 block discarded – undo
572 572
 		];
573 573
 		$this->mailer->expects($this->exactly(2))
574 574
 			->method('createEMailTemplate')
575
-			->willReturnCallback(function () use (&$calls) {
575
+			->willReturnCallback(function() use (&$calls) {
576 576
 				$expected = array_shift($calls);
577 577
 				$this->assertEquals($expected, func_get_args());
578 578
 				return $this->createMock(IEMailTemplate::class);
@@ -752,17 +752,17 @@  discard block
 block discarded – undo
752 752
 
753 753
 		$this->assertSame(1, count($result));
754 754
 
755
-		$this->assertSame($itemSource, (int)$result[0]['item_source']);
755
+		$this->assertSame($itemSource, (int) $result[0]['item_source']);
756 756
 		$this->assertSame($itemType, $result[0]['item_type']);
757 757
 		$this->assertSame($shareWith, $result[0]['share_with']);
758 758
 		$this->assertSame($sharedBy, $result[0]['uid_initiator']);
759 759
 		$this->assertSame($uidOwner, $result[0]['uid_owner']);
760
-		$this->assertSame($permissions, (int)$result[0]['permissions']);
760
+		$this->assertSame($permissions, (int) $result[0]['permissions']);
761 761
 		$this->assertSame($token, $result[0]['token']);
762 762
 		$this->assertSame($password, $result[0]['password']);
763 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']);
764
+		$this->assertSame($sendPasswordByTalk, (bool) $result[0]['password_by_talk']);
765
+		$this->assertSame($hideDownload, (bool) $result[0]['hide_download']);
766 766
 		$this->assertSame($label, $result[0]['label']);
767 767
 		$this->assertSame($expiration->getTimestamp(), \DateTime::createFromFormat('Y-m-d H:i:s', $result[0]['expiration'])->getTimestamp());
768 768
 	}
@@ -805,12 +805,12 @@  discard block
 block discarded – undo
805 805
 
806 806
 		$this->assertSame(1, count($result));
807 807
 
808
-		$this->assertSame($itemSource, (int)$result[0]['item_source']);
808
+		$this->assertSame($itemSource, (int) $result[0]['item_source']);
809 809
 		$this->assertSame($itemType, $result[0]['item_type']);
810 810
 		$this->assertSame($shareWith, $result[0]['share_with']);
811 811
 		$this->assertSame($sharedBy, $result[0]['uid_initiator']);
812 812
 		$this->assertSame($uidOwner, $result[0]['uid_owner']);
813
-		$this->assertSame($permissions + 1, (int)$result[0]['permissions']);
813
+		$this->assertSame($permissions + 1, (int) $result[0]['permissions']);
814 814
 		$this->assertSame($token, $result[0]['token']);
815 815
 		$this->assertSame($note, $result[0]['note']);
816 816
 	}
@@ -893,10 +893,10 @@  discard block
 block discarded – undo
893 893
 
894 894
 		$instance->expects($this->once())->method('createShareObject')
895 895
 			->willReturnCallback(
896
-				function ($data) use ($uidOwner, $sharedBy, $id2) {
896
+				function($data) use ($uidOwner, $sharedBy, $id2) {
897 897
 					$this->assertSame($uidOwner, $data['uid_owner']);
898 898
 					$this->assertSame($sharedBy, $data['uid_initiator']);
899
-					$this->assertSame($id2, (int)$data['id']);
899
+					$this->assertSame($id2, (int) $data['id']);
900 900
 					return $this->share;
901 901
 				}
902 902
 			);
@@ -945,10 +945,10 @@  discard block
 block discarded – undo
945 945
 
946 946
 		$instance->expects($this->once())->method('createShareObject')
947 947
 			->willReturnCallback(
948
-				function ($data) use ($uidOwner, $sharedBy, $id) {
948
+				function($data) use ($uidOwner, $sharedBy, $id) {
949 949
 					$this->assertSame($uidOwner, $data['uid_owner']);
950 950
 					$this->assertSame($sharedBy, $data['uid_initiator']);
951
-					$this->assertSame($id, (int)$data['id']);
951
+					$this->assertSame($id, (int) $data['id']);
952 952
 					return $this->share;
953 953
 				}
954 954
 			);
@@ -978,8 +978,8 @@  discard block
 block discarded – undo
978 978
 
979 979
 		$instance->expects($this->once())->method('createShareObject')
980 980
 			->willReturnCallback(
981
-				function ($data) use ($idMail) {
982
-					$this->assertSame($idMail, (int)$data['id']);
981
+				function($data) use ($idMail) {
982
+					$this->assertSame($idMail, (int) $data['id']);
983 983
 					return $this->share;
984 984
 				}
985 985
 			);
@@ -1088,7 +1088,7 @@  discard block
 block discarded – undo
1088 1088
 
1089 1089
 		$this->assertTrue(is_array($after));
1090 1090
 		$this->assertSame(1, count($after));
1091
-		$this->assertSame($id, (int)$after[0]['id']);
1091
+		$this->assertSame($id, (int) $after[0]['id']);
1092 1092
 	}
1093 1093
 
1094 1094
 	public function testGetRawShare(): void {
@@ -1107,12 +1107,12 @@  discard block
 block discarded – undo
1107 1107
 		$result = $this->invokePrivate($instance, 'getRawShare', [$id]);
1108 1108
 
1109 1109
 		$this->assertTrue(is_array($result));
1110
-		$this->assertSame($itemSource, (int)$result['item_source']);
1110
+		$this->assertSame($itemSource, (int) $result['item_source']);
1111 1111
 		$this->assertSame($itemType, $result['item_type']);
1112 1112
 		$this->assertSame($shareWith, $result['share_with']);
1113 1113
 		$this->assertSame($sharedBy, $result['uid_initiator']);
1114 1114
 		$this->assertSame($uidOwner, $result['uid_owner']);
1115
-		$this->assertSame($permissions, (int)$result['permissions']);
1115
+		$this->assertSame($permissions, (int) $result['permissions']);
1116 1116
 		$this->assertSame($token, $result['token']);
1117 1117
 	}
1118 1118
 
@@ -1159,7 +1159,7 @@  discard block
 block discarded – undo
1159 1159
 		$qb->execute();
1160 1160
 		$id = $qb->getLastInsertId();
1161 1161
 
1162
-		return (int)$id;
1162
+		return (int) $id;
1163 1163
 	}
1164 1164
 
1165 1165
 	public function testGetSharesInFolder(): void {
@@ -1172,8 +1172,8 @@  discard block
 block discarded – undo
1172 1172
 
1173 1173
 		$provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1174 1174
 
1175
-		$u1 = $userManager->createUser('testFed', md5((string)time()));
1176
-		$u2 = $userManager->createUser('testFed2', md5((string)time()));
1175
+		$u1 = $userManager->createUser('testFed', md5((string) time()));
1176
+		$u2 = $userManager->createUser('testFed2', md5((string) time()));
1177 1177
 
1178 1178
 		$folder1 = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1179 1179
 		$file1 = $folder1->newFile('bar1');
@@ -1218,8 +1218,8 @@  discard block
 block discarded – undo
1218 1218
 
1219 1219
 		$provider = $this->getInstance(['sendMailNotification', 'createShareActivity']);
1220 1220
 
1221
-		$u1 = $userManager->createUser('testFed', md5((string)time()));
1222
-		$u2 = $userManager->createUser('testFed2', md5((string)time()));
1221
+		$u1 = $userManager->createUser('testFed', md5((string) time()));
1222
+		$u2 = $userManager->createUser('testFed2', md5((string) time()));
1223 1223
 
1224 1224
 		$folder = $rootFolder->getUserFolder($u1->getUID())->newFolder('foo');
1225 1225
 
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.