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