From 7b6c88f17c595f3b0d88fe383827794b83dba3e7 Mon Sep 17 00:00:00 2001 From: Dan Brown <ssddanbrown@googlemail.com> 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 @@ <?php +use BookStack\Ownable; + if (!function_exists('versioned_asset')) { /** * Get the path to a versioned file. @@ -34,18 +36,18 @@ if (!function_exists('versioned_asset')) { * If an ownable element is passed in the jointPermissions are checked against * that particular item. * @param $permission - * @param \BookStack\Ownable $ownable + * @param Ownable $ownable * @return mixed */ -function userCan($permission, \BookStack\Ownable $ownable = null) +function userCan($permission, Ownable $ownable = null) { if ($ownable === null) { return auth()->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 @@ +<?php + +class ImageTest extends TestCase +{ + + /** + * Get a test image that can be uploaded + * @param $fileName + * @return \Illuminate\Http\UploadedFile + */ + protected function getTestImage($fileName) + { + return new \Illuminate\Http\UploadedFile(base_path('tests/test-image.jpg'), $fileName, 'image/jpeg', 5238); + } + + /** + * Get the path for a test image. + * @param $type + * @param $fileName + * @return string + */ + protected function getTestImagePath($type, $fileName) + { + return '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/' . $fileName; + } + + /** + * Uploads an image with the given name. + * @param $name + * @param int $uploadedTo + * @return string + */ + protected function uploadImage($name, $uploadedTo = 0) + { + $file = $this->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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 literal 0 HcmV?d00001