Completed
Push — master ( b46984...c89ca8 )
by
unknown
43:33
created
apps/files_sharing/tests/Controller/ShareControllerTest.php 1 patch
Indentation   +770 added lines, -770 removed lines patch added patch discarded remove patch
@@ -56,774 +56,774 @@
 block discarded – undo
56 56
  */
57 57
 class ShareControllerTest extends \Test\TestCase {
58 58
 
59
-	private string $user;
60
-	private string $oldUser;
61
-	private string $appName = 'files_sharing';
62
-	private ShareController $shareController;
63
-
64
-	private IL10N&MockObject $l10n;
65
-	private IConfig&MockObject $config;
66
-	private ISession&MockObject $session;
67
-	private Defaults&MockObject $defaults;
68
-	private IAppConfig&MockObject $appConfig;
69
-	private Manager&MockObject $shareManager;
70
-	private IPreview&MockObject $previewManager;
71
-	private IUserManager&MockObject $userManager;
72
-	private ITemplateManager&MockObject $templateManager;
73
-	private IInitialState&MockObject $initialState;
74
-	private IURLGenerator&MockObject $urlGenerator;
75
-	private ISecureRandom&MockObject $secureRandom;
76
-	private IAccountManager&MockObject $accountManager;
77
-	private IEventDispatcher&MockObject $eventDispatcher;
78
-	private FederatedShareProvider&MockObject $federatedShareProvider;
79
-	private IPublicShareTemplateFactory&MockObject $publicShareTemplateFactory;
80
-
81
-	protected function setUp(): void {
82
-		parent::setUp();
83
-		$this->appName = 'files_sharing';
84
-
85
-		$this->shareManager = $this->createMock(Manager::class);
86
-		$this->urlGenerator = $this->createMock(IURLGenerator::class);
87
-		$this->session = $this->createMock(ISession::class);
88
-		$this->previewManager = $this->createMock(IPreview::class);
89
-		$this->config = $this->createMock(IConfig::class);
90
-		$this->appConfig = $this->createMock(IAppConfig::class);
91
-		$this->userManager = $this->createMock(IUserManager::class);
92
-		$this->templateManager = $this->createMock(ITemplateManager::class);
93
-		$this->initialState = $this->createMock(IInitialState::class);
94
-		$this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
95
-		$this->federatedShareProvider->expects($this->any())
96
-			->method('isOutgoingServer2serverShareEnabled')->willReturn(true);
97
-		$this->federatedShareProvider->expects($this->any())
98
-			->method('isIncomingServer2serverShareEnabled')->willReturn(true);
99
-		$this->accountManager = $this->createMock(IAccountManager::class);
100
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
101
-		$this->l10n = $this->createMock(IL10N::class);
102
-		$this->secureRandom = $this->createMock(ISecureRandom::class);
103
-		$this->defaults = $this->createMock(Defaults::class);
104
-		$this->publicShareTemplateFactory = $this->createMock(IPublicShareTemplateFactory::class);
105
-		$this->publicShareTemplateFactory
106
-			->expects($this->any())
107
-			->method('getProvider')
108
-			->willReturn(
109
-				new DefaultPublicShareTemplateProvider(
110
-					$this->userManager,
111
-					$this->accountManager,
112
-					$this->previewManager,
113
-					$this->federatedShareProvider,
114
-					$this->urlGenerator,
115
-					$this->eventDispatcher,
116
-					$this->l10n,
117
-					$this->defaults,
118
-					$this->config,
119
-					$this->createMock(IRequest::class),
120
-					$this->templateManager,
121
-					$this->initialState,
122
-					$this->appConfig,
123
-				)
124
-			);
125
-
126
-		$this->shareController = new ShareController(
127
-			$this->appName,
128
-			$this->createMock(IRequest::class),
129
-			$this->config,
130
-			$this->urlGenerator,
131
-			$this->userManager,
132
-			$this->createMock(IManager::class),
133
-			$this->shareManager,
134
-			$this->session,
135
-			$this->previewManager,
136
-			$this->createMock(IRootFolder::class),
137
-			$this->federatedShareProvider,
138
-			$this->accountManager,
139
-			$this->eventDispatcher,
140
-			$this->l10n,
141
-			$this->secureRandom,
142
-			$this->defaults,
143
-			$this->publicShareTemplateFactory,
144
-		);
145
-
146
-
147
-		// Store current user
148
-		$this->oldUser = \OC_User::getUser();
149
-
150
-		// Create a dummy user
151
-		$this->user = Server::get(ISecureRandom::class)->generate(12, ISecureRandom::CHAR_LOWER);
152
-
153
-		Server::get(IUserManager::class)->createUser($this->user, $this->user);
154
-		\OC_Util::tearDownFS();
155
-		$this->loginAsUser($this->user);
156
-	}
157
-
158
-	protected function tearDown(): void {
159
-		\OC_Util::tearDownFS();
160
-		\OC_User::setUserId('');
161
-		Filesystem::tearDown();
162
-		$user = Server::get(IUserManager::class)->get($this->user);
163
-		if ($user !== null) {
164
-			$user->delete();
165
-		}
166
-		\OC_User::setIncognitoMode(false);
167
-
168
-		Server::get(ISession::class)->set('public_link_authenticated', '');
169
-
170
-		// Set old user
171
-		\OC_User::setUserId($this->oldUser);
172
-		\OC_Util::setupFS($this->oldUser);
173
-		parent::tearDown();
174
-	}
175
-
176
-	public function testShowShareInvalidToken(): void {
177
-		$this->shareController->setToken('invalidtoken');
178
-
179
-		$this->shareManager
180
-			->expects($this->once())
181
-			->method('getShareByToken')
182
-			->with('invalidtoken')
183
-			->willThrowException(new ShareNotFound());
184
-
185
-		$this->expectException(NotFoundException::class);
186
-
187
-		// Test without a not existing token
188
-		$this->shareController->showShare();
189
-	}
190
-
191
-	public function testShowShareNotAuthenticated(): void {
192
-		$this->shareController->setToken('validtoken');
193
-
194
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
195
-		$share->setPassword('password');
196
-
197
-		$this->shareManager
198
-			->expects($this->once())
199
-			->method('getShareByToken')
200
-			->with('validtoken')
201
-			->willReturn($share);
202
-
203
-		$this->expectException(NotFoundException::class);
204
-
205
-		// Test without a not existing token
206
-		$this->shareController->showShare();
207
-	}
208
-
209
-
210
-	public function testShowShare(): void {
211
-		$note = 'personal note';
212
-		$filename = 'file1.txt';
213
-
214
-		$this->shareController->setToken('token');
215
-
216
-		$owner = $this->createMock(IUser::class);
217
-		$owner->method('getDisplayName')->willReturn('ownerDisplay');
218
-		$owner->method('getUID')->willReturn('ownerUID');
219
-		$owner->method('isEnabled')->willReturn(true);
220
-
221
-		$initiator = $this->createMock(IUser::class);
222
-		$initiator->method('getDisplayName')->willReturn('initiatorDisplay');
223
-		$initiator->method('getUID')->willReturn('initiatorUID');
224
-		$initiator->method('isEnabled')->willReturn(true);
225
-
226
-		$file = $this->createMock(File::class);
227
-		$file->method('getName')->willReturn($filename);
228
-		$file->method('getMimetype')->willReturn('text/plain');
229
-		$file->method('getSize')->willReturn(33);
230
-		$file->method('isReadable')->willReturn(true);
231
-		$file->method('isShareable')->willReturn(true);
232
-		$file->method('getId')->willReturn(111);
233
-
234
-		$accountName = $this->createMock(IAccountProperty::class);
235
-		$accountName->method('getScope')
236
-			->willReturn(IAccountManager::SCOPE_PUBLISHED);
237
-		$account = $this->createMock(IAccount::class);
238
-		$account->method('getProperty')
239
-			->with(IAccountManager::PROPERTY_DISPLAYNAME)
240
-			->willReturn($accountName);
241
-		$this->accountManager->expects($this->once())
242
-			->method('getAccount')
243
-			->with($owner)
244
-			->willReturn($account);
245
-
246
-		/** @var Manager */
247
-		$manager = Server::get(Manager::class);
248
-		$share = $manager->newShare();
249
-		$share->setId(42)
250
-			->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE)
251
-			->setPassword('password')
252
-			->setShareOwner('ownerUID')
253
-			->setSharedBy('initiatorUID')
254
-			->setNode($file)
255
-			->setNote($note)
256
-			->setTarget("/$filename")
257
-			->setToken('token');
258
-
259
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
260
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
261
-
262
-		$this->urlGenerator->expects(self::atLeast(2))
263
-			->method('linkToRouteAbsolute')
264
-			->willReturnMap([
265
-				// every file has the show show share url in the opengraph url prop
266
-				['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
267
-				// this share is not an image to the default preview is used
268
-				['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
269
-			]);
270
-
271
-		$this->urlGenerator->expects($this->once())
272
-			->method('getAbsoluteURL')
273
-			->willReturnMap([
274
-				['/public.php/dav/files/token/?accept=zip', 'downloadUrl'],
275
-			]);
276
-
277
-		$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
278
-
279
-		$this->config->method('getSystemValue')
280
-			->willReturnMap(
281
-				[
282
-					['max_filesize_animated_gifs_public_sharing', 10, 10],
283
-					['enable_previews', true, true],
284
-					['preview_max_x', 1024, 1024],
285
-					['preview_max_y', 1024, 1024],
286
-				]
287
-			);
288
-
289
-		$this->shareManager
290
-			->expects($this->once())
291
-			->method('getShareByToken')
292
-			->with('token')
293
-			->willReturn($share);
294
-
295
-		$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
296
-			if ($uid === 'ownerUID') {
297
-				return $owner;
298
-			}
299
-			if ($uid === 'initiatorUID') {
300
-				return $initiator;
301
-			}
302
-			return null;
303
-		});
304
-
305
-		$this->eventDispatcher->method('dispatchTyped')->with(
306
-			$this->callback(function ($event) use ($share) {
307
-				if ($event instanceof BeforeTemplateRenderedEvent) {
308
-					return $event->getShare() === $share;
309
-				} else {
310
-					return true;
311
-				}
312
-			})
313
-		);
314
-
315
-		$this->l10n->expects($this->any())
316
-			->method('t')
317
-			->willReturnCallback(function ($text, $parameters) {
318
-				return vsprintf($text, $parameters);
319
-			});
320
-
321
-		$this->defaults->expects(self::any())
322
-			->method('getProductName')
323
-			->willReturn('Nextcloud');
324
-
325
-		// Ensure the correct initial state is setup
326
-		// Shared node is a file so this is a single file share:
327
-		$view = 'public-file-share';
328
-		// Set up initial state
329
-		$initialState = [];
330
-		$this->initialState->expects(self::any())
331
-			->method('provideInitialState')
332
-			->willReturnCallback(function ($key, $value) use (&$initialState): void {
333
-				$initialState[$key] = $value;
334
-			});
335
-		$expectedInitialState = [
336
-			'isPublic' => true,
337
-			'sharingToken' => 'token',
338
-			'sharePermissions' => (Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE),
339
-			'filename' => $filename,
340
-			'view' => $view,
341
-			'fileId' => 111,
342
-			'owner' => 'ownerUID',
343
-			'ownerDisplayName' => 'ownerDisplay',
344
-			'isFileRequest' => false,
345
-			'templates' => [],
346
-		];
347
-
348
-		$response = $this->shareController->showShare();
349
-
350
-		$this->assertEquals($expectedInitialState, $initialState);
351
-
352
-		$csp = new ContentSecurityPolicy();
353
-		$csp->addAllowedFrameDomain('\'self\'');
354
-		$expectedResponse = new PublicTemplateResponse('files', 'index');
355
-		$expectedResponse->setParams(['pageTitle' => $filename]);
356
-		$expectedResponse->setContentSecurityPolicy($csp);
357
-		$expectedResponse->setHeaderTitle($filename);
358
-		$expectedResponse->setHeaderDetails('shared by ownerDisplay');
359
-		$expectedResponse->setHeaderActions([
360
-			new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
361
-			new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
362
-			new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
363
-		]);
364
-
365
-		$this->assertEquals($expectedResponse, $response);
366
-	}
367
-
368
-	public function testShowFileDropShare(): void {
369
-		$filename = 'folder1';
370
-
371
-		$this->shareController->setToken('token');
372
-
373
-		$owner = $this->createMock(IUser::class);
374
-		$owner->method('getDisplayName')->willReturn('ownerDisplay');
375
-		$owner->method('getUID')->willReturn('ownerUID');
376
-		$owner->method('isEnabled')->willReturn(true);
377
-
378
-		$initiator = $this->createMock(IUser::class);
379
-		$initiator->method('getDisplayName')->willReturn('initiatorDisplay');
380
-		$initiator->method('getUID')->willReturn('initiatorUID');
381
-		$initiator->method('isEnabled')->willReturn(true);
382
-
383
-		$file = $this->createMock(Folder::class);
384
-		$file->method('isReadable')->willReturn(true);
385
-		$file->method('isShareable')->willReturn(true);
386
-		$file->method('getId')->willReturn(1234);
387
-		$file->method('getMimetype')->willReturn('text/plain');
388
-		$file->method('getName')->willReturn($filename);
389
-
390
-		$accountName = $this->createMock(IAccountProperty::class);
391
-		$accountName->method('getScope')
392
-			->willReturn(IAccountManager::SCOPE_PUBLISHED);
393
-		$account = $this->createMock(IAccount::class);
394
-		$account->method('getProperty')
395
-			->with(IAccountManager::PROPERTY_DISPLAYNAME)
396
-			->willReturn($accountName);
397
-		$this->accountManager->expects($this->once())
398
-			->method('getAccount')
399
-			->with($owner)
400
-			->willReturn($account);
401
-
402
-		/** @var Manager */
403
-		$manager = Server::get(Manager::class);
404
-		$share = $manager->newShare();
405
-		$share->setId(42)
406
-			->setPermissions(Constants::PERMISSION_CREATE)
407
-			->setPassword('password')
408
-			->setShareOwner('ownerUID')
409
-			->setSharedBy('initiatorUID')
410
-			->setNote('The note')
411
-			->setLabel('A label')
412
-			->setNode($file)
413
-			->setTarget("/$filename")
414
-			->setToken('token');
415
-
416
-		$this->appConfig
417
-			->expects($this->once())
418
-			->method('getValueString')
419
-			->with('core', 'shareapi_public_link_disclaimertext', '')
420
-			->willReturn('My disclaimer text');
421
-
422
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
423
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
424
-
425
-		$this->urlGenerator->expects(self::atLeastOnce())
426
-			->method('linkToRouteAbsolute')
427
-			->willReturnMap([
428
-				// every file has the show show share url in the opengraph url prop
429
-				['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
430
-				// there is no preview or folders so no other link for opengraph
431
-			]);
432
-
433
-		$this->config->method('getSystemValue')
434
-			->willReturnMap(
435
-				[
436
-					['max_filesize_animated_gifs_public_sharing', 10, 10],
437
-					['enable_previews', true, true],
438
-					['preview_max_x', 1024, 1024],
439
-					['preview_max_y', 1024, 1024],
440
-				]
441
-			);
442
-
443
-		$this->shareManager
444
-			->expects($this->once())
445
-			->method('getShareByToken')
446
-			->with('token')
447
-			->willReturn($share);
448
-
449
-		$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
450
-			if ($uid === 'ownerUID') {
451
-				return $owner;
452
-			}
453
-			if ($uid === 'initiatorUID') {
454
-				return $initiator;
455
-			}
456
-			return null;
457
-		});
458
-
459
-		$this->eventDispatcher->method('dispatchTyped')->with(
460
-			$this->callback(function ($event) use ($share) {
461
-				if ($event instanceof BeforeTemplateRenderedEvent) {
462
-					return $event->getShare() === $share;
463
-				} else {
464
-					return true;
465
-				}
466
-			})
467
-		);
468
-
469
-		$this->l10n->expects($this->any())
470
-			->method('t')
471
-			->willReturnCallback(function ($text, $parameters) {
472
-				return vsprintf($text, $parameters);
473
-			});
474
-
475
-		// Set up initial state
476
-		$initialState = [];
477
-		$this->initialState->expects(self::any())
478
-			->method('provideInitialState')
479
-			->willReturnCallback(function ($key, $value) use (&$initialState): void {
480
-				$initialState[$key] = $value;
481
-			});
482
-		$expectedInitialState = [
483
-			'isPublic' => true,
484
-			'sharingToken' => 'token',
485
-			'sharePermissions' => Constants::PERMISSION_CREATE,
486
-			'filename' => $filename,
487
-			'view' => 'public-file-drop',
488
-			'disclaimer' => 'My disclaimer text',
489
-			'owner' => 'ownerUID',
490
-			'ownerDisplayName' => 'ownerDisplay',
491
-			'isFileRequest' => false,
492
-			'note' => 'The note',
493
-			'label' => 'A label',
494
-			'templates' => [],
495
-		];
496
-
497
-		$response = $this->shareController->showShare();
498
-
499
-		$this->assertEquals($expectedInitialState, $initialState);
500
-
501
-		$csp = new ContentSecurityPolicy();
502
-		$csp->addAllowedFrameDomain('\'self\'');
503
-		$expectedResponse = new PublicTemplateResponse('files', 'index');
504
-		$expectedResponse->setParams(['pageTitle' => 'A label']);
505
-		$expectedResponse->setContentSecurityPolicy($csp);
506
-		$expectedResponse->setHeaderTitle('A label');
507
-		$expectedResponse->setHeaderDetails('shared by ownerDisplay');
508
-		$expectedResponse->setHeaderActions([
509
-			new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'shareUrl'),
510
-		]);
511
-
512
-		$this->assertEquals($expectedResponse, $response);
513
-	}
514
-
515
-	public function testShowShareWithPrivateName(): void {
516
-		$note = 'personal note';
517
-		$filename = 'file1.txt';
518
-
519
-		$this->shareController->setToken('token');
520
-
521
-		$owner = $this->createMock(IUser::class);
522
-		$owner->method('getDisplayName')->willReturn('ownerDisplay');
523
-		$owner->method('getUID')->willReturn('ownerUID');
524
-		$owner->method('isEnabled')->willReturn(true);
525
-
526
-		$initiator = $this->createMock(IUser::class);
527
-		$initiator->method('getDisplayName')->willReturn('initiatorDisplay');
528
-		$initiator->method('getUID')->willReturn('initiatorUID');
529
-		$initiator->method('isEnabled')->willReturn(true);
530
-
531
-		$file = $this->createMock(File::class);
532
-		$file->method('getName')->willReturn($filename);
533
-		$file->method('getMimetype')->willReturn('text/plain');
534
-		$file->method('getSize')->willReturn(33);
535
-		$file->method('isReadable')->willReturn(true);
536
-		$file->method('isShareable')->willReturn(true);
537
-		$file->method('getId')->willReturn(111);
538
-
539
-		$accountName = $this->createMock(IAccountProperty::class);
540
-		$accountName->method('getScope')
541
-			->willReturn(IAccountManager::SCOPE_LOCAL);
542
-		$account = $this->createMock(IAccount::class);
543
-		$account->method('getProperty')
544
-			->with(IAccountManager::PROPERTY_DISPLAYNAME)
545
-			->willReturn($accountName);
546
-		$this->accountManager->expects($this->once())
547
-			->method('getAccount')
548
-			->with($owner)
549
-			->willReturn($account);
550
-
551
-		/** @var IShare */
552
-		$share = Server::get(Manager::class)->newShare();
553
-		$share->setId(42);
554
-		$share->setPassword('password')
555
-			->setShareOwner('ownerUID')
556
-			->setSharedBy('initiatorUID')
557
-			->setNode($file)
558
-			->setNote($note)
559
-			->setToken('token')
560
-			->setPermissions(Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE)
561
-			->setTarget("/$filename");
562
-
563
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
564
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
565
-
566
-		$this->urlGenerator->expects(self::atLeast(2))
567
-			->method('linkToRouteAbsolute')
568
-			->willReturnMap([
569
-				// every file has the show show share url in the opengraph url prop
570
-				['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
571
-				// this share is not an image to the default preview is used
572
-				['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
573
-			]);
574
-
575
-		$this->urlGenerator->expects($this->once())
576
-			->method('getAbsoluteURL')
577
-			->willReturnMap([
578
-				['/public.php/dav/files/token/?accept=zip', 'downloadUrl'],
579
-			]);
580
-
581
-		$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
582
-
583
-		$this->config->method('getSystemValue')
584
-			->willReturnMap(
585
-				[
586
-					['max_filesize_animated_gifs_public_sharing', 10, 10],
587
-					['enable_previews', true, true],
588
-					['preview_max_x', 1024, 1024],
589
-					['preview_max_y', 1024, 1024],
590
-				]
591
-			);
592
-		$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
593
-		$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
594
-
595
-		$this->shareManager
596
-			->expects($this->once())
597
-			->method('getShareByToken')
598
-			->with('token')
599
-			->willReturn($share);
600
-
601
-		$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
602
-			if ($uid === 'ownerUID') {
603
-				return $owner;
604
-			}
605
-			if ($uid === 'initiatorUID') {
606
-				return $initiator;
607
-			}
608
-			return null;
609
-		});
610
-
611
-		$this->eventDispatcher->method('dispatchTyped')->with(
612
-			$this->callback(function ($event) use ($share) {
613
-				if ($event instanceof BeforeTemplateRenderedEvent) {
614
-					return $event->getShare() === $share;
615
-				} else {
616
-					return true;
617
-				}
618
-			})
619
-		);
620
-
621
-		$this->l10n->expects($this->any())
622
-			->method('t')
623
-			->willReturnCallback(function ($text, $parameters) {
624
-				return vsprintf($text, $parameters);
625
-			});
626
-
627
-		$this->defaults->expects(self::any())
628
-			->method('getProductName')
629
-			->willReturn('Nextcloud');
630
-
631
-		$response = $this->shareController->showShare();
632
-
633
-		$csp = new ContentSecurityPolicy();
634
-		$csp->addAllowedFrameDomain('\'self\'');
635
-		$expectedResponse = new PublicTemplateResponse('files', 'index');
636
-		$expectedResponse->setParams(['pageTitle' => $filename]);
637
-		$expectedResponse->setContentSecurityPolicy($csp);
638
-		$expectedResponse->setHeaderTitle($filename);
639
-		$expectedResponse->setHeaderDetails('');
640
-		$expectedResponse->setHeaderActions([
641
-			new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
642
-			new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
643
-			new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
644
-		]);
645
-
646
-		$this->assertEquals($expectedResponse, $response);
647
-	}
648
-
649
-
650
-	public function testShowShareInvalid(): void {
651
-		$this->expectException(NotFoundException::class);
652
-
653
-		$filename = 'file1.txt';
654
-		$this->shareController->setToken('token');
655
-
656
-		$owner = $this->getMockBuilder(IUser::class)->getMock();
657
-		$owner->method('getDisplayName')->willReturn('ownerDisplay');
658
-		$owner->method('getUID')->willReturn('ownerUID');
659
-
660
-		$file = $this->getMockBuilder('OCP\Files\File')->getMock();
661
-		$file->method('getName')->willReturn($filename);
662
-		$file->method('getMimetype')->willReturn('text/plain');
663
-		$file->method('getSize')->willReturn(33);
664
-		$file->method('isShareable')->willReturn(false);
665
-		$file->method('isReadable')->willReturn(true);
666
-
667
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
668
-		$share->setId(42);
669
-		$share->setPassword('password')
670
-			->setShareOwner('ownerUID')
671
-			->setNode($file)
672
-			->setTarget("/$filename");
673
-
674
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
675
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
676
-
677
-		$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
678
-
679
-		$this->config->method('getSystemValue')
680
-			->willReturnMap(
681
-				[
682
-					['max_filesize_animated_gifs_public_sharing', 10, 10],
683
-					['enable_previews', true, true],
684
-				]
685
-			);
686
-		$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
687
-		$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
688
-
689
-		$this->shareManager
690
-			->expects($this->once())
691
-			->method('getShareByToken')
692
-			->with('token')
693
-			->willReturn($share);
694
-
695
-		$this->userManager->method('get')->with('ownerUID')->willReturn($owner);
696
-
697
-		$this->shareController->showShare();
698
-	}
699
-
700
-	public function testDownloadShareWithCreateOnlyShare(): void {
701
-		$share = $this->getMockBuilder(IShare::class)->getMock();
702
-		$share->method('getPassword')->willReturn('password');
703
-		$share
704
-			->expects($this->once())
705
-			->method('getPermissions')
706
-			->willReturn(Constants::PERMISSION_CREATE);
707
-
708
-		$this->shareManager
709
-			->expects($this->once())
710
-			->method('getShareByToken')
711
-			->with('validtoken')
712
-			->willReturn($share);
713
-
714
-		// Test with a password protected share and no authentication
715
-		$response = $this->shareController->downloadShare('validtoken');
716
-		$expectedResponse = new DataResponse('Share has no read permission');
717
-		$this->assertEquals($expectedResponse, $response);
718
-	}
719
-
720
-	public function testDownloadShareWithoutDownloadPermission(): void {
721
-		$attributes = $this->createMock(IAttributes::class);
722
-		$attributes->expects(self::once())
723
-			->method('getAttribute')
724
-			->with('permissions', 'download')
725
-			->willReturn(false);
726
-
727
-		$share = $this->createMock(IShare::class);
728
-		$share->method('getPassword')->willReturn('password');
729
-		$share->expects(self::once())
730
-			->method('getPermissions')
731
-			->willReturn(Constants::PERMISSION_READ);
732
-		$share->expects(self::once())
733
-			->method('getAttributes')
734
-			->willReturn($attributes);
735
-
736
-		$this->shareManager
737
-			->expects(self::once())
738
-			->method('getShareByToken')
739
-			->with('validtoken')
740
-			->willReturn($share);
741
-
742
-		// Test with a password protected share and no authentication
743
-		$response = $this->shareController->downloadShare('validtoken');
744
-		$expectedResponse = new DataResponse('Share has no download permission');
745
-		$this->assertEquals($expectedResponse, $response);
746
-	}
747
-
748
-	public function testDisabledOwner(): void {
749
-		$this->shareController->setToken('token');
750
-
751
-		$owner = $this->getMockBuilder(IUser::class)->getMock();
752
-		$owner->method('isEnabled')->willReturn(false);
753
-
754
-		$initiator = $this->createMock(IUser::class);
755
-		$initiator->method('isEnabled')->willReturn(false);
756
-
757
-		/* @var MockObject|Folder $folder */
758
-		$folder = $this->createMock(Folder::class);
759
-
760
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
761
-		$share->setId(42);
762
-		$share->setPermissions(Constants::PERMISSION_CREATE)
763
-			->setShareOwner('ownerUID')
764
-			->setSharedBy('initiatorUID')
765
-			->setNode($folder)
766
-			->setTarget('/share');
767
-
768
-		$this->shareManager
769
-			->expects($this->once())
770
-			->method('getShareByToken')
771
-			->with('token')
772
-			->willReturn($share);
773
-
774
-		$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
775
-			if ($uid === 'ownerUID') {
776
-				return $owner;
777
-			}
778
-			if ($uid === 'initiatorUID') {
779
-				return $initiator;
780
-			}
781
-			return null;
782
-		});
783
-
784
-		$this->expectException(NotFoundException::class);
785
-
786
-		$this->shareController->showShare();
787
-	}
788
-
789
-	public function testDisabledInitiator(): void {
790
-		$this->shareController->setToken('token');
791
-
792
-		$owner = $this->getMockBuilder(IUser::class)->getMock();
793
-		$owner->method('isEnabled')->willReturn(false);
794
-
795
-		$initiator = $this->createMock(IUser::class);
796
-		$initiator->method('isEnabled')->willReturn(true);
797
-
798
-		/* @var MockObject|Folder $folder */
799
-		$folder = $this->createMock(Folder::class);
800
-
801
-		$share = Server::get(\OCP\Share\IManager::class)->newShare();
802
-		$share->setId(42);
803
-		$share->setPermissions(Constants::PERMISSION_CREATE)
804
-			->setShareOwner('ownerUID')
805
-			->setSharedBy('initiatorUID')
806
-			->setNode($folder)
807
-			->setTarget('/share');
808
-
809
-		$this->shareManager
810
-			->expects($this->once())
811
-			->method('getShareByToken')
812
-			->with('token')
813
-			->willReturn($share);
814
-
815
-		$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
816
-			if ($uid === 'ownerUID') {
817
-				return $owner;
818
-			}
819
-			if ($uid === 'initiatorUID') {
820
-				return $initiator;
821
-			}
822
-			return null;
823
-		});
824
-
825
-		$this->expectException(NotFoundException::class);
826
-
827
-		$this->shareController->showShare();
828
-	}
59
+    private string $user;
60
+    private string $oldUser;
61
+    private string $appName = 'files_sharing';
62
+    private ShareController $shareController;
63
+
64
+    private IL10N&MockObject $l10n;
65
+    private IConfig&MockObject $config;
66
+    private ISession&MockObject $session;
67
+    private Defaults&MockObject $defaults;
68
+    private IAppConfig&MockObject $appConfig;
69
+    private Manager&MockObject $shareManager;
70
+    private IPreview&MockObject $previewManager;
71
+    private IUserManager&MockObject $userManager;
72
+    private ITemplateManager&MockObject $templateManager;
73
+    private IInitialState&MockObject $initialState;
74
+    private IURLGenerator&MockObject $urlGenerator;
75
+    private ISecureRandom&MockObject $secureRandom;
76
+    private IAccountManager&MockObject $accountManager;
77
+    private IEventDispatcher&MockObject $eventDispatcher;
78
+    private FederatedShareProvider&MockObject $federatedShareProvider;
79
+    private IPublicShareTemplateFactory&MockObject $publicShareTemplateFactory;
80
+
81
+    protected function setUp(): void {
82
+        parent::setUp();
83
+        $this->appName = 'files_sharing';
84
+
85
+        $this->shareManager = $this->createMock(Manager::class);
86
+        $this->urlGenerator = $this->createMock(IURLGenerator::class);
87
+        $this->session = $this->createMock(ISession::class);
88
+        $this->previewManager = $this->createMock(IPreview::class);
89
+        $this->config = $this->createMock(IConfig::class);
90
+        $this->appConfig = $this->createMock(IAppConfig::class);
91
+        $this->userManager = $this->createMock(IUserManager::class);
92
+        $this->templateManager = $this->createMock(ITemplateManager::class);
93
+        $this->initialState = $this->createMock(IInitialState::class);
94
+        $this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
95
+        $this->federatedShareProvider->expects($this->any())
96
+            ->method('isOutgoingServer2serverShareEnabled')->willReturn(true);
97
+        $this->federatedShareProvider->expects($this->any())
98
+            ->method('isIncomingServer2serverShareEnabled')->willReturn(true);
99
+        $this->accountManager = $this->createMock(IAccountManager::class);
100
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
101
+        $this->l10n = $this->createMock(IL10N::class);
102
+        $this->secureRandom = $this->createMock(ISecureRandom::class);
103
+        $this->defaults = $this->createMock(Defaults::class);
104
+        $this->publicShareTemplateFactory = $this->createMock(IPublicShareTemplateFactory::class);
105
+        $this->publicShareTemplateFactory
106
+            ->expects($this->any())
107
+            ->method('getProvider')
108
+            ->willReturn(
109
+                new DefaultPublicShareTemplateProvider(
110
+                    $this->userManager,
111
+                    $this->accountManager,
112
+                    $this->previewManager,
113
+                    $this->federatedShareProvider,
114
+                    $this->urlGenerator,
115
+                    $this->eventDispatcher,
116
+                    $this->l10n,
117
+                    $this->defaults,
118
+                    $this->config,
119
+                    $this->createMock(IRequest::class),
120
+                    $this->templateManager,
121
+                    $this->initialState,
122
+                    $this->appConfig,
123
+                )
124
+            );
125
+
126
+        $this->shareController = new ShareController(
127
+            $this->appName,
128
+            $this->createMock(IRequest::class),
129
+            $this->config,
130
+            $this->urlGenerator,
131
+            $this->userManager,
132
+            $this->createMock(IManager::class),
133
+            $this->shareManager,
134
+            $this->session,
135
+            $this->previewManager,
136
+            $this->createMock(IRootFolder::class),
137
+            $this->federatedShareProvider,
138
+            $this->accountManager,
139
+            $this->eventDispatcher,
140
+            $this->l10n,
141
+            $this->secureRandom,
142
+            $this->defaults,
143
+            $this->publicShareTemplateFactory,
144
+        );
145
+
146
+
147
+        // Store current user
148
+        $this->oldUser = \OC_User::getUser();
149
+
150
+        // Create a dummy user
151
+        $this->user = Server::get(ISecureRandom::class)->generate(12, ISecureRandom::CHAR_LOWER);
152
+
153
+        Server::get(IUserManager::class)->createUser($this->user, $this->user);
154
+        \OC_Util::tearDownFS();
155
+        $this->loginAsUser($this->user);
156
+    }
157
+
158
+    protected function tearDown(): void {
159
+        \OC_Util::tearDownFS();
160
+        \OC_User::setUserId('');
161
+        Filesystem::tearDown();
162
+        $user = Server::get(IUserManager::class)->get($this->user);
163
+        if ($user !== null) {
164
+            $user->delete();
165
+        }
166
+        \OC_User::setIncognitoMode(false);
167
+
168
+        Server::get(ISession::class)->set('public_link_authenticated', '');
169
+
170
+        // Set old user
171
+        \OC_User::setUserId($this->oldUser);
172
+        \OC_Util::setupFS($this->oldUser);
173
+        parent::tearDown();
174
+    }
175
+
176
+    public function testShowShareInvalidToken(): void {
177
+        $this->shareController->setToken('invalidtoken');
178
+
179
+        $this->shareManager
180
+            ->expects($this->once())
181
+            ->method('getShareByToken')
182
+            ->with('invalidtoken')
183
+            ->willThrowException(new ShareNotFound());
184
+
185
+        $this->expectException(NotFoundException::class);
186
+
187
+        // Test without a not existing token
188
+        $this->shareController->showShare();
189
+    }
190
+
191
+    public function testShowShareNotAuthenticated(): void {
192
+        $this->shareController->setToken('validtoken');
193
+
194
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
195
+        $share->setPassword('password');
196
+
197
+        $this->shareManager
198
+            ->expects($this->once())
199
+            ->method('getShareByToken')
200
+            ->with('validtoken')
201
+            ->willReturn($share);
202
+
203
+        $this->expectException(NotFoundException::class);
204
+
205
+        // Test without a not existing token
206
+        $this->shareController->showShare();
207
+    }
208
+
209
+
210
+    public function testShowShare(): void {
211
+        $note = 'personal note';
212
+        $filename = 'file1.txt';
213
+
214
+        $this->shareController->setToken('token');
215
+
216
+        $owner = $this->createMock(IUser::class);
217
+        $owner->method('getDisplayName')->willReturn('ownerDisplay');
218
+        $owner->method('getUID')->willReturn('ownerUID');
219
+        $owner->method('isEnabled')->willReturn(true);
220
+
221
+        $initiator = $this->createMock(IUser::class);
222
+        $initiator->method('getDisplayName')->willReturn('initiatorDisplay');
223
+        $initiator->method('getUID')->willReturn('initiatorUID');
224
+        $initiator->method('isEnabled')->willReturn(true);
225
+
226
+        $file = $this->createMock(File::class);
227
+        $file->method('getName')->willReturn($filename);
228
+        $file->method('getMimetype')->willReturn('text/plain');
229
+        $file->method('getSize')->willReturn(33);
230
+        $file->method('isReadable')->willReturn(true);
231
+        $file->method('isShareable')->willReturn(true);
232
+        $file->method('getId')->willReturn(111);
233
+
234
+        $accountName = $this->createMock(IAccountProperty::class);
235
+        $accountName->method('getScope')
236
+            ->willReturn(IAccountManager::SCOPE_PUBLISHED);
237
+        $account = $this->createMock(IAccount::class);
238
+        $account->method('getProperty')
239
+            ->with(IAccountManager::PROPERTY_DISPLAYNAME)
240
+            ->willReturn($accountName);
241
+        $this->accountManager->expects($this->once())
242
+            ->method('getAccount')
243
+            ->with($owner)
244
+            ->willReturn($account);
245
+
246
+        /** @var Manager */
247
+        $manager = Server::get(Manager::class);
248
+        $share = $manager->newShare();
249
+        $share->setId(42)
250
+            ->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE)
251
+            ->setPassword('password')
252
+            ->setShareOwner('ownerUID')
253
+            ->setSharedBy('initiatorUID')
254
+            ->setNode($file)
255
+            ->setNote($note)
256
+            ->setTarget("/$filename")
257
+            ->setToken('token');
258
+
259
+        $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
260
+        $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
261
+
262
+        $this->urlGenerator->expects(self::atLeast(2))
263
+            ->method('linkToRouteAbsolute')
264
+            ->willReturnMap([
265
+                // every file has the show show share url in the opengraph url prop
266
+                ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
267
+                // this share is not an image to the default preview is used
268
+                ['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
269
+            ]);
270
+
271
+        $this->urlGenerator->expects($this->once())
272
+            ->method('getAbsoluteURL')
273
+            ->willReturnMap([
274
+                ['/public.php/dav/files/token/?accept=zip', 'downloadUrl'],
275
+            ]);
276
+
277
+        $this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
278
+
279
+        $this->config->method('getSystemValue')
280
+            ->willReturnMap(
281
+                [
282
+                    ['max_filesize_animated_gifs_public_sharing', 10, 10],
283
+                    ['enable_previews', true, true],
284
+                    ['preview_max_x', 1024, 1024],
285
+                    ['preview_max_y', 1024, 1024],
286
+                ]
287
+            );
288
+
289
+        $this->shareManager
290
+            ->expects($this->once())
291
+            ->method('getShareByToken')
292
+            ->with('token')
293
+            ->willReturn($share);
294
+
295
+        $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
296
+            if ($uid === 'ownerUID') {
297
+                return $owner;
298
+            }
299
+            if ($uid === 'initiatorUID') {
300
+                return $initiator;
301
+            }
302
+            return null;
303
+        });
304
+
305
+        $this->eventDispatcher->method('dispatchTyped')->with(
306
+            $this->callback(function ($event) use ($share) {
307
+                if ($event instanceof BeforeTemplateRenderedEvent) {
308
+                    return $event->getShare() === $share;
309
+                } else {
310
+                    return true;
311
+                }
312
+            })
313
+        );
314
+
315
+        $this->l10n->expects($this->any())
316
+            ->method('t')
317
+            ->willReturnCallback(function ($text, $parameters) {
318
+                return vsprintf($text, $parameters);
319
+            });
320
+
321
+        $this->defaults->expects(self::any())
322
+            ->method('getProductName')
323
+            ->willReturn('Nextcloud');
324
+
325
+        // Ensure the correct initial state is setup
326
+        // Shared node is a file so this is a single file share:
327
+        $view = 'public-file-share';
328
+        // Set up initial state
329
+        $initialState = [];
330
+        $this->initialState->expects(self::any())
331
+            ->method('provideInitialState')
332
+            ->willReturnCallback(function ($key, $value) use (&$initialState): void {
333
+                $initialState[$key] = $value;
334
+            });
335
+        $expectedInitialState = [
336
+            'isPublic' => true,
337
+            'sharingToken' => 'token',
338
+            'sharePermissions' => (Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE),
339
+            'filename' => $filename,
340
+            'view' => $view,
341
+            'fileId' => 111,
342
+            'owner' => 'ownerUID',
343
+            'ownerDisplayName' => 'ownerDisplay',
344
+            'isFileRequest' => false,
345
+            'templates' => [],
346
+        ];
347
+
348
+        $response = $this->shareController->showShare();
349
+
350
+        $this->assertEquals($expectedInitialState, $initialState);
351
+
352
+        $csp = new ContentSecurityPolicy();
353
+        $csp->addAllowedFrameDomain('\'self\'');
354
+        $expectedResponse = new PublicTemplateResponse('files', 'index');
355
+        $expectedResponse->setParams(['pageTitle' => $filename]);
356
+        $expectedResponse->setContentSecurityPolicy($csp);
357
+        $expectedResponse->setHeaderTitle($filename);
358
+        $expectedResponse->setHeaderDetails('shared by ownerDisplay');
359
+        $expectedResponse->setHeaderActions([
360
+            new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
361
+            new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
362
+            new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
363
+        ]);
364
+
365
+        $this->assertEquals($expectedResponse, $response);
366
+    }
367
+
368
+    public function testShowFileDropShare(): void {
369
+        $filename = 'folder1';
370
+
371
+        $this->shareController->setToken('token');
372
+
373
+        $owner = $this->createMock(IUser::class);
374
+        $owner->method('getDisplayName')->willReturn('ownerDisplay');
375
+        $owner->method('getUID')->willReturn('ownerUID');
376
+        $owner->method('isEnabled')->willReturn(true);
377
+
378
+        $initiator = $this->createMock(IUser::class);
379
+        $initiator->method('getDisplayName')->willReturn('initiatorDisplay');
380
+        $initiator->method('getUID')->willReturn('initiatorUID');
381
+        $initiator->method('isEnabled')->willReturn(true);
382
+
383
+        $file = $this->createMock(Folder::class);
384
+        $file->method('isReadable')->willReturn(true);
385
+        $file->method('isShareable')->willReturn(true);
386
+        $file->method('getId')->willReturn(1234);
387
+        $file->method('getMimetype')->willReturn('text/plain');
388
+        $file->method('getName')->willReturn($filename);
389
+
390
+        $accountName = $this->createMock(IAccountProperty::class);
391
+        $accountName->method('getScope')
392
+            ->willReturn(IAccountManager::SCOPE_PUBLISHED);
393
+        $account = $this->createMock(IAccount::class);
394
+        $account->method('getProperty')
395
+            ->with(IAccountManager::PROPERTY_DISPLAYNAME)
396
+            ->willReturn($accountName);
397
+        $this->accountManager->expects($this->once())
398
+            ->method('getAccount')
399
+            ->with($owner)
400
+            ->willReturn($account);
401
+
402
+        /** @var Manager */
403
+        $manager = Server::get(Manager::class);
404
+        $share = $manager->newShare();
405
+        $share->setId(42)
406
+            ->setPermissions(Constants::PERMISSION_CREATE)
407
+            ->setPassword('password')
408
+            ->setShareOwner('ownerUID')
409
+            ->setSharedBy('initiatorUID')
410
+            ->setNote('The note')
411
+            ->setLabel('A label')
412
+            ->setNode($file)
413
+            ->setTarget("/$filename")
414
+            ->setToken('token');
415
+
416
+        $this->appConfig
417
+            ->expects($this->once())
418
+            ->method('getValueString')
419
+            ->with('core', 'shareapi_public_link_disclaimertext', '')
420
+            ->willReturn('My disclaimer text');
421
+
422
+        $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
423
+        $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
424
+
425
+        $this->urlGenerator->expects(self::atLeastOnce())
426
+            ->method('linkToRouteAbsolute')
427
+            ->willReturnMap([
428
+                // every file has the show show share url in the opengraph url prop
429
+                ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
430
+                // there is no preview or folders so no other link for opengraph
431
+            ]);
432
+
433
+        $this->config->method('getSystemValue')
434
+            ->willReturnMap(
435
+                [
436
+                    ['max_filesize_animated_gifs_public_sharing', 10, 10],
437
+                    ['enable_previews', true, true],
438
+                    ['preview_max_x', 1024, 1024],
439
+                    ['preview_max_y', 1024, 1024],
440
+                ]
441
+            );
442
+
443
+        $this->shareManager
444
+            ->expects($this->once())
445
+            ->method('getShareByToken')
446
+            ->with('token')
447
+            ->willReturn($share);
448
+
449
+        $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
450
+            if ($uid === 'ownerUID') {
451
+                return $owner;
452
+            }
453
+            if ($uid === 'initiatorUID') {
454
+                return $initiator;
455
+            }
456
+            return null;
457
+        });
458
+
459
+        $this->eventDispatcher->method('dispatchTyped')->with(
460
+            $this->callback(function ($event) use ($share) {
461
+                if ($event instanceof BeforeTemplateRenderedEvent) {
462
+                    return $event->getShare() === $share;
463
+                } else {
464
+                    return true;
465
+                }
466
+            })
467
+        );
468
+
469
+        $this->l10n->expects($this->any())
470
+            ->method('t')
471
+            ->willReturnCallback(function ($text, $parameters) {
472
+                return vsprintf($text, $parameters);
473
+            });
474
+
475
+        // Set up initial state
476
+        $initialState = [];
477
+        $this->initialState->expects(self::any())
478
+            ->method('provideInitialState')
479
+            ->willReturnCallback(function ($key, $value) use (&$initialState): void {
480
+                $initialState[$key] = $value;
481
+            });
482
+        $expectedInitialState = [
483
+            'isPublic' => true,
484
+            'sharingToken' => 'token',
485
+            'sharePermissions' => Constants::PERMISSION_CREATE,
486
+            'filename' => $filename,
487
+            'view' => 'public-file-drop',
488
+            'disclaimer' => 'My disclaimer text',
489
+            'owner' => 'ownerUID',
490
+            'ownerDisplayName' => 'ownerDisplay',
491
+            'isFileRequest' => false,
492
+            'note' => 'The note',
493
+            'label' => 'A label',
494
+            'templates' => [],
495
+        ];
496
+
497
+        $response = $this->shareController->showShare();
498
+
499
+        $this->assertEquals($expectedInitialState, $initialState);
500
+
501
+        $csp = new ContentSecurityPolicy();
502
+        $csp->addAllowedFrameDomain('\'self\'');
503
+        $expectedResponse = new PublicTemplateResponse('files', 'index');
504
+        $expectedResponse->setParams(['pageTitle' => 'A label']);
505
+        $expectedResponse->setContentSecurityPolicy($csp);
506
+        $expectedResponse->setHeaderTitle('A label');
507
+        $expectedResponse->setHeaderDetails('shared by ownerDisplay');
508
+        $expectedResponse->setHeaderActions([
509
+            new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'shareUrl'),
510
+        ]);
511
+
512
+        $this->assertEquals($expectedResponse, $response);
513
+    }
514
+
515
+    public function testShowShareWithPrivateName(): void {
516
+        $note = 'personal note';
517
+        $filename = 'file1.txt';
518
+
519
+        $this->shareController->setToken('token');
520
+
521
+        $owner = $this->createMock(IUser::class);
522
+        $owner->method('getDisplayName')->willReturn('ownerDisplay');
523
+        $owner->method('getUID')->willReturn('ownerUID');
524
+        $owner->method('isEnabled')->willReturn(true);
525
+
526
+        $initiator = $this->createMock(IUser::class);
527
+        $initiator->method('getDisplayName')->willReturn('initiatorDisplay');
528
+        $initiator->method('getUID')->willReturn('initiatorUID');
529
+        $initiator->method('isEnabled')->willReturn(true);
530
+
531
+        $file = $this->createMock(File::class);
532
+        $file->method('getName')->willReturn($filename);
533
+        $file->method('getMimetype')->willReturn('text/plain');
534
+        $file->method('getSize')->willReturn(33);
535
+        $file->method('isReadable')->willReturn(true);
536
+        $file->method('isShareable')->willReturn(true);
537
+        $file->method('getId')->willReturn(111);
538
+
539
+        $accountName = $this->createMock(IAccountProperty::class);
540
+        $accountName->method('getScope')
541
+            ->willReturn(IAccountManager::SCOPE_LOCAL);
542
+        $account = $this->createMock(IAccount::class);
543
+        $account->method('getProperty')
544
+            ->with(IAccountManager::PROPERTY_DISPLAYNAME)
545
+            ->willReturn($accountName);
546
+        $this->accountManager->expects($this->once())
547
+            ->method('getAccount')
548
+            ->with($owner)
549
+            ->willReturn($account);
550
+
551
+        /** @var IShare */
552
+        $share = Server::get(Manager::class)->newShare();
553
+        $share->setId(42);
554
+        $share->setPassword('password')
555
+            ->setShareOwner('ownerUID')
556
+            ->setSharedBy('initiatorUID')
557
+            ->setNode($file)
558
+            ->setNote($note)
559
+            ->setToken('token')
560
+            ->setPermissions(Constants::PERMISSION_ALL & ~Constants::PERMISSION_SHARE)
561
+            ->setTarget("/$filename");
562
+
563
+        $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
564
+        $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
565
+
566
+        $this->urlGenerator->expects(self::atLeast(2))
567
+            ->method('linkToRouteAbsolute')
568
+            ->willReturnMap([
569
+                // every file has the show show share url in the opengraph url prop
570
+                ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
571
+                // this share is not an image to the default preview is used
572
+                ['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
573
+            ]);
574
+
575
+        $this->urlGenerator->expects($this->once())
576
+            ->method('getAbsoluteURL')
577
+            ->willReturnMap([
578
+                ['/public.php/dav/files/token/?accept=zip', 'downloadUrl'],
579
+            ]);
580
+
581
+        $this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
582
+
583
+        $this->config->method('getSystemValue')
584
+            ->willReturnMap(
585
+                [
586
+                    ['max_filesize_animated_gifs_public_sharing', 10, 10],
587
+                    ['enable_previews', true, true],
588
+                    ['preview_max_x', 1024, 1024],
589
+                    ['preview_max_y', 1024, 1024],
590
+                ]
591
+            );
592
+        $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
593
+        $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
594
+
595
+        $this->shareManager
596
+            ->expects($this->once())
597
+            ->method('getShareByToken')
598
+            ->with('token')
599
+            ->willReturn($share);
600
+
601
+        $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
602
+            if ($uid === 'ownerUID') {
603
+                return $owner;
604
+            }
605
+            if ($uid === 'initiatorUID') {
606
+                return $initiator;
607
+            }
608
+            return null;
609
+        });
610
+
611
+        $this->eventDispatcher->method('dispatchTyped')->with(
612
+            $this->callback(function ($event) use ($share) {
613
+                if ($event instanceof BeforeTemplateRenderedEvent) {
614
+                    return $event->getShare() === $share;
615
+                } else {
616
+                    return true;
617
+                }
618
+            })
619
+        );
620
+
621
+        $this->l10n->expects($this->any())
622
+            ->method('t')
623
+            ->willReturnCallback(function ($text, $parameters) {
624
+                return vsprintf($text, $parameters);
625
+            });
626
+
627
+        $this->defaults->expects(self::any())
628
+            ->method('getProductName')
629
+            ->willReturn('Nextcloud');
630
+
631
+        $response = $this->shareController->showShare();
632
+
633
+        $csp = new ContentSecurityPolicy();
634
+        $csp->addAllowedFrameDomain('\'self\'');
635
+        $expectedResponse = new PublicTemplateResponse('files', 'index');
636
+        $expectedResponse->setParams(['pageTitle' => $filename]);
637
+        $expectedResponse->setContentSecurityPolicy($csp);
638
+        $expectedResponse->setHeaderTitle($filename);
639
+        $expectedResponse->setHeaderDetails('');
640
+        $expectedResponse->setHeaderActions([
641
+            new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
642
+            new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
643
+            new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
644
+        ]);
645
+
646
+        $this->assertEquals($expectedResponse, $response);
647
+    }
648
+
649
+
650
+    public function testShowShareInvalid(): void {
651
+        $this->expectException(NotFoundException::class);
652
+
653
+        $filename = 'file1.txt';
654
+        $this->shareController->setToken('token');
655
+
656
+        $owner = $this->getMockBuilder(IUser::class)->getMock();
657
+        $owner->method('getDisplayName')->willReturn('ownerDisplay');
658
+        $owner->method('getUID')->willReturn('ownerUID');
659
+
660
+        $file = $this->getMockBuilder('OCP\Files\File')->getMock();
661
+        $file->method('getName')->willReturn($filename);
662
+        $file->method('getMimetype')->willReturn('text/plain');
663
+        $file->method('getSize')->willReturn(33);
664
+        $file->method('isShareable')->willReturn(false);
665
+        $file->method('isReadable')->willReturn(true);
666
+
667
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
668
+        $share->setId(42);
669
+        $share->setPassword('password')
670
+            ->setShareOwner('ownerUID')
671
+            ->setNode($file)
672
+            ->setTarget("/$filename");
673
+
674
+        $this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
675
+        $this->session->method('get')->with('public_link_authenticated')->willReturn('42');
676
+
677
+        $this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
678
+
679
+        $this->config->method('getSystemValue')
680
+            ->willReturnMap(
681
+                [
682
+                    ['max_filesize_animated_gifs_public_sharing', 10, 10],
683
+                    ['enable_previews', true, true],
684
+                ]
685
+            );
686
+        $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
687
+        $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
688
+
689
+        $this->shareManager
690
+            ->expects($this->once())
691
+            ->method('getShareByToken')
692
+            ->with('token')
693
+            ->willReturn($share);
694
+
695
+        $this->userManager->method('get')->with('ownerUID')->willReturn($owner);
696
+
697
+        $this->shareController->showShare();
698
+    }
699
+
700
+    public function testDownloadShareWithCreateOnlyShare(): void {
701
+        $share = $this->getMockBuilder(IShare::class)->getMock();
702
+        $share->method('getPassword')->willReturn('password');
703
+        $share
704
+            ->expects($this->once())
705
+            ->method('getPermissions')
706
+            ->willReturn(Constants::PERMISSION_CREATE);
707
+
708
+        $this->shareManager
709
+            ->expects($this->once())
710
+            ->method('getShareByToken')
711
+            ->with('validtoken')
712
+            ->willReturn($share);
713
+
714
+        // Test with a password protected share and no authentication
715
+        $response = $this->shareController->downloadShare('validtoken');
716
+        $expectedResponse = new DataResponse('Share has no read permission');
717
+        $this->assertEquals($expectedResponse, $response);
718
+    }
719
+
720
+    public function testDownloadShareWithoutDownloadPermission(): void {
721
+        $attributes = $this->createMock(IAttributes::class);
722
+        $attributes->expects(self::once())
723
+            ->method('getAttribute')
724
+            ->with('permissions', 'download')
725
+            ->willReturn(false);
726
+
727
+        $share = $this->createMock(IShare::class);
728
+        $share->method('getPassword')->willReturn('password');
729
+        $share->expects(self::once())
730
+            ->method('getPermissions')
731
+            ->willReturn(Constants::PERMISSION_READ);
732
+        $share->expects(self::once())
733
+            ->method('getAttributes')
734
+            ->willReturn($attributes);
735
+
736
+        $this->shareManager
737
+            ->expects(self::once())
738
+            ->method('getShareByToken')
739
+            ->with('validtoken')
740
+            ->willReturn($share);
741
+
742
+        // Test with a password protected share and no authentication
743
+        $response = $this->shareController->downloadShare('validtoken');
744
+        $expectedResponse = new DataResponse('Share has no download permission');
745
+        $this->assertEquals($expectedResponse, $response);
746
+    }
747
+
748
+    public function testDisabledOwner(): void {
749
+        $this->shareController->setToken('token');
750
+
751
+        $owner = $this->getMockBuilder(IUser::class)->getMock();
752
+        $owner->method('isEnabled')->willReturn(false);
753
+
754
+        $initiator = $this->createMock(IUser::class);
755
+        $initiator->method('isEnabled')->willReturn(false);
756
+
757
+        /* @var MockObject|Folder $folder */
758
+        $folder = $this->createMock(Folder::class);
759
+
760
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
761
+        $share->setId(42);
762
+        $share->setPermissions(Constants::PERMISSION_CREATE)
763
+            ->setShareOwner('ownerUID')
764
+            ->setSharedBy('initiatorUID')
765
+            ->setNode($folder)
766
+            ->setTarget('/share');
767
+
768
+        $this->shareManager
769
+            ->expects($this->once())
770
+            ->method('getShareByToken')
771
+            ->with('token')
772
+            ->willReturn($share);
773
+
774
+        $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
775
+            if ($uid === 'ownerUID') {
776
+                return $owner;
777
+            }
778
+            if ($uid === 'initiatorUID') {
779
+                return $initiator;
780
+            }
781
+            return null;
782
+        });
783
+
784
+        $this->expectException(NotFoundException::class);
785
+
786
+        $this->shareController->showShare();
787
+    }
788
+
789
+    public function testDisabledInitiator(): void {
790
+        $this->shareController->setToken('token');
791
+
792
+        $owner = $this->getMockBuilder(IUser::class)->getMock();
793
+        $owner->method('isEnabled')->willReturn(false);
794
+
795
+        $initiator = $this->createMock(IUser::class);
796
+        $initiator->method('isEnabled')->willReturn(true);
797
+
798
+        /* @var MockObject|Folder $folder */
799
+        $folder = $this->createMock(Folder::class);
800
+
801
+        $share = Server::get(\OCP\Share\IManager::class)->newShare();
802
+        $share->setId(42);
803
+        $share->setPermissions(Constants::PERMISSION_CREATE)
804
+            ->setShareOwner('ownerUID')
805
+            ->setSharedBy('initiatorUID')
806
+            ->setNode($folder)
807
+            ->setTarget('/share');
808
+
809
+        $this->shareManager
810
+            ->expects($this->once())
811
+            ->method('getShareByToken')
812
+            ->with('token')
813
+            ->willReturn($share);
814
+
815
+        $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
816
+            if ($uid === 'ownerUID') {
817
+                return $owner;
818
+            }
819
+            if ($uid === 'initiatorUID') {
820
+                return $initiator;
821
+            }
822
+            return null;
823
+        });
824
+
825
+        $this->expectException(NotFoundException::class);
826
+
827
+        $this->shareController->showShare();
828
+    }
829 829
 }
