+ public function test_image_file_update()
+ {
+ $page = $this->entities->page();
+ $this->asEditor();
+
+ $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
+ $relPath = $imgDetails['path'];
+
+ $newUpload = $this->files->uploadedImage('updated-image.png', 'compressed.png');
+ $this->assertFileEquals($this->files->testFilePath('test-image.png'), public_path($relPath));
+
+ $imageId = $imgDetails['response']->id;
+ $image = Image::findOrFail($imageId);
+ $image->updated_at = now()->subMonth();
+ $image->save();
+
+ $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
+ ->assertOk();
+
+ $this->assertFileEquals($this->files->testFilePath('compressed.png'), public_path($relPath));
+
+ $image->refresh();
+ $this->assertTrue($image->updated_at->gt(now()->subMinute()));
+
+ $this->files->deleteAtRelativePath($relPath);
+ }
+
+ public function test_image_file_update_allows_case_differences()
+ {
+ $page = $this->entities->page();
+ $this->asEditor();
+
+ $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
+ $relPath = $imgDetails['path'];
+
+ $newUpload = $this->files->uploadedImage('updated-image.PNG', 'compressed.png');
+ $this->assertFileEquals($this->files->testFilePath('test-image.png'), public_path($relPath));
+
+ $imageId = $imgDetails['response']->id;
+ $image = Image::findOrFail($imageId);
+ $image->updated_at = now()->subMonth();
+ $image->save();
+
+ $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
+ ->assertOk();
+
+ $this->assertFileEquals($this->files->testFilePath('compressed.png'), public_path($relPath));
+
+ $image->refresh();
+ $this->assertTrue($image->updated_at->gt(now()->subMinute()));
+
+ $this->files->deleteAtRelativePath($relPath);
+ }
+
+ public function test_image_file_update_does_not_allow_change_in_image_extension()
+ {
+ $page = $this->entities->page();
+ $this->asEditor();
+
+ $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
+ $relPath = $imgDetails['path'];
+ $newUpload = $this->files->uploadedImage('updated-image.jpg', 'compressed.png');
+
+ $imageId = $imgDetails['response']->id;
+ $this->call('PUT', "/images/{$imageId}/file", [], [], ['file' => $newUpload])
+ ->assertJson([
+ "message" => "Image file replacements must be of the same type",
+ "status" => "error",
+ ]);
+
+ $this->files->deleteAtRelativePath($relPath);
+ }
+
+ public function test_gallery_get_list_format()
+ {
+ $this->asEditor();
+
+ $imgDetails = $this->files->uploadGalleryImageToPage($this, $this->entities->page());
+ $image = Image::query()->first();
+
+ $pageId = $imgDetails['page']->id;
+ $firstPageRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}");
+ $firstPageRequest->assertSuccessful();
+ $this->withHtml($firstPageRequest)->assertElementExists('div');
+ $firstPageRequest->assertSuccessful()->assertSeeText($image->name);
+
+ $secondPageRequest = $this->get("/images/gallery?page=2&uploaded_to={$pageId}");
+ $secondPageRequest->assertSuccessful();
+ $this->withHtml($secondPageRequest)->assertElementNotExists('div');
+
+ $namePartial = substr($imgDetails['name'], 0, 3);
+ $searchHitRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
+ $searchHitRequest->assertSuccessful()->assertSee($imgDetails['name']);
+
+ $namePartial = Str::random(16);
+ $searchFailRequest = $this->get("/images/gallery?page=1&uploaded_to={$pageId}&search={$namePartial}");
+ $searchFailRequest->assertSuccessful()->assertDontSee($imgDetails['name']);
+ $searchFailRequest->assertSuccessful();
+ $this->withHtml($searchFailRequest)->assertElementNotExists('div');
+ }
+
+ public function test_image_gallery_lists_for_draft_page()
+ {
+ $this->actingAs($this->users->editor());
+ $draft = $this->entities->newDraftPage();
+ $this->files->uploadGalleryImageToPage($this, $draft);
+ $image = Image::query()->where('uploaded_to', '=', $draft->id)->firstOrFail();
+
+ $resp = $this->get("/images/gallery?page=1&uploaded_to={$draft->id}");
+ $resp->assertSee($image->getThumb(150, 150));
+ }
+
+ public function test_image_usage()
+ {
+ $page = $this->entities->page();
+ $editor = $this->users->editor();
+ $this->actingAs($editor);
+
+ $imgDetails = $this->files->uploadGalleryImageToPage($this, $page);
+
+ $image = Image::query()->first();
+ $page->html = '<img src="' . $image->url . '">';
+ $page->save();
+
+ $usage = $this->get('/images/edit/' . $image->id . '?delete=true');
+ $usage->assertSuccessful();
+ $usage->assertSeeText($page->name);
+ $usage->assertSee($page->getUrl());
+
+ $this->files->deleteAtRelativePath($imgDetails['path']);
+ }
+
+ public function test_php_files_cannot_be_uploaded()
+ {
+ $page = $this->entities->page();
+ $admin = $this->users->admin();
+ $this->actingAs($admin);
+
+ $fileName = 'bad.php';
+ $relPath = $this->files->expectedImagePath('gallery', $fileName);
+ $this->files->deleteAtRelativePath($relPath);
+
+ $file = $this->files->imageFromBase64File('bad-php.base64', $fileName);
+ $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
+ $upload->assertStatus(500);
+ $this->assertStringContainsString('The file must have a valid & supported image extension', $upload->json('message'));
+
+ $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
+
+ $this->assertDatabaseMissing('images', [
+ 'type' => 'gallery',
+ 'name' => $fileName,
+ ]);
+ }
+
+ public function test_php_like_files_cannot_be_uploaded()
+ {
+ $page = $this->entities->page();
+ $admin = $this->users->admin();
+ $this->actingAs($admin);
+
+ $fileName = 'bad.phtml';
+ $relPath = $this->files->expectedImagePath('gallery', $fileName);
+ $this->files->deleteAtRelativePath($relPath);
+
+ $file = $this->files->imageFromBase64File('bad-phtml.base64', $fileName);
+ $upload = $this->withHeader('Content-Type', 'image/jpeg')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
+ $upload->assertStatus(500);
+ $this->assertStringContainsString('The file must have a valid & supported image extension', $upload->json('message'));
+
+ $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded php file was uploaded but should have been stopped');
+ }
+
+ public function test_files_with_double_extensions_will_get_sanitized()
+ {
+ $page = $this->entities->page();
+ $admin = $this->users->admin();
+ $this->actingAs($admin);
+
+ $fileName = 'bad.phtml.png';
+ $relPath = $this->files->expectedImagePath('gallery', $fileName);
+ $expectedRelPath = dirname($relPath) . '/bad-phtml.png';
+ $this->files->deleteAtRelativePath($expectedRelPath);
+
+ $file = $this->files->imageFromBase64File('bad-phtml-png.base64', $fileName);
+ $upload = $this->withHeader('Content-Type', 'image/png')->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $file], []);
+ $upload->assertStatus(200);
+
+ $lastImage = Image::query()->latest('id')->first();
+
+ $this->assertEquals('bad.phtml.png', $lastImage->name);
+ $this->assertEquals('bad-phtml.png', basename($lastImage->path));
+ $this->assertFileDoesNotExist(public_path($relPath), 'Uploaded image file name was not stripped of dots');
+ $this->assertFileExists(public_path($expectedRelPath));
+
+ $this->files->deleteAtRelativePath($lastImage->path);
+ }
+
+ public function test_url_entities_removed_from_filenames()
+ {
+ $this->asEditor();
+ $badNames = [
+ 'bad-char-#-image.png',
+ 'bad-char-?-image.png',
+ '?#.png',
+ '?.png',
+ '#.png',
+ ];
+ foreach ($badNames as $name) {
+ $galleryFile = $this->files->uploadedImage($name);
+ $page = $this->entities->page();
+ $badPath = $this->files->expectedImagePath('gallery', $name);
+ $this->files->deleteAtRelativePath($badPath);
+
+ $upload = $this->call('POST', '/images/gallery', ['uploaded_to' => $page->id], [], ['file' => $galleryFile], []);
+ $upload->assertStatus(200);
+
+ $lastImage = Image::query()->latest('id')->first();
+ $newFileName = explode('.', basename($lastImage->path))[0];
+
+ $this->assertEquals($lastImage->name, $name);
+ $this->assertFalse(strpos($lastImage->path, $name), 'Path contains original image name');
+ $this->assertFalse(file_exists(public_path($badPath)), 'Uploaded image file name was not stripped of url entities');
+
+ $this->assertTrue(strlen($newFileName) > 0, 'File name was reduced to nothing');
+
+ $this->files->deleteAtRelativePath($lastImage->path);
+ }
+ }
+