From 7b6c88f17c595f3b0d88fe383827794b83dba3e7 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 1 Jul 2016 20:11:49 +0100 Subject: [PATCH] Fixed error on image deletion Also Added tests to cover image upload and deletion. Fixes #136. --- app/Http/Controllers/ImageController.php | 6 +- app/Providers/AppServiceProvider.php | 7 +- app/Services/PermissionService.php | 9 ++- app/helpers.php | 10 ++- tests/ImageTest.php | 95 +++++++++++++++++++++++ tests/TestCase.php | 10 ++- tests/test-image.jpg | Bin 0 -> 5238 bytes 7 files changed, 124 insertions(+), 13 deletions(-) create mode 100644 tests/ImageTest.php create mode 100644 tests/test-image.jpg diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index 2e5d5f303..621c23e85 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -51,9 +51,9 @@ class ImageController extends Controller $this->validate($request, [ 'term' => 'required|string' ]); - + $searchTerm = $request->get('term'); - $imgData = $this->imageRepo->searchPaginatedByType($type, $page,24, $searchTerm); + $imgData = $this->imageRepo->searchPaginatedByType($type, $page, 24, $searchTerm); return response()->json($imgData); } @@ -99,7 +99,7 @@ class ImageController extends Controller { $this->checkPermission('image-create-all'); $this->validate($request, [ - 'file' => 'image|mimes:jpeg,gif,png' + 'file' => 'is_image' ]); $imageUpload = $request->file('file'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 8bcbcbdad..f214c9141 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -15,7 +15,12 @@ class AppServiceProvider extends ServiceProvider */ public function boot() { - // + // Custom validation methods + \Validator::extend('is_image', function($attribute, $value, $parameters, $validator) { + $imageMimes = ['image/png', 'image/bmp', 'image/gif', 'image/jpeg', 'image/jpg', 'image/tiff', 'image/webp']; + return in_array($value->getMimeType(), $imageMimes); + }); + } /** diff --git a/app/Services/PermissionService.php b/app/Services/PermissionService.php index 218cb30a5..0fffe60f2 100644 --- a/app/Services/PermissionService.php +++ b/app/Services/PermissionService.php @@ -4,6 +4,7 @@ use BookStack\Book; use BookStack\Chapter; use BookStack\Entity; use BookStack\JointPermission; +use BookStack\Ownable; use BookStack\Page; use BookStack\Role; use BookStack\User; @@ -307,16 +308,16 @@ class PermissionService /** * Checks if an entity has a restriction set upon it. - * @param Entity $entity + * @param Ownable $ownable * @param $permission * @return bool */ - public function checkEntityUserAccess(Entity $entity, $permission) + public function checkOwnableUserAccess(Ownable $ownable, $permission) { if ($this->isAdmin) return true; $explodedPermission = explode('-', $permission); - $baseQuery = $entity->where('id', '=', $entity->id); + $baseQuery = $ownable->where('id', '=', $ownable->id); $action = end($explodedPermission); $this->currentAction = $action; @@ -327,7 +328,7 @@ class PermissionService $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all'); $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own'); $this->currentAction = 'view'; - $isOwner = $this->currentUser && $this->currentUser->id === $entity->created_by; + $isOwner = $this->currentUser && $this->currentUser->id === $ownable->created_by; return ($allPermission || ($isOwner && $ownPermission)); } diff --git a/app/helpers.php b/app/helpers.php index b8f61d94e..42e4c1894 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,5 +1,7 @@ user() && auth()->user()->can($permission); } // Check permission on ownable item - $permissionService = app('BookStack\Services\PermissionService'); - return $permissionService->checkEntityUserAccess($ownable, $permission); + $permissionService = app(\BookStack\Services\PermissionService::class); + return $permissionService->checkOwnableUserAccess($ownable, $permission); } /** diff --git a/tests/ImageTest.php b/tests/ImageTest.php new file mode 100644 index 000000000..806a36acc --- /dev/null +++ b/tests/ImageTest.php @@ -0,0 +1,95 @@ +getTestImage($name); + $this->call('POST', '/images/gallery/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []); + return $this->getTestImagePath('gallery', $name); + } + + /** + * Delete an uploaded image. + * @param $relPath + */ + protected function deleteImage($relPath) + { + unlink(public_path($relPath)); + } + + + public function test_image_upload() + { + $page = \BookStack\Page::first(); + $this->asAdmin(); + $admin = $this->getAdmin(); + $imageName = 'first-image.jpg'; + + $relPath = $this->uploadImage($imageName, $page->id); + $this->assertResponseOk(); + + $this->assertTrue(file_exists(public_path($relPath)), 'Uploaded image exists'); + + $this->seeInDatabase('images', [ + 'url' => $relPath, + 'type' => 'gallery', + 'uploaded_to' => $page->id, + 'path' => $relPath, + 'created_by' => $admin->id, + 'updated_by' => $admin->id, + 'name' => $imageName + ]); + + $this->deleteImage($relPath); + } + + public function test_image_delete() + { + $page = \BookStack\Page::first(); + $this->asAdmin(); + $imageName = 'first-image.jpg'; + + $relPath = $this->uploadImage($imageName, $page->id); + $image = \BookStack\Image::first(); + + $this->call('DELETE', '/images/' . $image->id); + $this->assertResponseOk(); + + $this->dontSeeInDatabase('images', [ + 'url' => $relPath, + 'type' => 'gallery' + ]); + + $this->assertFalse(file_exists(public_path($relPath)), 'Uploaded image has been deleted'); + } + +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index 4c2893f4e..6a8c2d732 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -39,11 +39,19 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase */ public function asAdmin() { + return $this->actingAs($this->getAdmin()); + } + + /** + * Get the current admin user. + * @return mixed + */ + public function getAdmin() { if($this->admin === null) { $adminRole = \BookStack\Role::getRole('admin'); $this->admin = $adminRole->users->first(); } - return $this->actingAs($this->admin); + return $this->admin; } /** diff --git a/tests/test-image.jpg b/tests/test-image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb8da91011c2d691eb038afe579d60a8451d28cf GIT binary patch literal 5238 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