Please login to merge, or discard this patch.
lib/private/Preview/GeneratorHelper.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -18,23 +18,23 @@
 block discarded – undo
18 18
  * @psalm-import-type ProviderClosure from IPreview
19 19
  */
20 20
 class GeneratorHelper {
21
-	public function getThumbnail(IProviderV2 $provider, File $file, int $maxWidth, int $maxHeight, bool $crop = false): IImage|false {
22
-		if ($provider instanceof Imaginary) {
23
-			return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
24
-		}
25
-		return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
26
-	}
21
+    public function getThumbnail(IProviderV2 $provider, File $file, int $maxWidth, int $maxHeight, bool $crop = false): IImage|false {
22
+        if ($provider instanceof Imaginary) {
23
+            return $provider->getCroppedThumbnail($file, $maxWidth, $maxHeight, $crop) ?? false;
24
+        }
25
+        return $provider->getThumbnail($file, $maxWidth, $maxHeight) ?? false;
26
+    }
27 27
 
28
-	public function getImage(ISimpleFile $maxPreview): IImage {
29
-		$image = new OCPImage();
30
-		$image->loadFromData($maxPreview->getContent());
31
-		return $image;
32
-	}
28
+    public function getImage(ISimpleFile $maxPreview): IImage {
29
+        $image = new OCPImage();
30
+        $image->loadFromData($maxPreview->getContent());
31
+        return $image;
32
+    }
33 33
 
34
-	/**
35
-	 * @param \Closure|string $providerClosure (string is only authorized in unit tests)
36
-	 */
37
-	public function getProvider(\Closure|string $providerClosure): IProviderV2|false {
38
-		return $providerClosure();
39
-	}
34
+    /**
35
+     * @param \Closure|string $providerClosure (string is only authorized in unit tests)
36
+     */
37
+    public function getProvider(\Closure|string $providerClosure): IProviderV2|false {
38
+        return $providerClosure();
39
+    }
40 40
 }
