1 <?php namespace Tests\Uploads;
3 use BookStack\Entities\Repos\PageRepo;
4 use BookStack\Uploads\Image;
5 use BookStack\Entities\Page;
6 use BookStack\Uploads\ImageService;
9 class ImageTest extends TestCase
14 public function test_image_upload()
16 $page = Page::first();
17 $admin = $this->getAdmin();
18 $this->actingAs($admin);
20 $imageName = 'first-image.png';
21 $relPath = $this->getTestImagePath('gallery', $imageName);
22 $this->deleteImage($relPath);
24 $upload = $this->uploadImage($imageName, $page->id);
25 $upload->assertStatus(200);
27 $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image not found at path: '. public_path($relPath));
29 $this->deleteImage($relPath);
31 $this->assertDatabaseHas('images', [
32 'url' => $this->baseUrl . $relPath,
34 'uploaded_to' => $page->id,
36 'created_by' => $admin->id,
37 'updated_by' => $admin->id,
42 public function test_secure_images_uploads_to_correct_place()
44 config()->set('filesystems.default', 'local_secure');
46 $galleryFile = $this->getTestImage('my-secure-test-upload');
47 $page = Page::first();
48 $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
50 $upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
51 $upload->assertStatus(200);
53 $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: '. $expectedPath);
55 if (file_exists($expectedPath)) {
56 unlink($expectedPath);
60 public function test_secure_images_included_in_exports()
62 config()->set('filesystems.default', 'local_secure');
64 $galleryFile = $this->getTestImage('my-secure-test-upload');
65 $page = Page::first();
66 $expectedPath = storage_path('uploads/images/gallery/' . Date('Y-m-M') . '/my-secure-test-upload');
68 $upload = $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
69 $imageUrl = json_decode($upload->getContent(), true)['url'];
70 $page->html .= "<img src=\"{$imageUrl}\">";
72 $upload->assertStatus(200);
74 $encodedImageContent = base64_encode(file_get_contents($expectedPath));
75 $export = $this->get($page->getUrl('/export/html'));
76 $this->assertTrue(str_contains($export->getContent(), $encodedImageContent), 'Uploaded image in export content');
78 if (file_exists($expectedPath)) {
79 unlink($expectedPath);
83 public function test_system_images_remain_public()
85 config()->set('filesystems.default', 'local_secure');
87 $galleryFile = $this->getTestImage('my-system-test-upload');
88 $page = Page::first();
89 $expectedPath = public_path('uploads/images/system/' . Date('Y-m-M') . '/my-system-test-upload');
91 $upload = $this->call('POST', '/images/system/upload', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
92 $upload->assertStatus(200);
94 $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: '. $expectedPath);
96 if (file_exists($expectedPath)) {
97 unlink($expectedPath);
101 public function test_image_delete()
103 $page = Page::first();
105 $imageName = 'first-image.png';
107 $this->uploadImage($imageName, $page->id);
108 $image = Image::first();
109 $relPath = $this->getTestImagePath('gallery', $imageName);
111 $delete = $this->delete( '/images/' . $image->id);
112 $delete->assertStatus(200);
114 $this->assertDatabaseMissing('images', [
115 'url' => $this->baseUrl . $relPath,
119 $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has not been deleted as expected');
122 public function testBase64Get()
124 $page = Page::first();
126 $imageName = 'first-image.png';
128 $this->uploadImage($imageName, $page->id);
129 $image = Image::first();
131 $imageGet = $this->getJson("/images/base64/{$image->id}");
132 $imageGet->assertJson([
133 'content' => 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEcDCo5iYNs+gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12O0jN/KgASYGFABqXwAZtoBV6Sl3hIAAAAASUVORK5CYII='
137 public function test_drawing_base64_upload()
139 $page = Page::first();
140 $editor = $this->getEditor();
141 $this->actingAs($editor);
143 $upload = $this->postJson('images/drawing/upload', [
144 'uploaded_to' => $page->id,
145 'image' => 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gEcDCo5iYNs+gAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAFElEQVQI12O0jN/KgASYGFABqXwAZtoBV6Sl3hIAAAAASUVORK5CYII='
148 $upload->assertStatus(200);
149 $upload->assertJson([
151 'uploaded_to' => $page->id,
152 'created_by' => $editor->id,
153 'updated_by' => $editor->id,
156 $image = Image::where('type', '=', 'drawio')->first();
157 $this->assertTrue(file_exists(public_path($image->path)), 'Uploaded image not found at path: '. public_path($image->path));
159 $testImageData = file_get_contents($this->getTestImageFilePath());
160 $uploadedImageData = file_get_contents(public_path($image->path));
161 $this->assertTrue($testImageData === $uploadedImageData, "Uploaded image file data does not match our test image as expected");
164 public function test_user_images_deleted_on_user_deletion()
166 $editor = $this->getEditor();
167 $this->actingAs($editor);
169 $imageName = 'profile.png';
170 $relPath = $this->getTestImagePath('gallery', $imageName);
171 $this->deleteImage($relPath);
173 $file = $this->getTestImage($imageName);
174 $this->call('POST', '/images/user/upload', [], [], ['file' => $file], []);
175 $this->call('POST', '/images/user/upload', [], [], ['file' => $file], []);
177 $profileImages = Image::where('type', '=', 'user')->where('created_by', '=', $editor->id)->get();
178 $this->assertTrue($profileImages->count() === 2, "Found profile images does not match upload count");
180 $userDelete = $this->asAdmin()->delete("/settings/users/{$editor->id}");
181 $userDelete->assertStatus(302);
182 $this->assertDatabaseMissing('images', [
184 'created_by' => $editor->id
188 public function test_deleted_unused_images()
190 $page = Page::first();
191 $admin = $this->getAdmin();
192 $this->actingAs($admin);
194 $imageName = 'unused-image.png';
195 $relPath = $this->getTestImagePath('gallery', $imageName);
196 $this->deleteImage($relPath);
198 $upload = $this->uploadImage($imageName, $page->id);
199 $upload->assertStatus(200);
200 $image = Image::where('type', '=', 'gallery')->first();
202 $pageRepo = app(PageRepo::class);
203 $pageRepo->updatePage($page, $page->book_id, [
204 'name' => $page->name,
205 'html' => $page->html . "<img src=\"{$image->url}\">",
209 // Ensure no images are reported as deletable
210 $imageService = app(ImageService::class);
211 $toDelete = $imageService->deleteUnusedImages(true, true);
212 $this->assertCount(0, $toDelete);
214 // Save a revision of our page without the image;
215 $pageRepo->updatePage($page, $page->book_id, [
216 'name' => $page->name,
217 'html' => "<p>Hello</p>",
221 // Ensure revision images are picked up okay
222 $imageService = app(ImageService::class);
223 $toDelete = $imageService->deleteUnusedImages(true, true);
224 $this->assertCount(0, $toDelete);
225 $toDelete = $imageService->deleteUnusedImages(false, true);
226 $this->assertCount(1, $toDelete);
228 // Check image is found when revisions are destroyed
229 $page->revisions()->delete();
230 $toDelete = $imageService->deleteUnusedImages(true, true);
231 $this->assertCount(1, $toDelete);
233 // Check the image is deleted
234 $absPath = public_path($relPath);
235 $this->assertTrue(file_exists($absPath), "Existing uploaded file at path {$absPath} exists");
236 $toDelete = $imageService->deleteUnusedImages(true, false);
237 $this->assertCount(1, $toDelete);
238 $this->assertFalse(file_exists($absPath));
240 $this->deleteImage($relPath);