3 namespace Tests\Uploads;
5 use BookStack\Entities\Repos\PageRepo;
6 use BookStack\Uploads\Image;
7 use BookStack\Uploads\ImageService;
8 use Illuminate\Support\Str;
11 class ImageTest extends TestCase
13 public function test_image_upload()
15 $page = $this->entities->page();
16 $admin = $this->users->admin();
17 $this->actingAs($admin);
19 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
20 $relPath = $imgDetails['path'];
22 $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image found at path: ' . public_path($relPath));
24 $this->files->deleteAtRelativePath($relPath);
26 $this->assertDatabaseHas('images', [
27 'url' => $this->baseUrl . $relPath,
29 'uploaded_to' => $page->id,
31 'created_by' => $admin->id,
32 'updated_by' => $admin->id,
33 'name' => $imgDetails['name'],
37 public function test_image_display_thumbnail_generation_does_not_increase_image_size()
39 $page = $this->entities->page();
40 $admin = $this->users->admin();
41 $this->actingAs($admin);
43 $originalFile = $this->files->testFilePath('compressed.png');
44 $originalFileSize = filesize($originalFile);
45 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'compressed.png');
46 $relPath = $imgDetails['path'];
48 $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image found at path: ' . public_path($relPath));
49 $displayImage = $imgDetails['response']->thumbs->display;
51 $displayImageRelPath = implode('/', array_slice(explode('/', $displayImage), 3));
52 $displayImagePath = public_path($displayImageRelPath);
53 $displayFileSize = filesize($displayImagePath);
55 $this->files->deleteAtRelativePath($relPath);
56 $this->files->deleteAtRelativePath($displayImageRelPath);
58 $this->assertEquals($originalFileSize, $displayFileSize, 'Display thumbnail generation should not increase image size');
61 public function test_image_display_thumbnail_generation_for_apng_images_uses_original_file()
63 $page = $this->entities->page();
64 $admin = $this->users->admin();
65 $this->actingAs($admin);
67 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'animated.png');
68 $this->files->deleteAtRelativePath($imgDetails['path']);
70 $this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
71 $this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
74 public function test_image_display_thumbnail_generation_for_animated_avif_images_uses_original_file()
76 $page = $this->entities->page();
77 $admin = $this->users->admin();
78 $this->actingAs($admin);
80 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page, 'animated.avif');
81 $this->files->deleteAtRelativePath($imgDetails['path']);
83 $this->assertStringContainsString('thumbs-', $imgDetails['response']->thumbs->gallery);
84 $this->assertStringNotContainsString('scaled-', $imgDetails['response']->thumbs->display);
87 public function test_image_edit()
89 $editor = $this->users->editor();
90 $this->actingAs($editor);
92 $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page());
93 $image = Image::query()->first();
95 $newName = Str::random();
96 $update = $this->put('/images/' . $image->id, ['name' => $newName]);
97 $update->assertSuccessful();
98 $update->assertSee($newName);
100 $this->files->deleteAtRelativePath($imgDetails['path']);
102 $this->assertDatabaseHas('images', [
108 public function test_image_file_update()
110 $page = $this->entities->page();
113 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
114 $relPath = $imgDetails['path'];
116 $newUpload = $this->files->uploadedImage('updated-image.png', 'compressed.png');
117 $this->assertFileEquals($this->files->testFilePath('test-image.png'), public_path($relPath));
119 $imageId = $imgDetails['response']->id;
120 $image = Image::findOrFail($imageId);
121 $image->updated_at = now()->subMonth();
124 $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
127 $this->assertFileEquals($this->files->testFilePath('compressed.png'), public_path($relPath));
130 $this->assertTrue($image->updated_at->gt(now()->subMinute()));
132 $this->files->deleteAtRelativePath($relPath);
135 public function test_image_file_update_allows_case_differences()
137 $page = $this->entities->page();
140 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
141 $relPath = $imgDetails['path'];
143 $newUpload = $this->files->uploadedImage('updated-image.PNG', 'compressed.png');
144 $this->assertFileEquals($this->files->testFilePath('test-image.png'), public_path($relPath));
146 $imageId = $imgDetails['response']->id;
147 $image = Image::findOrFail($imageId);
148 $image->updated_at = now()->subMonth();
151 $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
154 $this->assertFileEquals($this->files->testFilePath('compressed.png'), public_path($relPath));
157 $this->assertTrue($image->updated_at->gt(now()->subMinute()));
159 $this->files->deleteAtRelativePath($relPath);
162 public function test_image_file_update_does_not_allow_change_in_image_extension()
164 $page = $this->entities->page();
167 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
168 $relPath = $imgDetails['path'];
169 $newUpload = $this->files->uploadedImage('updated-image.jpg', 'compressed.png');
171 $imageId = $imgDetails['response']->id;
172 $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
174 "message" => "Image file replacements must be of the same type",
178 $this->files->deleteAtRelativePath($relPath);
181 public function test_gallery_get_list_format()
185 $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page());
186 $image = Image::query()->first();
188 $pageId = $imgDetails['page']->id;
189 $firstPageRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}");
190 $firstPageRequest->assertSuccessful();
191 $this->withHtml($firstPageRequest)->assertElementExists('div');
192 $firstPageRequest->assertSuccessful()->assertSeeText($image->name);
194 $secondPageRequest = $this->get("/images/gallery?page=2&uploaded_to={$pageId}");
195 $secondPageRequest->assertSuccessful();
196 $this->withHtml($secondPageRequest)->assertElementNotExists('div');
198 $namePartial = substr($imgDetails['name'], 0, 3);
199 $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
200 $searchHitRequest->assertSuccessful()->assertSee($imgDetails['name']);
202 $namePartial = Str::random(16);
203 $searchFailRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
204 $searchFailRequest->assertSuccessful()->assertDontSee($imgDetails['name']);
205 $searchFailRequest->assertSuccessful();
206 $this->withHtml($searchFailRequest)->assertElementNotExists('div');
209 public function test_image_gallery_lists_for_draft_page()
211 $this->actingAs($this->users->editor());
212 $draft = $this->entities->newDraftPage();
213 $this->files->uploadGalleryImageToPage($this, $draft);
214 $image = Image::query()->where('uploaded_to', '=', $draft->id)->firstOrFail();
216 $resp = $this->get("/images/gallery?page=1&uploaded_to={$draft->id}");
217 $resp->assertSee($image->getThumb(150, 150));
220 public function test_image_usage()
222 $page = $this->entities->page();
223 $editor = $this->users->editor();
224 $this->actingAs($editor);
226 $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
228 $image = Image::query()->first();
229 $page->html = '<img src="' . $image->url . '">';
232 $usage = $this->get('/images/edit/' . $image->id . '?delete=true');
233 $usage->assertSuccessful();
234 $usage->assertSeeText($page->name);
235 $usage->assertSee($page->getUrl());
237 $this->files->deleteAtRelativePath($imgDetails['path']);
240 public function test_php_files_cannot_be_uploaded()
242 $page = $this->entities->page();
243 $admin = $this->users->admin();
244 $this->actingAs($admin);
246 $fileName = 'bad.php';
247 $relPath = $this->files->expectedImagePath('gallery', $fileName);
248 $this->files->deleteAtRelativePath($relPath);
250 $file = $this->files->imageFromBase64File('bad-php.base64', $fileName);
251 $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
252 $upload->assertStatus(500);
253 $this->assertStringContainsString('The file must have a valid & supported image extension', $upload->json('message'));
255 $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
257 $this->assertDatabaseMissing('images', [
263 public function test_php_like_files_cannot_be_uploaded()
265 $page = $this->entities->page();
266 $admin = $this->users->admin();
267 $this->actingAs($admin);
269 $fileName = 'bad.phtml';
270 $relPath = $this->files->expectedImagePath('gallery', $fileName);
271 $this->files->deleteAtRelativePath($relPath);
273 $file = $this->files->imageFromBase64File('bad-phtml.base64', $fileName);
274 $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
275 $upload->assertStatus(500);
276 $this->assertStringContainsString('The file must have a valid & supported image extension', $upload->json('message'));
278 $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
281 public function test_files_with_double_extensions_will_get_sanitized()
283 $page = $this->entities->page();
284 $admin = $this->users->admin();
285 $this->actingAs($admin);
287 $fileName = 'bad.phtml.png';
288 $relPath = $this->files->expectedImagePath('gallery', $fileName);
289 $expectedRelPath = dirname($relPath) . '/bad-phtml.png';
290 $this->files->deleteAtRelativePath($expectedRelPath);
292 $file = $this->files->imageFromBase64File('bad-phtml-png.base64', $fileName);
293 $upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
294 $upload->assertStatus(200);
296 $lastImage = Image::query()->latest('id')->first();
298 $this->assertEquals('bad.phtml.png', $lastImage->name);
299 $this->assertEquals('bad-phtml.png', basename($lastImage->path));
300 $this->assertFileDoesNotExist(public_path($relPath), 'Uploaded image file name was not stripped of dots');
301 $this->assertFileExists(public_path($expectedRelPath));
303 $this->files->deleteAtRelativePath($lastImage->path);
306 public function test_url_entities_removed_from_filenames()
310 'bad-char-#-image.png',
311 'bad-char-?-image.png',
316 foreach ($badNames as $name) {
317 $galleryFile = $this->files->uploadedImage($name);
318 $page = $this->entities->page();
319 $badPath = $this->files->expectedImagePath('gallery', $name);
320 $this->files->deleteAtRelativePath($badPath);
322 $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
323 $upload->assertStatus(200);
325 $lastImage = Image::query()->latest('id')->first();
326 $newFileName = explode('.', basename($lastImage->path))[0];
328 $this->assertEquals($lastImage->name, $name);
329 $this->assertFalse(strpos($lastImage->path, $name), 'Path contains original image name');
330 $this->assertFalse(file_exists(public_path($badPath)), 'Uploaded image file name was not stripped of url entities');
332 $this->assertTrue(strlen($newFileName) > 0, 'File name was reduced to nothing');
334 $this->files->deleteAtRelativePath($lastImage->path);
338 public function test_secure_images_uploads_to_correct_place()
340 config()->set('filesystems.images', 'local_secure');
342 $galleryFile = $this->files->uploadedImage('my-secure-test-upload.png');
343 $page = $this->entities->page();
344 $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png');
346 $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
347 $upload->assertStatus(200);
349 $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: ' . $expectedPath);
351 if (file_exists($expectedPath)) {
352 unlink($expectedPath);
356 public function test_secure_image_paths_traversal_causes_500()
358 config()->set('filesystems.images', 'local_secure');
361 $resp = $this->get('/uploads/images/../../logs/laravel.log');
362 $resp->assertStatus(500);
365 public function test_secure_image_paths_traversal_on_non_secure_images_causes_404()
367 config()->set('filesystems.images', 'local');
370 $resp = $this->get('/uploads/images/../../logs/laravel.log');
371 $resp->assertStatus(404);
374 public function test_secure_image_paths_dont_serve_non_images()
376 config()->set('filesystems.images', 'local_secure');
379 $testFilePath = storage_path('/uploads/images/testing.txt');
380 file_put_contents($testFilePath, 'hello from test_secure_image_paths_dont_serve_non_images');
382 $resp = $this->get('/uploads/images/testing.txt');
383 $resp->assertStatus(404);
386 public function test_secure_images_included_in_exports()
388 config()->set('filesystems.images', 'local_secure');
390 $galleryFile = $this->files->uploadedImage('my-secure-test-upload.png');
391 $page = $this->entities->page();
392 $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-test-upload.png');
394 $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
395 $imageUrl = json_decode($upload->getContent(), true)['url'];
396 $page->html .= "<img src=\"{$imageUrl}\">";
398 $upload->assertStatus(200);
400 $encodedImageContent = base64_encode(file_get_contents($expectedPath));
401 $export = $this->get($page->getUrl('/export/html'));
402 $this->assertTrue(strpos($export->getContent(), $encodedImageContent) !== false, 'Uploaded image in export content');
404 if (file_exists($expectedPath)) {
405 unlink($expectedPath);
409 public function test_system_images_remain_public_with_local_secure()
411 config()->set('filesystems.images', 'local_secure');
413 $galleryFile = $this->files->uploadedImage('my-system-test-upload.png');
414 $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-upload.png');
416 $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []);
417 $upload->assertRedirect('/settings/customization');
419 $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: ' . $expectedPath);
421 if (file_exists($expectedPath)) {
422 unlink($expectedPath);
426 public function test_secure_images_not_tracked_in_session_history()
428 config()->set('filesystems.images', 'local_secure');
430 $page = $this->entities->page();
431 $result = $this->files->uploadGalleryImageToPage($this, $page);
432 $expectedPath = storage_path($result['path']);
433 $this->assertFileExists($expectedPath);
435 $this->get('/books');
436 $this->assertEquals(url('/books'), session()->previousUrl());
438 $resp = $this->get($result['path']);
440 $resp->assertHeader('Content-Type', 'image/png');
442 $this->assertEquals(url('/books'), session()->previousUrl());
444 if (file_exists($expectedPath)) {
445 unlink($expectedPath);
449 public function test_system_images_remain_public_with_local_secure_restricted()
451 config()->set('filesystems.images', 'local_secure_restricted');
453 $galleryFile = $this->files->uploadedImage('my-system-test-restricted-upload.png');
454 $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-restricted-upload.png');
456 $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []);
457 $upload->assertRedirect('/settings/customization');
459 $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: ' . $expectedPath);
461 if (file_exists($expectedPath)) {
462 unlink($expectedPath);
466 public function test_secure_restricted_images_inaccessible_without_relation_permission()
468 config()->set('filesystems.images', 'local_secure_restricted');
470 $galleryFile = $this->files->uploadedImage('my-secure-restricted-test-upload.png');
471 $page = $this->entities->page();
473 $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
474 $upload->assertStatus(200);
475 $expectedUrl = url('uploads/images/gallery/' . date('Y-m') . '/my-secure-restricted-test-upload.png');
476 $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-restricted-test-upload.png');
478 $this->get($expectedUrl)->assertOk();
480 $this->permissions->setEntityPermissions($page, [], []);
482 $resp = $this->get($expectedUrl);
483 $resp->assertNotFound();
485 if (file_exists($expectedPath)) {
486 unlink($expectedPath);
490 public function test_thumbnail_path_handled_by_secure_restricted_images()
492 config()->set('filesystems.images', 'local_secure_restricted');
494 $galleryFile = $this->files->uploadedImage('my-secure-restricted-thumb-test-test.png');
495 $page = $this->entities->page();
497 $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
498 $upload->assertStatus(200);
499 $expectedUrl = url('uploads/images/gallery/' . date('Y-m') . '/thumbs-150-150/my-secure-restricted-thumb-test-test.png');
500 $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-restricted-thumb-test-test.png');
502 $this->get($expectedUrl)->assertOk();
504 $this->permissions->setEntityPermissions($page, [], []);
506 $resp = $this->get($expectedUrl);
507 $resp->assertNotFound();
509 if (file_exists($expectedPath)) {
510 unlink($expectedPath);
514 public function test_secure_restricted_image_access_controlled_in_exports()
516 config()->set('filesystems.images', 'local_secure_restricted');
518 $galleryFile = $this->files->uploadedImage('my-secure-restricted-export-test.png');
520 $pageA = $this->entities->page();
521 $pageB = $this->entities->page();
522 $expectedPath = storage_path('uploads/images/gallery/' . date('Y-m') . '/my-secure-restricted-export-test.png');
524 $upload = $this->asEditor()->call('POST', '/images/gallery', ['uploaded_to' => $pageA->id], [], ['file' => $galleryFile], []);
527 $imageUrl = json_decode($upload->getContent(), true)['url'];
528 $pageB->html .= "<img src=\"{$imageUrl}\">";
531 $encodedImageContent = base64_encode(file_get_contents($expectedPath));
532 $export = $this->get($pageB->getUrl('/export/html'));
533 $this->assertStringContainsString($encodedImageContent, $export->getContent());
535 $this->permissions->setEntityPermissions($pageA, [], []);
537 $export = $this->get($pageB->getUrl('/export/html'));
538 $this->assertStringNotContainsString($encodedImageContent, $export->getContent());
540 if (file_exists($expectedPath)) {
541 unlink($expectedPath);
545 public function test_image_delete()
547 $page = $this->entities->page();
549 $imageName = 'first-image.png';
550 $relPath = $this->files->expectedImagePath('gallery', $imageName);
551 $this->files->deleteAtRelativePath($relPath);
553 $this->files->uploadGalleryImage($this, $imageName, $page->id);
554 $image = Image::first();
556 $delete = $this->delete('/images/' . $image->id);
557 $delete->assertStatus(200);
559 $this->assertDatabaseMissing('images', [
560 'url' => $this->baseUrl . $relPath,
564 $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
567 public function test_image_delete_does_not_delete_similar_images()
569 $page = $this->entities->page();
571 $imageName = 'first-image.png';
573 $relPath = $this->files->expectedImagePath('gallery', $imageName);
574 $this->files->deleteAtRelativePath($relPath);
576 $this->files->uploadGalleryImage($this, $imageName, $page->id);
577 $this->files->uploadGalleryImage($this, $imageName, $page->id);
578 $this->files->uploadGalleryImage($this, $imageName, $page->id);
580 $image = Image::first();
581 $folder = public_path(dirname($relPath));
582 $imageCount = count(glob($folder . '/*'));
584 $delete = $this->delete('/images/' . $image->id);
585 $delete->assertStatus(200);
587 $newCount = count(glob($folder . '/*'));
588 $this->assertEquals($imageCount - 1, $newCount, 'More files than expected have been deleted');
589 $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
592 public function test_image_manager_delete_button_only_shows_with_permission()
594 $page = $this->entities->page();
596 $imageName = 'first-image.png';
597 $relPath = $this->files->expectedImagePath('gallery', $imageName);
598 $this->files->deleteAtRelativePath($relPath);
599 $viewer = $this->users->viewer();
601 $this->files->uploadGalleryImage($this, $imageName, $page->id);
602 $image = Image::first();
604 $resp = $this->get("/images/edit/{$image->id}");
605 $this->withHtml($resp)->assertElementExists('button#image-manager-delete');
607 $resp = $this->actingAs($viewer)->get("/images/edit/{$image->id}");
608 $this->withHtml($resp)->assertElementNotExists('button#image-manager-delete');
610 $this->permissions->grantUserRolePermissions($viewer, ['image-delete-all']);
612 $resp = $this->actingAs($viewer)->get("/images/edit/{$image->id}");
613 $this->withHtml($resp)->assertElementExists('button#image-manager-delete');
615 $this->files->deleteAtRelativePath($relPath);
618 public function test_image_manager_regen_thumbnails()
621 $imageName = 'first-image.png';
622 $relPath = $this->files->expectedImagePath('gallery', $imageName);
623 $this->files->deleteAtRelativePath($relPath);
625 $this->files->uploadGalleryImage($this, $imageName, $this->entities->page()->id);
626 $image = Image::first();
628 $resp = $this->get("/images/edit/{$image->id}");
629 $this->withHtml($resp)->assertElementExists('button#image-manager-rebuild-thumbs');
631 $expectedThumbPath = dirname($relPath) . '/scaled-1680-/' . basename($relPath);
632 $this->files->deleteAtRelativePath($expectedThumbPath);
633 $this->assertFileDoesNotExist($this->files->relativeToFullPath($expectedThumbPath));
635 $resp = $this->put("/images/{$image->id}/rebuild-thumbnails");
638 $this->assertFileExists($this->files->relativeToFullPath($expectedThumbPath));
639 $this->files->deleteAtRelativePath($relPath);
642 public function test_gif_thumbnail_generation()
645 $originalFile = $this->files->testFilePath('animated.gif');
646 $originalFileSize = filesize($originalFile);
648 $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page(), 'animated.gif');
649 $relPath = $imgDetails['path'];
651 $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image found at path: ' . public_path($relPath));
652 $galleryThumb = $imgDetails['response']->thumbs->gallery;
653 $displayThumb = $imgDetails['response']->thumbs->display;
655 // Ensure display thumbnail is original image
656 $this->assertStringEndsWith($imgDetails['path'], $displayThumb);
657 $this->assertStringNotContainsString('thumbs', $displayThumb);
659 // Ensure gallery thumbnail is reduced image (single frame)
660 $galleryThumbRelPath = implode('/', array_slice(explode('/', $galleryThumb), 3));
661 $galleryThumbPath = public_path($galleryThumbRelPath);
662 $galleryFileSize = filesize($galleryThumbPath);
664 // Basic scan of GIF content to check frame count
665 $originalFrameCount = count(explode("\x00\x21\xF9", file_get_contents($originalFile)));
666 $galleryFrameCount = count(explode("\x00\x21\xF9", file_get_contents($galleryThumbPath)));
668 $this->files->deleteAtRelativePath($relPath);
669 $this->files->deleteAtRelativePath($galleryThumbRelPath);
671 $this->assertNotEquals($originalFileSize, $galleryFileSize);
672 $this->assertEquals(3, $originalFrameCount);
673 $this->assertEquals(1, $galleryFrameCount);
676 protected function getTestProfileImage()
678 $imageName = 'profile.png';
679 $relPath = $this->files->expectedImagePath('user', $imageName);
680 $this->files->deleteAtRelativePath($relPath);
682 return $this->files->uploadedImage($imageName);
685 public function test_user_image_upload()
687 $editor = $this->users->editor();
688 $admin = $this->users->admin();
689 $this->actingAs($admin);
691 $file = $this->getTestProfileImage();
692 $this->call('PUT', '/settings/users/' . $editor->id, [], [], ['profile_image' => $file], []);
694 $this->assertDatabaseHas('images', [
696 'uploaded_to' => $editor->id,
697 'created_by' => $admin->id,
701 public function test_user_images_deleted_on_user_deletion()
703 $editor = $this->users->editor();
704 $this->actingAs($editor);
706 $file = $this->getTestProfileImage();
707 $this->call('PUT', '/my-account/profile', [], [], ['profile_image' => $file], []);
709 $profileImages = Image::where('type', '=', 'user')->where('created_by', '=', $editor->id)->get();
710 $this->assertTrue($profileImages->count() === 1, 'Found profile images does not match upload count');
712 $imagePath = public_path($profileImages->first()->path);
713 $this->assertTrue(file_exists($imagePath));
715 $userDelete = $this->asAdmin()->delete($editor->getEditUrl());
716 $userDelete->assertStatus(302);
718 $this->assertDatabaseMissing('images', [
720 'created_by' => $editor->id,
722 $this->assertDatabaseMissing('images', [
724 'uploaded_to' => $editor->id,
727 $this->assertFalse(file_exists($imagePath));
730 public function test_deleted_unused_images()
732 $page = $this->entities->page();
733 $admin = $this->users->admin();
734 $this->actingAs($admin);
736 $imageName = 'unused-image.png';
737 $relPath = $this->files->expectedImagePath('gallery', $imageName);
738 $this->files->deleteAtRelativePath($relPath);
740 $upload = $this->files->uploadGalleryImage($this, $imageName, $page->id);
741 $upload->assertStatus(200);
742 $image = Image::where('type', '=', 'gallery')->first();
744 $pageRepo = app(PageRepo::class);
745 $pageRepo->update($page, [
746 'name' => $page->name,
747 'html' => $page->html . "<img src=\"{$image->url}\">",
751 // Ensure no images are reported as deletable
752 $imageService = app(ImageService::class);
753 $toDelete = $imageService->deleteUnusedImages(true, true);
754 $this->assertCount(0, $toDelete);
756 // Save a revision of our page without the image;
757 $pageRepo->update($page, [
758 'name' => $page->name,
759 'html' => '<p>Hello</p>',
763 // Ensure revision images are picked up okay
764 $imageService = app(ImageService::class);
765 $toDelete = $imageService->deleteUnusedImages(true, true);
766 $this->assertCount(0, $toDelete);
767 $toDelete = $imageService->deleteUnusedImages(false, true);
768 $this->assertCount(1, $toDelete);
770 // Check image is found when revisions are destroyed
771 $page->revisions()->delete();
772 $toDelete = $imageService->deleteUnusedImages(true, true);
773 $this->assertCount(1, $toDelete);
775 // Check the image is deleted
776 $absPath = public_path($relPath);
777 $this->assertTrue(file_exists($absPath), "Existing uploaded file at path {$absPath} exists");
778 $toDelete = $imageService->deleteUnusedImages(true, false);
779 $this->assertCount(1, $toDelete);
780 $this->assertFalse(file_exists($absPath));
782 $this->files->deleteAtRelativePath($relPath);