Please login to merge, or discard this patch.
lib/private/Preview/Generator.php 1 patch
Indentation   +512 added lines, -512 removed lines patch added patch discarded remove patch
@@ -26,530 +26,530 @@
 block discarded – undo
26 26
 use Psr\Log\LoggerInterface;
27 27
 
28 28
 class Generator {
29
-	public const SEMAPHORE_ID_ALL = 0x0a11;
30
-	public const SEMAPHORE_ID_NEW = 0x07ea;
31
-
32
-	public function __construct(
33
-		private IConfig $config,
34
-		private IPreview $previewManager,
35
-		private GeneratorHelper $helper,
36
-		private IEventDispatcher $eventDispatcher,
37
-		private LoggerInterface $logger,
38
-		private PreviewMapper $previewMapper,
39
-		private StorageFactory $storageFactory,
40
-	) {
41
-	}
42
-
43
-	/**
44
-	 * Returns a preview of a file
45
-	 *
46
-	 * The cache is searched first and if nothing usable was found then a preview is
47
-	 * generated by one of the providers
48
-	 *
49
-	 * @return ISimpleFile
50
-	 * @throws NotFoundException
51
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
52
-	 */
53
-	public function getPreview(
54
-		File $file,
55
-		int $width = -1,
56
-		int $height = -1,
57
-		bool $crop = false,
58
-		string $mode = IPreview::MODE_FILL,
59
-		?string $mimeType = null,
60
-		bool $cacheResult = true,
61
-	): ISimpleFile {
62
-		$specification = [
63
-			'width' => $width,
64
-			'height' => $height,
65
-			'crop' => $crop,
66
-			'mode' => $mode,
67
-		];
68
-
69
-		$this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
70
-			$file,
71
-			$width,
72
-			$height,
73
-			$crop,
74
-			$mode,
75
-			$mimeType,
76
-		));
77
-
78
-		$this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
79
-			'path' => $file->getPath(),
80
-			'width' => $width,
81
-			'height' => $height,
82
-			'crop' => $crop,
83
-			'mode' => $mode,
84
-			'mimeType' => $mimeType,
85
-		]);
86
-
87
-
88
-		// since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
89
-		return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult);
90
-	}
91
-
92
-	/**
93
-	 * Generates previews of a file
94
-	 *
95
-	 * @throws NotFoundException
96
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
97
-	 */
98
-	public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile {
99
-		//Make sure that we can read the file
100
-		if (!$file->isReadable()) {
101
-			$this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
102
-			throw new NotFoundException('Cannot read file');
103
-		}
104
-
105
-		if ($mimeType === null) {
106
-			$mimeType = $file->getMimeType();
107
-		}
108
-
109
-		[$file->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$file->getId()]);
110
-
111
-		$previewVersion = null;
112
-		if ($file instanceof IVersionedPreviewFile) {
113
-			$previewVersion = $file->getPreviewVersion();
114
-		}
115
-
116
-		// Get the max preview and infer the max preview sizes from that
117
-		$maxPreview = $this->getMaxPreview($previews, $file, $mimeType, $previewVersion);
118
-		$maxPreviewImage = null; // only load the image when we need it
119
-		if ($maxPreview->getSize() === 0) {
120
-			$this->storageFactory->deletePreview($maxPreview);
121
-			$this->previewMapper->delete($maxPreview);
122
-			$this->logger->error('Max preview generated for file {path} has size 0, deleting and throwing exception.', ['path' => $file->getPath()]);
123
-			throw new NotFoundException('Max preview size 0, invalid!');
124
-		}
125
-
126
-		$maxWidth = $maxPreview->getWidth();
127
-		$maxHeight = $maxPreview->getHeight();
128
-
129
-		if ($maxWidth <= 0 || $maxHeight <= 0) {
130
-			throw new NotFoundException('The maximum preview sizes are zero or less pixels');
131
-		}
132
-
133
-		$previewFile = null;
134
-		foreach ($specifications as $specification) {
135
-			$width = $specification['width'] ?? -1;
136
-			$height = $specification['height'] ?? -1;
137
-			$crop = $specification['crop'] ?? false;
138
-			$mode = $specification['mode'] ?? IPreview::MODE_FILL;
139
-
140
-			// If both width and height are -1 we just want the max preview
141
-			if ($width === -1 && $height === -1) {
142
-				$width = $maxWidth;
143
-				$height = $maxHeight;
144
-			}
145
-
146
-			// Calculate the preview size
147
-			[$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
148
-
149
-			// No need to generate a preview that is just the max preview
150
-			if ($width === $maxWidth && $height === $maxHeight) {
151
-				// ensure correct return value if this was the last one
152
-				$previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper);
153
-				continue;
154
-			}
155
-
156
-			// Try to get a cached preview. Else generate (and store) one
157
-			try {
158
-				$preview = array_find($previews, fn (Preview $preview): bool => $preview->getWidth() === $width
159
-					&& $preview->getHeight() === $height && $preview->getMimetype() === $maxPreview->getMimetype()
160
-					&& $preview->getVersion() === $previewVersion && $preview->isCropped() === $crop);
161
-
162
-				if ($preview) {
163
-					$previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper);
164
-				} else {
165
-					if (!$this->previewManager->isMimeSupported($mimeType)) {
166
-						throw new NotFoundException();
167
-					}
168
-
169
-					if ($maxPreviewImage === null) {
170
-						$maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper));
171
-					}
172
-
173
-					$this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
174
-					$previewFile = $this->generatePreview($file, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult);
175
-				}
176
-			} catch (\InvalidArgumentException $e) {
177
-				throw new NotFoundException('', 0, $e);
178
-			}
179
-
180
-			if ($previewFile->getSize() === 0) {
181
-				$previewFile->delete();
182
-				throw new NotFoundException('Cached preview size 0, invalid!');
183
-			}
184
-		}
185
-		assert($previewFile !== null);
186
-
187
-		// Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
188
-		// Garbage Collection does NOT free this memory.  We have to do it ourselves.
189
-		if ($maxPreviewImage instanceof \OCP\Image) {
190
-			$maxPreviewImage->destroy();
191
-		}
192
-
193
-		return $previewFile;
194
-	}
195
-
196
-	/**
197
-	 * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
198
-	 * Return an identifier of the semaphore on success, which can be used to release it via
199
-	 * {@see Generator::unguardWithSemaphore()}.
200
-	 *
201
-	 * @param int $semId
202
-	 * @param int $concurrency
203
-	 * @return false|\SysvSemaphore the semaphore on success or false on failure
204
-	 */
205
-	public static function guardWithSemaphore(int $semId, int $concurrency) {
206
-		if (!extension_loaded('sysvsem')) {
207
-			return false;
208
-		}
209
-		$sem = sem_get($semId, $concurrency);
210
-		if ($sem === false) {
211
-			return false;
212
-		}
213
-		if (!sem_acquire($sem)) {
214
-			return false;
215
-		}
216
-		return $sem;
217
-	}
218
-
219
-	/**
220
-	 * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
221
-	 *
222
-	 * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
223
-	 * @return bool
224
-	 */
225
-	public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
226
-		if ($semId === false || !($semId instanceof \SysvSemaphore)) {
227
-			return false;
228
-		}
229
-		return sem_release($semId);
230
-	}
231
-
232
-	/**
233
-	 * Get the number of concurrent threads supported by the host.
234
-	 *
235
-	 * @return int number of concurrent threads, or 0 if it cannot be determined
236
-	 */
237
-	public static function getHardwareConcurrency(): int {
238
-		static $width;
239
-
240
-		if (!isset($width)) {
241
-			if (function_exists('ini_get')) {
242
-				$openBasedir = ini_get('open_basedir');
243
-				if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
244
-					$width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
245
-				} else {
246
-					$width = 0;
247
-				}
248
-			} else {
249
-				$width = 0;
250
-			}
251
-		}
252
-		return $width;
253
-	}
254
-
255
-	/**
256
-	 * Get number of concurrent preview generations from system config
257
-	 *
258
-	 * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
259
-	 * are available. If not set, the default values are determined with the hardware concurrency
260
-	 * of the host. In case the hardware concurrency cannot be determined, or the user sets an
261
-	 * invalid value, fallback values are:
262
-	 * For new images whose previews do not exist and need to be generated, 4;
263
-	 * For all preview generation requests, 8.
264
-	 * Value of `preview_concurrency_all` should be greater than or equal to that of
265
-	 * `preview_concurrency_new`, otherwise, the latter is returned.
266
-	 *
267
-	 * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
268
-	 * @return int number of concurrent preview generations, or -1 if $type is invalid
269
-	 */
270
-	public function getNumConcurrentPreviews(string $type): int {
271
-		static $cached = [];
272
-		if (array_key_exists($type, $cached)) {
273
-			return $cached[$type];
274
-		}
275
-
276
-		$hardwareConcurrency = self::getHardwareConcurrency();
277
-		switch ($type) {
278
-			case 'preview_concurrency_all':
279
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
280
-				$concurrency_all = $this->config->getSystemValueInt($type, $fallback);
281
-				$concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
282
-				$cached[$type] = max($concurrency_all, $concurrency_new);
283
-				break;
284
-			case 'preview_concurrency_new':
285
-				$fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
286
-				$cached[$type] = $this->config->getSystemValueInt($type, $fallback);
287
-				break;
288
-			default:
289
-				return -1;
290
-		}
291
-		return $cached[$type];
292
-	}
293
-
294
-	/**
295
-	 * @param Preview[] $previews
296
-	 * @throws NotFoundException
297
-	 */
298
-	private function getMaxPreview(array $previews, File $file, string $mimeType, ?string $version): Preview {
299
-		// We don't know the max preview size, so we can't use getCachedPreview.
300
-		// It might have been generated with a higher resolution than the current value.
301
-		foreach ($previews as $preview) {
302
-			if ($preview->isMax() && ($version === $preview->getVersion())) {
303
-				return $preview;
304
-			}
305
-		}
306
-
307
-		$maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
308
-		$maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
309
-
310
-		return $this->generateProviderPreview($file, $maxWidth, $maxHeight, false, true, $mimeType, $version);
311
-	}
312
-
313
-	private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): Preview {
314
-		$previewProviders = $this->previewManager->getProviders();
315
-		foreach ($previewProviders as $supportedMimeType => $providers) {
316
-			// Filter out providers that does not support this mime
317
-			if (!preg_match($supportedMimeType, $mimeType)) {
318
-				continue;
319
-			}
320
-
321
-			foreach ($providers as $providerClosure) {
322
-
323
-				$provider = $this->helper->getProvider($providerClosure);
324
-				if (!$provider) {
325
-					continue;
326
-				}
327
-
328
-				if (!$provider->isAvailable($file)) {
329
-					continue;
330
-				}
331
-
332
-				$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
333
-				$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
334
-				try {
335
-					$this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
336
-						'mimeType' => $mimeType,
337
-						'width' => $width,
338
-						'height' => $height,
339
-					]);
340
-					$preview = $this->helper->getThumbnail($provider, $file, $width, $height);
341
-				} finally {
342
-					self::unguardWithSemaphore($sem);
343
-				}
344
-
345
-				if (!($preview instanceof IImage)) {
346
-					continue;
347
-				}
348
-
349
-				try {
350
-					$previewEntry = new Preview();
351
-					$previewEntry->setFileId($file->getId());
352
-					$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
353
-					$previewEntry->setSourceMimeType($file->getMimeType());
354
-					$previewEntry->setWidth($preview->width());
355
-					$previewEntry->setHeight($preview->height());
356
-					$previewEntry->setVersion($version);
357
-					$previewEntry->setMax($max);
358
-					$previewEntry->setCropped($crop);
359
-					$previewEntry->setEncrypted(false);
360
-					$previewEntry->setMimetype($preview->dataMimeType());
361
-					$previewEntry->setEtag($file->getEtag());
362
-					$previewEntry->setMtime((new \DateTime())->getTimestamp());
363
-					$previewEntry->setSize(0);
364
-					return $this->savePreview($previewEntry, $preview);
365
-				} catch (NotPermittedException) {
366
-					throw new NotFoundException();
367
-				}
368
-			}
369
-		}
370
-
371
-		throw new NotFoundException('No provider successfully handled the preview generation');
372
-	}
373
-
374
-	/**
375
-	 * @psalm-param IPreview::MODE_* $mode
376
-	 * @return int[]
377
-	 */
378
-	private function calculateSize(int $width, int $height, bool $crop, string $mode, int $maxWidth, int $maxHeight): array {
379
-		/*
29
+    public const SEMAPHORE_ID_ALL = 0x0a11;
30
+    public const SEMAPHORE_ID_NEW = 0x07ea;
31
+
32
+    public function __construct(
33
+        private IConfig $config,
34
+        private IPreview $previewManager,
35
+        private GeneratorHelper $helper,
36
+        private IEventDispatcher $eventDispatcher,
37
+        private LoggerInterface $logger,
38
+        private PreviewMapper $previewMapper,
39
+        private StorageFactory $storageFactory,
40
+    ) {
41
+    }
42
+
43
+    /**
44
+     * Returns a preview of a file
45
+     *
46
+     * The cache is searched first and if nothing usable was found then a preview is
47
+     * generated by one of the providers
48
+     *
49
+     * @return ISimpleFile
50
+     * @throws NotFoundException
51
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
52
+     */
53
+    public function getPreview(
54
+        File $file,
55
+        int $width = -1,
56
+        int $height = -1,
57
+        bool $crop = false,
58
+        string $mode = IPreview::MODE_FILL,
59
+        ?string $mimeType = null,
60
+        bool $cacheResult = true,
61
+    ): ISimpleFile {
62
+        $specification = [
63
+            'width' => $width,
64
+            'height' => $height,
65
+            'crop' => $crop,
66
+            'mode' => $mode,
67
+        ];
68
+
69
+        $this->eventDispatcher->dispatchTyped(new BeforePreviewFetchedEvent(
70
+            $file,
71
+            $width,
72
+            $height,
73
+            $crop,
74
+            $mode,
75
+            $mimeType,
76
+        ));
77
+
78
+        $this->logger->debug('Requesting preview for {path} with width={width}, height={height}, crop={crop}, mode={mode}, mimeType={mimeType}', [
79
+            'path' => $file->getPath(),
80
+            'width' => $width,
81
+            'height' => $height,
82
+            'crop' => $crop,
83
+            'mode' => $mode,
84
+            'mimeType' => $mimeType,
85
+        ]);
86
+
87
+
88
+        // since we only ask for one preview, and the generate method return the last one it created, it returns the one we want
89
+        return $this->generatePreviews($file, [$specification], $mimeType, $cacheResult);
90
+    }
91
+
92
+    /**
93
+     * Generates previews of a file
94
+     *
95
+     * @throws NotFoundException
96
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
97
+     */
98
+    public function generatePreviews(File $file, array $specifications, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile {
99
+        //Make sure that we can read the file
100
+        if (!$file->isReadable()) {
101
+            $this->logger->warning('Cannot read file: {path}, skipping preview generation.', ['path' => $file->getPath()]);
102
+            throw new NotFoundException('Cannot read file');
103
+        }
104
+
105
+        if ($mimeType === null) {
106
+            $mimeType = $file->getMimeType();
107
+        }
108
+
109
+        [$file->getId() => $previews] = $this->previewMapper->getAvailablePreviews([$file->getId()]);
110
+
111
+        $previewVersion = null;
112
+        if ($file instanceof IVersionedPreviewFile) {
113
+            $previewVersion = $file->getPreviewVersion();
114
+        }
115
+
116
+        // Get the max preview and infer the max preview sizes from that
117
+        $maxPreview = $this->getMaxPreview($previews, $file, $mimeType, $previewVersion);
118
+        $maxPreviewImage = null; // only load the image when we need it
119
+        if ($maxPreview->getSize() === 0) {
120
+            $this->storageFactory->deletePreview($maxPreview);
121
+            $this->previewMapper->delete($maxPreview);
122
+            $this->logger->error('Max preview generated for file {path} has size 0, deleting and throwing exception.', ['path' => $file->getPath()]);
123
+            throw new NotFoundException('Max preview size 0, invalid!');
124
+        }
125
+
126
+        $maxWidth = $maxPreview->getWidth();
127
+        $maxHeight = $maxPreview->getHeight();
128
+
129
+        if ($maxWidth <= 0 || $maxHeight <= 0) {
130
+            throw new NotFoundException('The maximum preview sizes are zero or less pixels');
131
+        }
132
+
133
+        $previewFile = null;
134
+        foreach ($specifications as $specification) {
135
+            $width = $specification['width'] ?? -1;
136
+            $height = $specification['height'] ?? -1;
137
+            $crop = $specification['crop'] ?? false;
138
+            $mode = $specification['mode'] ?? IPreview::MODE_FILL;
139
+
140
+            // If both width and height are -1 we just want the max preview
141
+            if ($width === -1 && $height === -1) {
142
+                $width = $maxWidth;
143
+                $height = $maxHeight;
144
+            }
145
+
146
+            // Calculate the preview size
147
+            [$width, $height] = $this->calculateSize($width, $height, $crop, $mode, $maxWidth, $maxHeight);
148
+
149
+            // No need to generate a preview that is just the max preview
150
+            if ($width === $maxWidth && $height === $maxHeight) {
151
+                // ensure correct return value if this was the last one
152
+                $previewFile = new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper);
153
+                continue;
154
+            }
155
+
156
+            // Try to get a cached preview. Else generate (and store) one
157
+            try {
158
+                $preview = array_find($previews, fn (Preview $preview): bool => $preview->getWidth() === $width
159
+                    && $preview->getHeight() === $height && $preview->getMimetype() === $maxPreview->getMimetype()
160
+                    && $preview->getVersion() === $previewVersion && $preview->isCropped() === $crop);
161
+
162
+                if ($preview) {
163
+                    $previewFile = new PreviewFile($preview, $this->storageFactory, $this->previewMapper);
164
+                } else {
165
+                    if (!$this->previewManager->isMimeSupported($mimeType)) {
166
+                        throw new NotFoundException();
167
+                    }
168
+
169
+                    if ($maxPreviewImage === null) {
170
+                        $maxPreviewImage = $this->helper->getImage(new PreviewFile($maxPreview, $this->storageFactory, $this->previewMapper));
171
+                    }
172
+
173
+                    $this->logger->debug('Cached preview not found for file {path}, generating a new preview.', ['path' => $file->getPath()]);
174
+                    $previewFile = $this->generatePreview($file, $maxPreviewImage, $width, $height, $crop, $maxWidth, $maxHeight, $previewVersion, $cacheResult);
175
+                }
176
+            } catch (\InvalidArgumentException $e) {
177
+                throw new NotFoundException('', 0, $e);
178
+            }
179
+
180
+            if ($previewFile->getSize() === 0) {
181
+                $previewFile->delete();
182
+                throw new NotFoundException('Cached preview size 0, invalid!');
183
+            }
184
+        }
185
+        assert($previewFile !== null);
186
+
187
+        // Free memory being used by the embedded image resource.  Without this the image is kept in memory indefinitely.
188
+        // Garbage Collection does NOT free this memory.  We have to do it ourselves.
189
+        if ($maxPreviewImage instanceof \OCP\Image) {
190
+            $maxPreviewImage->destroy();
191
+        }
192
+
193
+        return $previewFile;
194
+    }
195
+
196
+    /**
197
+     * Acquire a semaphore of the specified id and concurrency, blocking if necessary.
198
+     * Return an identifier of the semaphore on success, which can be used to release it via
199
+     * {@see Generator::unguardWithSemaphore()}.
200
+     *
201
+     * @param int $semId
202
+     * @param int $concurrency
203
+     * @return false|\SysvSemaphore the semaphore on success or false on failure
204
+     */
205
+    public static function guardWithSemaphore(int $semId, int $concurrency) {
206
+        if (!extension_loaded('sysvsem')) {
207
+            return false;
208
+        }
209
+        $sem = sem_get($semId, $concurrency);
210
+        if ($sem === false) {
211
+            return false;
212
+        }
213
+        if (!sem_acquire($sem)) {
214
+            return false;
215
+        }
216
+        return $sem;
217
+    }
218
+
219
+    /**
220
+     * Releases the semaphore acquired from {@see Generator::guardWithSemaphore()}.
221
+     *
222
+     * @param false|\SysvSemaphore $semId the semaphore identifier returned by guardWithSemaphore
223
+     * @return bool
224
+     */
225
+    public static function unguardWithSemaphore(false|\SysvSemaphore $semId): bool {
226
+        if ($semId === false || !($semId instanceof \SysvSemaphore)) {
227
+            return false;
228
+        }
229
+        return sem_release($semId);
230
+    }
231
+
232
+    /**
233
+     * Get the number of concurrent threads supported by the host.
234
+     *
235
+     * @return int number of concurrent threads, or 0 if it cannot be determined
236
+     */
237
+    public static function getHardwareConcurrency(): int {
238
+        static $width;
239
+
240
+        if (!isset($width)) {
241
+            if (function_exists('ini_get')) {
242
+                $openBasedir = ini_get('open_basedir');
243
+                if (empty($openBasedir) || strpos($openBasedir, '/proc/cpuinfo') !== false) {
244
+                    $width = is_readable('/proc/cpuinfo') ? substr_count(file_get_contents('/proc/cpuinfo'), 'processor') : 0;
245
+                } else {
246
+                    $width = 0;
247
+                }
248
+            } else {
249
+                $width = 0;
250
+            }
251
+        }
252
+        return $width;
253
+    }
254
+
255
+    /**
256
+     * Get number of concurrent preview generations from system config
257
+     *
258
+     * Two config entries, `preview_concurrency_new` and `preview_concurrency_all`,
259
+     * are available. If not set, the default values are determined with the hardware concurrency
260
+     * of the host. In case the hardware concurrency cannot be determined, or the user sets an
261
+     * invalid value, fallback values are:
262
+     * For new images whose previews do not exist and need to be generated, 4;
263
+     * For all preview generation requests, 8.
264
+     * Value of `preview_concurrency_all` should be greater than or equal to that of
265
+     * `preview_concurrency_new`, otherwise, the latter is returned.
266
+     *
267
+     * @param string $type either `preview_concurrency_new` or `preview_concurrency_all`
268
+     * @return int number of concurrent preview generations, or -1 if $type is invalid
269
+     */
270
+    public function getNumConcurrentPreviews(string $type): int {
271
+        static $cached = [];
272
+        if (array_key_exists($type, $cached)) {
273
+            return $cached[$type];
274
+        }
275
+
276
+        $hardwareConcurrency = self::getHardwareConcurrency();
277
+        switch ($type) {
278
+            case 'preview_concurrency_all':
279
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency * 2 : 8;
280
+                $concurrency_all = $this->config->getSystemValueInt($type, $fallback);
281
+                $concurrency_new = $this->getNumConcurrentPreviews('preview_concurrency_new');
282
+                $cached[$type] = max($concurrency_all, $concurrency_new);
283
+                break;
284
+            case 'preview_concurrency_new':
285
+                $fallback = $hardwareConcurrency > 0 ? $hardwareConcurrency : 4;
286
+                $cached[$type] = $this->config->getSystemValueInt($type, $fallback);
287
+                break;
288
+            default:
289
+                return -1;
290
+        }
291
+        return $cached[$type];
292
+    }
293
+
294
+    /**
295
+     * @param Preview[] $previews
296
+     * @throws NotFoundException
297
+     */
298
+    private function getMaxPreview(array $previews, File $file, string $mimeType, ?string $version): Preview {
299
+        // We don't know the max preview size, so we can't use getCachedPreview.
300
+        // It might have been generated with a higher resolution than the current value.
301
+        foreach ($previews as $preview) {
302
+            if ($preview->isMax() && ($version === $preview->getVersion())) {
303
+                return $preview;
304
+            }
305
+        }
306
+
307
+        $maxWidth = $this->config->getSystemValueInt('preview_max_x', 4096);
308
+        $maxHeight = $this->config->getSystemValueInt('preview_max_y', 4096);
309
+
310
+        return $this->generateProviderPreview($file, $maxWidth, $maxHeight, false, true, $mimeType, $version);
311
+    }
312
+
313
+    private function generateProviderPreview(File $file, int $width, int $height, bool $crop, bool $max, string $mimeType, ?string $version): Preview {
314
+        $previewProviders = $this->previewManager->getProviders();
315
+        foreach ($previewProviders as $supportedMimeType => $providers) {
316
+            // Filter out providers that does not support this mime
317
+            if (!preg_match($supportedMimeType, $mimeType)) {
318
+                continue;
319
+            }
320
+
321
+            foreach ($providers as $providerClosure) {
322
+
323
+                $provider = $this->helper->getProvider($providerClosure);
324
+                if (!$provider) {
325
+                    continue;
326
+                }
327
+
328
+                if (!$provider->isAvailable($file)) {
329
+                    continue;
330
+                }
331
+
332
+                $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
333
+                $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
334
+                try {
335
+                    $this->logger->debug('Calling preview provider for {mimeType} with width={width}, height={height}', [
336
+                        'mimeType' => $mimeType,
337
+                        'width' => $width,
338
+                        'height' => $height,
339
+                    ]);
340
+                    $preview = $this->helper->getThumbnail($provider, $file, $width, $height);
341
+                } finally {
342
+                    self::unguardWithSemaphore($sem);
343
+                }
344
+
345
+                if (!($preview instanceof IImage)) {
346
+                    continue;
347
+                }
348
+
349
+                try {
350
+                    $previewEntry = new Preview();
351
+                    $previewEntry->setFileId($file->getId());
352
+                    $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
353
+                    $previewEntry->setSourceMimeType($file->getMimeType());
354
+                    $previewEntry->setWidth($preview->width());
355
+                    $previewEntry->setHeight($preview->height());
356
+                    $previewEntry->setVersion($version);
357
+                    $previewEntry->setMax($max);
358
+                    $previewEntry->setCropped($crop);
359
+                    $previewEntry->setEncrypted(false);
360
+                    $previewEntry->setMimetype($preview->dataMimeType());
361
+                    $previewEntry->setEtag($file->getEtag());
362
+                    $previewEntry->setMtime((new \DateTime())->getTimestamp());
363
+                    $previewEntry->setSize(0);
364
+                    return $this->savePreview($previewEntry, $preview);
365
+                } catch (NotPermittedException) {
366
+                    throw new NotFoundException();
367
+                }
368
+            }
369
+        }
370
+
371
+        throw new NotFoundException('No provider successfully handled the preview generation');
372
+    }
373
+
374
+    /**
375
+     * @psalm-param IPreview::MODE_* $mode
376
+     * @return int[]
377
+     */
378
+    private function calculateSize(int $width, int $height, bool $crop, string $mode, int $maxWidth, int $maxHeight): array {
379
+        /*
380 380
 		 * If we are not cropping we have to make sure the requested image
381 381
 		 * respects the aspect ratio of the original.
382 382
 		 */
383
-		if (!$crop) {
384
-			$ratio = $maxHeight / $maxWidth;
383
+        if (!$crop) {
384
+            $ratio = $maxHeight / $maxWidth;
385 385
 
386
-			if ($width === -1) {
387
-				$width = $height / $ratio;
388
-			}
389
-			if ($height === -1) {
390
-				$height = $width * $ratio;
391
-			}
386
+            if ($width === -1) {
387
+                $width = $height / $ratio;
388
+            }
389
+            if ($height === -1) {
390
+                $height = $width * $ratio;
391
+            }
392 392
 
393
-			$ratioH = $height / $maxHeight;
394
-			$ratioW = $width / $maxWidth;
393
+            $ratioH = $height / $maxHeight;
394
+            $ratioW = $width / $maxWidth;
395 395
 
396
-			/*
396
+            /*
397 397
 			 * Fill means that the $height and $width are the max
398 398
 			 * Cover means min.
399 399
 			 */
400
-			if ($mode === IPreview::MODE_FILL) {
401
-				if ($ratioH > $ratioW) {
402
-					$height = $width * $ratio;
403
-				} else {
404
-					$width = $height / $ratio;
405
-				}
406
-			} elseif ($mode === IPreview::MODE_COVER) {
407
-				if ($ratioH > $ratioW) {
408
-					$width = $height / $ratio;
409
-				} else {
410
-					$height = $width * $ratio;
411
-				}
412
-			}
413
-		}
414
-
415
-		if ($height !== $maxHeight && $width !== $maxWidth) {
416
-			/*
400
+            if ($mode === IPreview::MODE_FILL) {
401
+                if ($ratioH > $ratioW) {
402
+                    $height = $width * $ratio;
403
+                } else {
404
+                    $width = $height / $ratio;
405
+                }
406
+            } elseif ($mode === IPreview::MODE_COVER) {
407
+                if ($ratioH > $ratioW) {
408
+                    $width = $height / $ratio;
409
+                } else {
410
+                    $height = $width * $ratio;
411
+                }
412
+            }
413
+        }
414
+
415
+        if ($height !== $maxHeight && $width !== $maxWidth) {
416
+            /*
417 417
 			 * Scale to the nearest power of four
418 418
 			 */
419
-			$pow4height = 4 ** ceil(log($height) / log(4));
420
-			$pow4width = 4 ** ceil(log($width) / log(4));
421
-
422
-			// Minimum size is 64
423
-			$pow4height = max($pow4height, 64);
424
-			$pow4width = max($pow4width, 64);
425
-
426
-			$ratioH = $height / $pow4height;
427
-			$ratioW = $width / $pow4width;
428
-
429
-			if ($ratioH < $ratioW) {
430
-				$width = $pow4width;
431
-				$height /= $ratioW;
432
-			} else {
433
-				$height = $pow4height;
434
-				$width /= $ratioH;
435
-			}
436
-		}
437
-
438
-		/*
419
+            $pow4height = 4 ** ceil(log($height) / log(4));
420
+            $pow4width = 4 ** ceil(log($width) / log(4));
421
+
422
+            // Minimum size is 64
423
+            $pow4height = max($pow4height, 64);
424
+            $pow4width = max($pow4width, 64);
425
+
426
+            $ratioH = $height / $pow4height;
427
+            $ratioW = $width / $pow4width;
428
+
429
+            if ($ratioH < $ratioW) {
430
+                $width = $pow4width;
431
+                $height /= $ratioW;
432
+            } else {
433
+                $height = $pow4height;
434
+                $width /= $ratioH;
435
+            }
436
+        }
437
+
438
+        /*
439 439
 		 * Make sure the requested height and width fall within the max
440 440
 		 * of the preview.
441 441
 		 */
442
-		if ($height > $maxHeight) {
443
-			$ratio = $height / $maxHeight;
444
-			$height = $maxHeight;
445
-			$width /= $ratio;
446
-		}
447
-		if ($width > $maxWidth) {
448
-			$ratio = $width / $maxWidth;
449
-			$width = $maxWidth;
450
-			$height /= $ratio;
451
-		}
452
-
453
-		return [(int)round($width), (int)round($height)];
454
-	}
455
-
456
-	/**
457
-	 * @throws NotFoundException
458
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
459
-	 */
460
-	private function generatePreview(
461
-		File $file,
462
-		IImage $maxPreview,
463
-		int $width,
464
-		int $height,
465
-		bool $crop,
466
-		int $maxWidth,
467
-		int $maxHeight,
468
-		?string $version,
469
-		bool $cacheResult,
470
-	): ISimpleFile {
471
-		$preview = $maxPreview;
472
-		if (!$preview->valid()) {
473
-			throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
474
-		}
475
-
476
-		$previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
477
-		$sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
478
-		try {
479
-			if ($crop) {
480
-				if ($height !== $preview->height() && $width !== $preview->width()) {
481
-					//Resize
482
-					$widthR = $preview->width() / $width;
483
-					$heightR = $preview->height() / $height;
484
-
485
-					if ($widthR > $heightR) {
486
-						$scaleH = $height;
487
-						$scaleW = $maxWidth / $heightR;
488
-					} else {
489
-						$scaleH = $maxHeight / $widthR;
490
-						$scaleW = $width;
491
-					}
492
-					$preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
493
-				}
494
-				$cropX = (int)floor(abs($width - $preview->width()) * 0.5);
495
-				$cropY = (int)floor(abs($height - $preview->height()) * 0.5);
496
-				$preview = $preview->cropCopy($cropX, $cropY, $width, $height);
497
-			} else {
498
-				$preview = $maxPreview->resizeCopy(max($width, $height));
499
-			}
500
-		} finally {
501
-			self::unguardWithSemaphore($sem);
502
-		}
503
-
504
-		$previewEntry = new Preview();
505
-		$previewEntry->setFileId($file->getId());
506
-		$previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
507
-		$previewEntry->setWidth($width);
508
-		$previewEntry->setSourceMimeType($file->getMimeType());
509
-		$previewEntry->setHeight($height);
510
-		$previewEntry->setVersion($version);
511
-		$previewEntry->setMax(false);
512
-		$previewEntry->setCropped($crop);
513
-		$previewEntry->setEncrypted(false);
514
-		$previewEntry->setMimeType($preview->dataMimeType());
515
-		$previewEntry->setEtag($file->getEtag());
516
-		$previewEntry->setMtime((new \DateTime())->getTimestamp());
517
-		$previewEntry->setSize(0);
518
-		if ($cacheResult) {
519
-			$previewEntry = $this->savePreview($previewEntry, $preview);
520
-			return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
521
-		} else {
522
-			return new InMemoryFile($previewEntry->getName(), $preview->data());
523
-		}
524
-	}
525
-
526
-	/**
527
-	 * @throws InvalidPathException
528
-	 * @throws NotFoundException
529
-	 * @throws NotPermittedException
530
-	 * @throws \OCP\DB\Exception
531
-	 */
532
-	public function savePreview(Preview $previewEntry, IImage $preview): Preview {
533
-		$previewEntry = $this->previewMapper->insert($previewEntry);
534
-
535
-		// we need to save to DB first
536
-		try {
537
-			if ($preview instanceof IStreamImage) {
538
-				$size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
539
-			} else {
540
-				$stream = fopen('php://temp', 'w+');
541
-				fwrite($stream, $preview->data());
542
-				rewind($stream);
543
-				$size = $this->storageFactory->writePreview($previewEntry, $stream);
544
-			}
545
-			if (!$size) {
546
-				throw new \RuntimeException('Unable to write preview file');
547
-			}
548
-		} catch (\Exception $e) {
549
-			$this->previewMapper->delete($previewEntry);
550
-			throw $e;
551
-		}
552
-		$previewEntry->setSize($size);
553
-		return $this->previewMapper->update($previewEntry);
554
-	}
442
+        if ($height > $maxHeight) {
443
+            $ratio = $height / $maxHeight;
444
+            $height = $maxHeight;
445
+            $width /= $ratio;
446
+        }
447
+        if ($width > $maxWidth) {
448
+            $ratio = $width / $maxWidth;
449
+            $width = $maxWidth;
450
+            $height /= $ratio;
451
+        }
452
+
453
+        return [(int)round($width), (int)round($height)];
454
+    }
455
+
456
+    /**
457
+     * @throws NotFoundException
458
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
459
+     */
460
+    private function generatePreview(
461
+        File $file,
462
+        IImage $maxPreview,
463
+        int $width,
464
+        int $height,
465
+        bool $crop,
466
+        int $maxWidth,
467
+        int $maxHeight,
468
+        ?string $version,
469
+        bool $cacheResult,
470
+    ): ISimpleFile {
471
+        $preview = $maxPreview;
472
+        if (!$preview->valid()) {
473
+            throw new \InvalidArgumentException('Failed to generate preview, failed to load image');
474
+        }
475
+
476
+        $previewConcurrency = $this->getNumConcurrentPreviews('preview_concurrency_new');
477
+        $sem = self::guardWithSemaphore(self::SEMAPHORE_ID_NEW, $previewConcurrency);
478
+        try {
479
+            if ($crop) {
480
+                if ($height !== $preview->height() && $width !== $preview->width()) {
481
+                    //Resize
482
+                    $widthR = $preview->width() / $width;
483
+                    $heightR = $preview->height() / $height;
484
+
485
+                    if ($widthR > $heightR) {
486
+                        $scaleH = $height;
487
+                        $scaleW = $maxWidth / $heightR;
488
+                    } else {
489
+                        $scaleH = $maxHeight / $widthR;
490
+                        $scaleW = $width;
491
+                    }
492
+                    $preview = $preview->preciseResizeCopy((int)round($scaleW), (int)round($scaleH));
493
+                }
494
+                $cropX = (int)floor(abs($width - $preview->width()) * 0.5);
495
+                $cropY = (int)floor(abs($height - $preview->height()) * 0.5);
496
+                $preview = $preview->cropCopy($cropX, $cropY, $width, $height);
497
+            } else {
498
+                $preview = $maxPreview->resizeCopy(max($width, $height));
499
+            }
500
+        } finally {
501
+            self::unguardWithSemaphore($sem);
502
+        }
503
+
504
+        $previewEntry = new Preview();
505
+        $previewEntry->setFileId($file->getId());
506
+        $previewEntry->setStorageId($file->getMountPoint()->getNumericStorageId());
507
+        $previewEntry->setWidth($width);
508
+        $previewEntry->setSourceMimeType($file->getMimeType());
509
+        $previewEntry->setHeight($height);
510
+        $previewEntry->setVersion($version);
511
+        $previewEntry->setMax(false);
512
+        $previewEntry->setCropped($crop);
513
+        $previewEntry->setEncrypted(false);
514
+        $previewEntry->setMimeType($preview->dataMimeType());
515
+        $previewEntry->setEtag($file->getEtag());
516
+        $previewEntry->setMtime((new \DateTime())->getTimestamp());
517
+        $previewEntry->setSize(0);
518
+        if ($cacheResult) {
519
+            $previewEntry = $this->savePreview($previewEntry, $preview);
520
+            return new PreviewFile($previewEntry, $this->storageFactory, $this->previewMapper);
521
+        } else {
522
+            return new InMemoryFile($previewEntry->getName(), $preview->data());
523
+        }
524
+    }
525
+
526
+    /**
527
+     * @throws InvalidPathException
528
+     * @throws NotFoundException
529
+     * @throws NotPermittedException
530
+     * @throws \OCP\DB\Exception
531
+     */
532
+    public function savePreview(Preview $previewEntry, IImage $preview): Preview {
533
+        $previewEntry = $this->previewMapper->insert($previewEntry);
534
+
535
+        // we need to save to DB first
536
+        try {
537
+            if ($preview instanceof IStreamImage) {
538
+                $size = $this->storageFactory->writePreview($previewEntry, $preview->resource());
539
+            } else {
540
+                $stream = fopen('php://temp', 'w+');
541
+                fwrite($stream, $preview->data());
542
+                rewind($stream);
543
+                $size = $this->storageFactory->writePreview($previewEntry, $stream);
544
+            }
545
+            if (!$size) {
546
+                throw new \RuntimeException('Unable to write preview file');
547
+            }
548
+        } catch (\Exception $e) {
549
+            $this->previewMapper->delete($previewEntry);
550
+            throw $e;
551
+        }
552
+        $previewEntry->setSize($size);
553
+        return $this->previewMapper->update($previewEntry);
554
+    }
555 555
 }
Please login to merge, or discard this patch.
lib/private/PreviewManager.php 1 patch
Indentation   +414 added lines, -414 removed lines patch added patch discarded remove patch
@@ -33,418 +33,418 @@
 block discarded – undo
33 33
  * @psalm-import-type ProviderClosure from IPreview
34 34
  */
35 35
 class PreviewManager implements IPreview {
36
-	protected IConfig $config;
37
-	protected IRootFolder $rootFolder;
38
-	protected IEventDispatcher $eventDispatcher;
39
-	private ?Generator $generator = null;
40
-	private GeneratorHelper $helper;
41
-	protected bool $providerListDirty = false;
42
-	protected bool $registeredCoreProviders = false;
43
-	/**
44
-	 * @var array<string, list<ProviderClosure>> $providers
45
-	 */
46
-	protected array $providers = [];
47
-
48
-	/** @var array mime type => support status */
49
-	protected array $mimeTypeSupportMap = [];
50
-	/** @var ?list<class-string<IProviderV2>> $defaultProviders */
51
-	protected ?array $defaultProviders = null;
52
-	protected ?string $userId;
53
-	private Coordinator $bootstrapCoordinator;
54
-
55
-	/**
56
-	 * Hash map (without value) of loaded bootstrap providers
57
-	 * @psalm-var array<string, null>
58
-	 */
59
-	private array $loadedBootstrapProviders = [];
60
-	private ContainerInterface $container;
61
-	private IBinaryFinder $binaryFinder;
62
-	private IMagickSupport $imagickSupport;
63
-	private bool $enablePreviews;
64
-
65
-	public function __construct(
66
-		IConfig $config,
67
-		IRootFolder $rootFolder,
68
-		IEventDispatcher $eventDispatcher,
69
-		GeneratorHelper $helper,
70
-		?string $userId,
71
-		Coordinator $bootstrapCoordinator,
72
-		ContainerInterface $container,
73
-		IBinaryFinder $binaryFinder,
74
-		IMagickSupport $imagickSupport,
75
-	) {
76
-		$this->config = $config;
77
-		$this->rootFolder = $rootFolder;
78
-		$this->eventDispatcher = $eventDispatcher;
79
-		$this->helper = $helper;
80
-		$this->userId = $userId;
81
-		$this->bootstrapCoordinator = $bootstrapCoordinator;
82
-		$this->container = $container;
83
-		$this->binaryFinder = $binaryFinder;
84
-		$this->imagickSupport = $imagickSupport;
85
-		$this->enablePreviews = $config->getSystemValueBool('enable_previews', true);
86
-	}
87
-
88
-	/**
89
-	 * In order to improve lazy loading a closure can be registered which will be
90
-	 * called in case preview providers are actually requested
91
-	 *
92
-	 * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
93
-	 * @param ProviderClosure $callable
94
-	 */
95
-	public function registerProvider(string $mimeTypeRegex, Closure $callable): void {
96
-		if (!$this->enablePreviews) {
97
-			return;
98
-		}
99
-
100
-		if (!isset($this->providers[$mimeTypeRegex])) {
101
-			$this->providers[$mimeTypeRegex] = [];
102
-		}
103
-		$this->providers[$mimeTypeRegex][] = $callable;
104
-		$this->providerListDirty = true;
105
-	}
106
-
107
-	/**
108
-	 * Get all providers
109
-	 */
110
-	public function getProviders(): array {
111
-		if (!$this->enablePreviews) {
112
-			return [];
113
-		}
114
-
115
-		$this->registerCoreProviders();
116
-		$this->registerBootstrapProviders();
117
-		if ($this->providerListDirty) {
118
-			$keys = array_map('strlen', array_keys($this->providers));
119
-			array_multisort($keys, SORT_DESC, $this->providers);
120
-			$this->providerListDirty = false;
121
-		}
122
-
123
-		return $this->providers;
124
-	}
125
-
126
-	/**
127
-	 * Does the manager have any providers
128
-	 */
129
-	public function hasProviders(): bool {
130
-		$this->registerCoreProviders();
131
-		return !empty($this->providers);
132
-	}
133
-
134
-	private function getGenerator(): Generator {
135
-		if ($this->generator === null) {
136
-			$this->generator = new Generator(
137
-				$this->config,
138
-				$this,
139
-				new GeneratorHelper(),
140
-				$this->eventDispatcher,
141
-				$this->container->get(LoggerInterface::class),
142
-				$this->container->get(PreviewMapper::class),
143
-				$this->container->get(StorageFactory::class),
144
-			);
145
-		}
146
-		return $this->generator;
147
-	}
148
-
149
-	public function getPreview(
150
-		File $file,
151
-		int $width = -1,
152
-		int $height = -1,
153
-		bool $crop = false,
154
-		string $mode = IPreview::MODE_FILL,
155
-		?string $mimeType = null,
156
-		bool $cacheResult = true,
157
-	): ISimpleFile {
158
-		$this->throwIfPreviewsDisabled($file, $mimeType);
159
-		$previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
160
-		$sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
161
-		try {
162
-			$preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult);
163
-		} finally {
164
-			Generator::unguardWithSemaphore($sem);
165
-		}
166
-
167
-		return $preview;
168
-	}
169
-
170
-	/**
171
-	 * Generates previews of a file
172
-	 *
173
-	 * @param array $specifications
174
-	 * @return ISimpleFile the last preview that was generated
175
-	 * @throws NotFoundException
176
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
177
-	 * @since 19.0.0
178
-	 */
179
-	public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile {
180
-		$this->throwIfPreviewsDisabled($file, $mimeType);
181
-		return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
182
-	}
183
-
184
-	public function isMimeSupported(string $mimeType = '*'): bool {
185
-		if (!$this->enablePreviews) {
186
-			return false;
187
-		}
188
-
189
-		if (isset($this->mimeTypeSupportMap[$mimeType])) {
190
-			return $this->mimeTypeSupportMap[$mimeType];
191
-		}
192
-
193
-		$this->registerCoreProviders();
194
-		$this->registerBootstrapProviders();
195
-		$providerMimeTypes = array_keys($this->providers);
196
-		foreach ($providerMimeTypes as $supportedMimeType) {
197
-			if (preg_match($supportedMimeType, $mimeType)) {
198
-				$this->mimeTypeSupportMap[$mimeType] = true;
199
-				return true;
200
-			}
201
-		}
202
-		$this->mimeTypeSupportMap[$mimeType] = false;
203
-		return false;
204
-	}
205
-
206
-	public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool {
207
-		if (!$this->enablePreviews) {
208
-			return false;
209
-		}
210
-
211
-		$fileMimeType = $mimeType ?? $file->getMimeType();
212
-
213
-		$this->registerCoreProviders();
214
-		if (!$this->isMimeSupported($fileMimeType)) {
215
-			return false;
216
-		}
217
-
218
-		$mount = $file->getMountPoint();
219
-		if ($mount && !$mount->getOption('previews', true)) {
220
-			return false;
221
-		}
222
-
223
-		foreach ($this->providers as $supportedMimeType => $providers) {
224
-			if (preg_match($supportedMimeType, $fileMimeType)) {
225
-				foreach ($providers as $providerClosure) {
226
-					$provider = $this->helper->getProvider($providerClosure);
227
-					if (!$provider) {
228
-						continue;
229
-					}
230
-					if ($provider->isAvailable($file)) {
231
-						return true;
232
-					}
233
-				}
234
-			}
235
-		}
236
-		return false;
237
-	}
238
-
239
-	/**
240
-	 * List of enabled default providers
241
-	 *
242
-	 * The following providers are enabled by default:
243
-	 *  - OC\Preview\PNG
244
-	 *  - OC\Preview\JPEG
245
-	 *  - OC\Preview\GIF
246
-	 *  - OC\Preview\BMP
247
-	 *  - OC\Preview\XBitmap
248
-	 *  - OC\Preview\MarkDown
249
-	 *  - OC\Preview\MP3
250
-	 *  - OC\Preview\TXT
251
-	 *
252
-	 * The following providers are disabled by default due to performance or privacy concerns:
253
-	 *  - OC\Preview\Font
254
-	 *  - OC\Preview\HEIC
255
-	 *  - OC\Preview\Illustrator
256
-	 *  - OC\Preview\Movie
257
-	 *  - OC\Preview\MSOfficeDoc
258
-	 *  - OC\Preview\MSOffice2003
259
-	 *  - OC\Preview\MSOffice2007
260
-	 *  - OC\Preview\OpenDocument
261
-	 *  - OC\Preview\PDF
262
-	 *  - OC\Preview\Photoshop
263
-	 *  - OC\Preview\Postscript
264
-	 *  - OC\Preview\StarOffice
265
-	 *  - OC\Preview\SVG
266
-	 *  - OC\Preview\TIFF
267
-	 *
268
-	 * @return list<class-string<IProviderV2>>
269
-	 */
270
-	protected function getEnabledDefaultProvider(): array {
271
-		if ($this->defaultProviders !== null) {
272
-			return $this->defaultProviders;
273
-		}
274
-
275
-		$imageProviders = [
276
-			Preview\PNG::class,
277
-			Preview\JPEG::class,
278
-			Preview\GIF::class,
279
-			Preview\BMP::class,
280
-			Preview\XBitmap::class,
281
-			Preview\Krita::class,
282
-			Preview\WebP::class,
283
-		];
284
-
285
-		$this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([
286
-			Preview\MarkDown::class,
287
-			Preview\MP3::class,
288
-			Preview\TXT::class,
289
-			Preview\OpenDocument::class,
290
-		], $imageProviders));
291
-
292
-		if (in_array(Preview\Image::class, $this->defaultProviders)) {
293
-			$this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
294
-		}
295
-		$this->defaultProviders = array_values(array_unique($this->defaultProviders));
296
-		/** @var list<class-string<IProviderV2>> $providers */
297
-		$providers = $this->defaultProviders;
298
-		return $providers;
299
-	}
300
-
301
-	/**
302
-	 * Register the default providers (if enabled)
303
-	 */
304
-	protected function registerCoreProvider(string $class, string $mimeType, array $options = []): void {
305
-		if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
306
-			$this->registerProvider($mimeType, function () use ($class, $options) {
307
-				return new $class($options);
308
-			});
309
-		}
310
-	}
311
-
312
-	/**
313
-	 * Register the default providers (if enabled)
314
-	 */
315
-	protected function registerCoreProviders(): void {
316
-		if ($this->registeredCoreProviders) {
317
-			return;
318
-		}
319
-		$this->registeredCoreProviders = true;
320
-
321
-		$this->registerCoreProvider(Preview\TXT::class, '/text\/plain/');
322
-		$this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/');
323
-		$this->registerCoreProvider(Preview\PNG::class, '/image\/png/');
324
-		$this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/');
325
-		$this->registerCoreProvider(Preview\GIF::class, '/image\/gif/');
326
-		$this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/');
327
-		$this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/');
328
-		$this->registerCoreProvider(Preview\WebP::class, '/image\/webp/');
329
-		$this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
330
-		$this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/');
331
-		$this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
332
-		$this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
333
-		$this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes());
334
-
335
-		// SVG and Bitmap require imagick
336
-		if ($this->imagickSupport->hasExtension()) {
337
-			$imagickProviders = [
338
-				'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
339
-				'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class],
340
-				'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class],
341
-				'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class],
342
-				'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class],
343
-				'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class],
344
-				'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class],
345
-				'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class],
346
-				'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class],
347
-				'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class],
348
-			];
349
-
350
-			foreach ($imagickProviders as $queryFormat => $provider) {
351
-				$class = $provider['class'];
352
-				if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
353
-					continue;
354
-				}
355
-
356
-				if ($this->imagickSupport->supportsFormat($queryFormat)) {
357
-					$this->registerCoreProvider($class, $provider['mimetype']);
358
-				}
359
-			}
360
-		}
361
-
362
-		$this->registerCoreProvidersOffice();
363
-
364
-		// Video requires ffmpeg
365
-		if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
366
-			$movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
367
-			if (!is_string($movieBinary)) {
368
-				$movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg');
369
-			}
370
-
371
-
372
-			if (is_string($movieBinary)) {
373
-				$this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]);
374
-			}
375
-		}
376
-	}
377
-
378
-	private function registerCoreProvidersOffice(): void {
379
-		$officeProviders = [
380
-			['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
381
-			['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
382
-			['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
383
-			['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
384
-			['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
385
-			['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
386
-		];
387
-
388
-		$findBinary = true;
389
-		$officeBinary = false;
390
-
391
-		foreach ($officeProviders as $provider) {
392
-			$class = $provider['class'];
393
-			if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
394
-				continue;
395
-			}
396
-
397
-			if ($findBinary) {
398
-				// Office requires openoffice or libreoffice
399
-				$officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
400
-				if ($officeBinary === false) {
401
-					$officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
402
-				}
403
-				if ($officeBinary === false) {
404
-					$officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
405
-				}
406
-				$findBinary = false;
407
-			}
408
-
409
-			if ($officeBinary) {
410
-				$this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
411
-			}
412
-		}
413
-	}
414
-
415
-	private function registerBootstrapProviders(): void {
416
-		$context = $this->bootstrapCoordinator->getRegistrationContext();
417
-
418
-		if ($context === null) {
419
-			// Just ignore for now
420
-			return;
421
-		}
422
-
423
-		$providers = $context->getPreviewProviders();
424
-		foreach ($providers as $provider) {
425
-			$key = $provider->getMimeTypeRegex() . '-' . $provider->getService();
426
-			if (array_key_exists($key, $this->loadedBootstrapProviders)) {
427
-				// Do not load the provider more than once
428
-				continue;
429
-			}
430
-			$this->loadedBootstrapProviders[$key] = null;
431
-
432
-			$this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider): IProviderV2|false {
433
-				try {
434
-					return $this->container->get($provider->getService());
435
-				} catch (NotFoundExceptionInterface) {
436
-					return false;
437
-				}
438
-			});
439
-		}
440
-	}
441
-
442
-	/**
443
-	 * @throws NotFoundException if preview generation is disabled
444
-	 */
445
-	private function throwIfPreviewsDisabled(File $file, ?string $mimeType = null): void {
446
-		if (!$this->isAvailable($file, $mimeType)) {
447
-			throw new NotFoundException('Previews disabled');
448
-		}
449
-	}
36
+    protected IConfig $config;
37
+    protected IRootFolder $rootFolder;
38
+    protected IEventDispatcher $eventDispatcher;
39
+    private ?Generator $generator = null;
40
+    private GeneratorHelper $helper;
41
+    protected bool $providerListDirty = false;
42
+    protected bool $registeredCoreProviders = false;
43
+    /**
44
+     * @var array<string, list<ProviderClosure>> $providers
45
+     */
46
+    protected array $providers = [];
47
+
48
+    /** @var array mime type => support status */
49
+    protected array $mimeTypeSupportMap = [];
50
+    /** @var ?list<class-string<IProviderV2>> $defaultProviders */
51
+    protected ?array $defaultProviders = null;
52
+    protected ?string $userId;
53
+    private Coordinator $bootstrapCoordinator;
54
+
55
+    /**
56
+     * Hash map (without value) of loaded bootstrap providers
57
+     * @psalm-var array<string, null>
58
+     */
59
+    private array $loadedBootstrapProviders = [];
60
+    private ContainerInterface $container;
61
+    private IBinaryFinder $binaryFinder;
62
+    private IMagickSupport $imagickSupport;
63
+    private bool $enablePreviews;
64
+
65
+    public function __construct(
66
+        IConfig $config,
67
+        IRootFolder $rootFolder,
68
+        IEventDispatcher $eventDispatcher,
69
+        GeneratorHelper $helper,
70
+        ?string $userId,
71
+        Coordinator $bootstrapCoordinator,
72
+        ContainerInterface $container,
73
+        IBinaryFinder $binaryFinder,
74
+        IMagickSupport $imagickSupport,
75
+    ) {
76
+        $this->config = $config;
77
+        $this->rootFolder = $rootFolder;
78
+        $this->eventDispatcher = $eventDispatcher;
79
+        $this->helper = $helper;
80
+        $this->userId = $userId;
81
+        $this->bootstrapCoordinator = $bootstrapCoordinator;
82
+        $this->container = $container;
83
+        $this->binaryFinder = $binaryFinder;
84
+        $this->imagickSupport = $imagickSupport;
85
+        $this->enablePreviews = $config->getSystemValueBool('enable_previews', true);
86
+    }
87
+
88
+    /**
89
+     * In order to improve lazy loading a closure can be registered which will be
90
+     * called in case preview providers are actually requested
91
+     *
92
+     * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
93
+     * @param ProviderClosure $callable
94
+     */
95
+    public function registerProvider(string $mimeTypeRegex, Closure $callable): void {
96
+        if (!$this->enablePreviews) {
97
+            return;
98
+        }
99
+
100
+        if (!isset($this->providers[$mimeTypeRegex])) {
101
+            $this->providers[$mimeTypeRegex] = [];
102
+        }
103
+        $this->providers[$mimeTypeRegex][] = $callable;
104
+        $this->providerListDirty = true;
105
+    }
106
+
107
+    /**
108
+     * Get all providers
109
+     */
110
+    public function getProviders(): array {
111
+        if (!$this->enablePreviews) {
112
+            return [];
113
+        }
114
+
115
+        $this->registerCoreProviders();
116
+        $this->registerBootstrapProviders();
117
+        if ($this->providerListDirty) {
118
+            $keys = array_map('strlen', array_keys($this->providers));
119
+            array_multisort($keys, SORT_DESC, $this->providers);
120
+            $this->providerListDirty = false;
121
+        }
122
+
123
+        return $this->providers;
124
+    }
125
+
126
+    /**
127
+     * Does the manager have any providers
128
+     */
129
+    public function hasProviders(): bool {
130
+        $this->registerCoreProviders();
131
+        return !empty($this->providers);
132
+    }
133
+
134
+    private function getGenerator(): Generator {
135
+        if ($this->generator === null) {
136
+            $this->generator = new Generator(
137
+                $this->config,
138
+                $this,
139
+                new GeneratorHelper(),
140
+                $this->eventDispatcher,
141
+                $this->container->get(LoggerInterface::class),
142
+                $this->container->get(PreviewMapper::class),
143
+                $this->container->get(StorageFactory::class),
144
+            );
145
+        }
146
+        return $this->generator;
147
+    }
148
+
149
+    public function getPreview(
150
+        File $file,
151
+        int $width = -1,
152
+        int $height = -1,
153
+        bool $crop = false,
154
+        string $mode = IPreview::MODE_FILL,
155
+        ?string $mimeType = null,
156
+        bool $cacheResult = true,
157
+    ): ISimpleFile {
158
+        $this->throwIfPreviewsDisabled($file, $mimeType);
159
+        $previewConcurrency = $this->getGenerator()->getNumConcurrentPreviews('preview_concurrency_all');
160
+        $sem = Generator::guardWithSemaphore(Generator::SEMAPHORE_ID_ALL, $previewConcurrency);
161
+        try {
162
+            $preview = $this->getGenerator()->getPreview($file, $width, $height, $crop, $mode, $mimeType, $cacheResult);
163
+        } finally {
164
+            Generator::unguardWithSemaphore($sem);
165
+        }
166
+
167
+        return $preview;
168
+    }
169
+
170
+    /**
171
+     * Generates previews of a file
172
+     *
173
+     * @param array $specifications
174
+     * @return ISimpleFile the last preview that was generated
175
+     * @throws NotFoundException
176
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
177
+     * @since 19.0.0
178
+     */
179
+    public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile {
180
+        $this->throwIfPreviewsDisabled($file, $mimeType);
181
+        return $this->getGenerator()->generatePreviews($file, $specifications, $mimeType);
182
+    }
183
+
184
+    public function isMimeSupported(string $mimeType = '*'): bool {
185
+        if (!$this->enablePreviews) {
186
+            return false;
187
+        }
188
+
189
+        if (isset($this->mimeTypeSupportMap[$mimeType])) {
190
+            return $this->mimeTypeSupportMap[$mimeType];
191
+        }
192
+
193
+        $this->registerCoreProviders();
194
+        $this->registerBootstrapProviders();
195
+        $providerMimeTypes = array_keys($this->providers);
196
+        foreach ($providerMimeTypes as $supportedMimeType) {
197
+            if (preg_match($supportedMimeType, $mimeType)) {
198
+                $this->mimeTypeSupportMap[$mimeType] = true;
199
+                return true;
200
+            }
201
+        }
202
+        $this->mimeTypeSupportMap[$mimeType] = false;
203
+        return false;
204
+    }
205
+
206
+    public function isAvailable(\OCP\Files\FileInfo $file, ?string $mimeType = null): bool {
207
+        if (!$this->enablePreviews) {
208
+            return false;
209
+        }
210
+
211
+        $fileMimeType = $mimeType ?? $file->getMimeType();
212
+
213
+        $this->registerCoreProviders();
214
+        if (!$this->isMimeSupported($fileMimeType)) {
215
+            return false;
216
+        }
217
+
218
+        $mount = $file->getMountPoint();
219
+        if ($mount && !$mount->getOption('previews', true)) {
220
+            return false;
221
+        }
222
+
223
+        foreach ($this->providers as $supportedMimeType => $providers) {
224
+            if (preg_match($supportedMimeType, $fileMimeType)) {
225
+                foreach ($providers as $providerClosure) {
226
+                    $provider = $this->helper->getProvider($providerClosure);
227
+                    if (!$provider) {
228
+                        continue;
229
+                    }
230
+                    if ($provider->isAvailable($file)) {
231
+                        return true;
232
+                    }
233
+                }
234
+            }
235
+        }
236
+        return false;
237
+    }
238
+
239
+    /**
240
+     * List of enabled default providers
241
+     *
242
+     * The following providers are enabled by default:
243
+     *  - OC\Preview\PNG
244
+     *  - OC\Preview\JPEG
245
+     *  - OC\Preview\GIF
246
+     *  - OC\Preview\BMP
247
+     *  - OC\Preview\XBitmap
248
+     *  - OC\Preview\MarkDown
249
+     *  - OC\Preview\MP3
250
+     *  - OC\Preview\TXT
251
+     *
252
+     * The following providers are disabled by default due to performance or privacy concerns:
253
+     *  - OC\Preview\Font
254
+     *  - OC\Preview\HEIC
255
+     *  - OC\Preview\Illustrator
256
+     *  - OC\Preview\Movie
257
+     *  - OC\Preview\MSOfficeDoc
258
+     *  - OC\Preview\MSOffice2003
259
+     *  - OC\Preview\MSOffice2007
260
+     *  - OC\Preview\OpenDocument
261
+     *  - OC\Preview\PDF
262
+     *  - OC\Preview\Photoshop
263
+     *  - OC\Preview\Postscript
264
+     *  - OC\Preview\StarOffice
265
+     *  - OC\Preview\SVG
266
+     *  - OC\Preview\TIFF
267
+     *
268
+     * @return list<class-string<IProviderV2>>
269
+     */
270
+    protected function getEnabledDefaultProvider(): array {
271
+        if ($this->defaultProviders !== null) {
272
+            return $this->defaultProviders;
273
+        }
274
+
275
+        $imageProviders = [
276
+            Preview\PNG::class,
277
+            Preview\JPEG::class,
278
+            Preview\GIF::class,
279
+            Preview\BMP::class,
280
+            Preview\XBitmap::class,
281
+            Preview\Krita::class,
282
+            Preview\WebP::class,
283
+        ];
284
+
285
+        $this->defaultProviders = $this->config->getSystemValue('enabledPreviewProviders', array_merge([
286
+            Preview\MarkDown::class,
287
+            Preview\MP3::class,
288
+            Preview\TXT::class,
289
+            Preview\OpenDocument::class,
290
+        ], $imageProviders));
291
+
292
+        if (in_array(Preview\Image::class, $this->defaultProviders)) {
293
+            $this->defaultProviders = array_merge($this->defaultProviders, $imageProviders);
294
+        }
295
+        $this->defaultProviders = array_values(array_unique($this->defaultProviders));
296
+        /** @var list<class-string<IProviderV2>> $providers */
297
+        $providers = $this->defaultProviders;
298
+        return $providers;
299
+    }
300
+
301
+    /**
302
+     * Register the default providers (if enabled)
303
+     */
304
+    protected function registerCoreProvider(string $class, string $mimeType, array $options = []): void {
305
+        if (in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
306
+            $this->registerProvider($mimeType, function () use ($class, $options) {
307
+                return new $class($options);
308
+            });
309
+        }
310
+    }
311
+
312
+    /**
313
+     * Register the default providers (if enabled)
314
+     */
315
+    protected function registerCoreProviders(): void {
316
+        if ($this->registeredCoreProviders) {
317
+            return;
318
+        }
319
+        $this->registeredCoreProviders = true;
320
+
321
+        $this->registerCoreProvider(Preview\TXT::class, '/text\/plain/');
322
+        $this->registerCoreProvider(Preview\MarkDown::class, '/text\/(x-)?markdown/');
323
+        $this->registerCoreProvider(Preview\PNG::class, '/image\/png/');
324
+        $this->registerCoreProvider(Preview\JPEG::class, '/image\/jpeg/');
325
+        $this->registerCoreProvider(Preview\GIF::class, '/image\/gif/');
326
+        $this->registerCoreProvider(Preview\BMP::class, '/image\/bmp/');
327
+        $this->registerCoreProvider(Preview\XBitmap::class, '/image\/x-xbitmap/');
328
+        $this->registerCoreProvider(Preview\WebP::class, '/image\/webp/');
329
+        $this->registerCoreProvider(Preview\Krita::class, '/application\/x-krita/');
330
+        $this->registerCoreProvider(Preview\MP3::class, '/audio\/mpeg$/');
331
+        $this->registerCoreProvider(Preview\OpenDocument::class, '/application\/vnd.oasis.opendocument.*/');
332
+        $this->registerCoreProvider(Preview\Imaginary::class, Preview\Imaginary::supportedMimeTypes());
333
+        $this->registerCoreProvider(Preview\ImaginaryPDF::class, Preview\ImaginaryPDF::supportedMimeTypes());
334
+
335
+        // SVG and Bitmap require imagick
336
+        if ($this->imagickSupport->hasExtension()) {
337
+            $imagickProviders = [
338
+                'SVG' => ['mimetype' => '/image\/svg\+xml/', 'class' => Preview\SVG::class],
339
+                'TIFF' => ['mimetype' => '/image\/tiff/', 'class' => Preview\TIFF::class],
340
+                'PDF' => ['mimetype' => '/application\/pdf/', 'class' => Preview\PDF::class],
341
+                'AI' => ['mimetype' => '/application\/illustrator/', 'class' => Preview\Illustrator::class],
342
+                'PSD' => ['mimetype' => '/application\/x-photoshop/', 'class' => Preview\Photoshop::class],
343
+                'EPS' => ['mimetype' => '/application\/postscript/', 'class' => Preview\Postscript::class],
344
+                'TTF' => ['mimetype' => '/application\/(?:font-sfnt|x-font$)/', 'class' => Preview\Font::class],
345
+                'HEIC' => ['mimetype' => '/image\/(x-)?hei(f|c)/', 'class' => Preview\HEIC::class],
346
+                'TGA' => ['mimetype' => '/image\/(x-)?t(ar)?ga/', 'class' => Preview\TGA::class],
347
+                'SGI' => ['mimetype' => '/image\/(x-)?sgi/', 'class' => Preview\SGI::class],
348
+            ];
349
+
350
+            foreach ($imagickProviders as $queryFormat => $provider) {
351
+                $class = $provider['class'];
352
+                if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
353
+                    continue;
354
+                }
355
+
356
+                if ($this->imagickSupport->supportsFormat($queryFormat)) {
357
+                    $this->registerCoreProvider($class, $provider['mimetype']);
358
+                }
359
+            }
360
+        }
361
+
362
+        $this->registerCoreProvidersOffice();
363
+
364
+        // Video requires ffmpeg
365
+        if (in_array(Preview\Movie::class, $this->getEnabledDefaultProvider())) {
366
+            $movieBinary = $this->config->getSystemValue('preview_ffmpeg_path', null);
367
+            if (!is_string($movieBinary)) {
368
+                $movieBinary = $this->binaryFinder->findBinaryPath('ffmpeg');
369
+            }
370
+
371
+
372
+            if (is_string($movieBinary)) {
373
+                $this->registerCoreProvider(Preview\Movie::class, '/video\/.*/', ['movieBinary' => $movieBinary]);
374
+            }
375
+        }
376
+    }
377
+
378
+    private function registerCoreProvidersOffice(): void {
379
+        $officeProviders = [
380
+            ['mimetype' => '/application\/msword/', 'class' => Preview\MSOfficeDoc::class],
381
+            ['mimetype' => '/application\/vnd.ms-.*/', 'class' => Preview\MSOffice2003::class],
382
+            ['mimetype' => '/application\/vnd.openxmlformats-officedocument.*/', 'class' => Preview\MSOffice2007::class],
383
+            ['mimetype' => '/application\/vnd.oasis.opendocument.*/', 'class' => Preview\OpenDocument::class],
384
+            ['mimetype' => '/application\/vnd.sun.xml.*/', 'class' => Preview\StarOffice::class],
385
+            ['mimetype' => '/image\/emf/', 'class' => Preview\EMF::class],
386
+        ];
387
+
388
+        $findBinary = true;
389
+        $officeBinary = false;
390
+
391
+        foreach ($officeProviders as $provider) {
392
+            $class = $provider['class'];
393
+            if (!in_array(trim($class, '\\'), $this->getEnabledDefaultProvider())) {
394
+                continue;
395
+            }
396
+
397
+            if ($findBinary) {
398
+                // Office requires openoffice or libreoffice
399
+                $officeBinary = $this->config->getSystemValue('preview_libreoffice_path', false);
400
+                if ($officeBinary === false) {
401
+                    $officeBinary = $this->binaryFinder->findBinaryPath('libreoffice');
402
+                }
403
+                if ($officeBinary === false) {
404
+                    $officeBinary = $this->binaryFinder->findBinaryPath('openoffice');
405
+                }
406
+                $findBinary = false;
407
+            }
408
+
409
+            if ($officeBinary) {
410
+                $this->registerCoreProvider($class, $provider['mimetype'], ['officeBinary' => $officeBinary]);
411
+            }
412
+        }
413
+    }
414
+
415
+    private function registerBootstrapProviders(): void {
416
+        $context = $this->bootstrapCoordinator->getRegistrationContext();
417
+
418
+        if ($context === null) {
419
+            // Just ignore for now
420
+            return;
421
+        }
422
+
423
+        $providers = $context->getPreviewProviders();
424
+        foreach ($providers as $provider) {
425
+            $key = $provider->getMimeTypeRegex() . '-' . $provider->getService();
426
+            if (array_key_exists($key, $this->loadedBootstrapProviders)) {
427
+                // Do not load the provider more than once
428
+                continue;
429
+            }
430
+            $this->loadedBootstrapProviders[$key] = null;
431
+
432
+            $this->registerProvider($provider->getMimeTypeRegex(), function () use ($provider): IProviderV2|false {
433
+                try {
434
+                    return $this->container->get($provider->getService());
435
+                } catch (NotFoundExceptionInterface) {
436
+                    return false;
437
+                }
438
+            });
439
+        }
440
+    }
441
+
442
+    /**
443
+     * @throws NotFoundException if preview generation is disabled
444
+     */
445
+    private function throwIfPreviewsDisabled(File $file, ?string $mimeType = null): void {
446
+        if (!$this->isAvailable($file, $mimeType)) {
447
+            throw new NotFoundException('Previews disabled');
448
+        }
449
+    }
450 450
 }
Please login to merge, or discard this patch.
lib/public/IPreview.php 1 patch
Indentation   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -25,84 +25,84 @@
 block discarded – undo
25 25
  */
26 26
 #[Consumable(since: '6.0.0')]
27 27
 interface IPreview {
28
-	/**
29
-	 * @since 11.0.0
30
-	 */
31
-	public const MODE_FILL = 'fill';
28
+    /**
29
+     * @since 11.0.0
30
+     */
31
+    public const MODE_FILL = 'fill';
32 32
 
33
-	/**
34
-	 * @since 11.0.0
35
-	 */
36
-	public const MODE_COVER = 'cover';
33
+    /**
34
+     * @since 11.0.0
35
+     */
36
+    public const MODE_COVER = 'cover';
37 37
 
38
-	/**
39
-	 * In order to improve lazy loading a closure can be registered which will be
40
-	 * called in case preview providers are actually requested
41
-	 *
42
-	 * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
43
-	 * @param ProviderClosure $callable
44
-	 * @since 8.1.0
45
-	 * @see \OCP\AppFramework\Bootstrap\IRegistrationContext::registerPreviewProvider
46
-	 *
47
-	 * @deprecated 23.0.0 Register your provider via the IRegistrationContext when booting the app
48
-	 */
49
-	public function registerProvider(string $mimeTypeRegex, Closure $callable): void;
38
+    /**
39
+     * In order to improve lazy loading a closure can be registered which will be
40
+     * called in case preview providers are actually requested
41
+     *
42
+     * @param string $mimeTypeRegex Regex with the mime types that are supported by this provider
43
+     * @param ProviderClosure $callable
44
+     * @since 8.1.0
45
+     * @see \OCP\AppFramework\Bootstrap\IRegistrationContext::registerPreviewProvider
46
+     *
47
+     * @deprecated 23.0.0 Register your provider via the IRegistrationContext when booting the app
48
+     */
49
+    public function registerProvider(string $mimeTypeRegex, Closure $callable): void;
50 50
 
51
-	/**
52
-	 * Get all providers
53
-	 * @return array<string, list<ProviderClosure>>
54
-	 * @since 8.1.0
55
-	 */
56
-	public function getProviders(): array;
51
+    /**
52
+     * Get all providers
53
+     * @return array<string, list<ProviderClosure>>
54
+     * @since 8.1.0
55
+     */
56
+    public function getProviders(): array;
57 57
 
58
-	/**
59
-	 * Does the manager have any providers
60
-	 * @since 8.1.0
61
-	 */
62
-	public function hasProviders(): bool;
58
+    /**
59
+     * Does the manager have any providers
60
+     * @since 8.1.0
61
+     */
62
+    public function hasProviders(): bool;
63 63
 
64
-	/**
65
-	 * Returns a preview of a file
66
-	 *
67
-	 * The cache is searched first and if nothing usable was found then a preview is
68
-	 * generated by one of the providers
69
-	 *
70
-	 * @param IPreview::MODE_* $mode
71
-	 * @param string $mimeType To force a given mimetype for the file (files_versions needs this)
72
-	 * @param bool $cacheResult Whether to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews.
73
-	 * @return ISimpleFile
74
-	 * @throws NotFoundException
75
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
76
-	 * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
77
-	 * @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature
78
-	 */
79
-	public function getPreview(File $file, int $width = -1, int $height = -1, bool $crop = false, string $mode = IPreview::MODE_FILL, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile;
64
+    /**
65
+     * Returns a preview of a file
66
+     *
67
+     * The cache is searched first and if nothing usable was found then a preview is
68
+     * generated by one of the providers
69
+     *
70
+     * @param IPreview::MODE_* $mode
71
+     * @param string $mimeType To force a given mimetype for the file (files_versions needs this)
72
+     * @param bool $cacheResult Whether to cache the preview on the filesystem. Default to true. Can be useful to set to false to limit the amount of stored previews.
73
+     * @return ISimpleFile
74
+     * @throws NotFoundException
75
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
76
+     * @since 11.0.0 - \InvalidArgumentException was added in 12.0.0
77
+     * @since 32.0.0 - getPreview($cacheResult) added the $cacheResult argument to the signature
78
+     */
79
+    public function getPreview(File $file, int $width = -1, int $height = -1, bool $crop = false, string $mode = IPreview::MODE_FILL, ?string $mimeType = null, bool $cacheResult = true): ISimpleFile;
80 80
 
81
-	/**
82
-	 * Returns true if the passed mime type is supported
83
-	 * @param string $mimeType A glob
84
-	 * @since 6.0.0
85
-	 */
86
-	public function isMimeSupported(string $mimeType = '*'): bool;
81
+    /**
82
+     * Returns true if the passed mime type is supported
83
+     * @param string $mimeType A glob
84
+     * @since 6.0.0
85
+     */
86
+    public function isMimeSupported(string $mimeType = '*'): bool;
87 87
 
88
-	/**
89
-	 * Check if a preview can be generated for a file
90
-	 *
91
-	 * @param FileInfo $file
92
-	 * @param string|null $mimeType To force a given mimetype for the file
93
-	 * @since 8.0.0
94
-	 * @since 32.0.0 - isAvailable($mimeType) added the $mimeType argument to the signature
95
-	 */
96
-	public function isAvailable(FileInfo $file, ?string $mimeType = null): bool;
88
+    /**
89
+     * Check if a preview can be generated for a file
90
+     *
91
+     * @param FileInfo $file
92
+     * @param string|null $mimeType To force a given mimetype for the file
93
+     * @since 8.0.0
94
+     * @since 32.0.0 - isAvailable($mimeType) added the $mimeType argument to the signature
95
+     */
96
+    public function isAvailable(FileInfo $file, ?string $mimeType = null): bool;
97 97
 
98
-	/**
99
-	 * Generates previews of a file
100
-	 *
101
-	 * @param array $specifications
102
-	 * @return ISimpleFile the last preview that was generated
103
-	 * @throws NotFoundException
104
-	 * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
105
-	 * @since 19.0.0
106
-	 */
107
-	public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile;
98
+    /**
99
+     * Generates previews of a file
100
+     *
101
+     * @param array $specifications
102
+     * @return ISimpleFile the last preview that was generated
103
+     * @throws NotFoundException
104
+     * @throws \InvalidArgumentException if the preview would be invalid (in case the original image is invalid)
105
+     * @since 19.0.0
106
+     */
107
+    public function generatePreviews(File $file, array $specifications, ?string $mimeType = null): ISimpleFile;
108 108
 }
Please login to merge, or discard this patch.