diff --git a/app/File.php b/app/File.php index ebfa0a296..055f217bd 100644 --- a/app/File.php +++ b/app/File.php @@ -7,12 +7,20 @@ class File extends Ownable /** * Get the page this file was uploaded to. - * @return mixed + * @return Page */ public function page() { return $this->belongsTo(Page::class, 'uploaded_to'); } + /** + * Get the url of this file. + * @return string + */ + public function getUrl() + { + return '/files/' . $this->id; + } } diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index b97112c1c..e09fb98c6 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -1,10 +1,7 @@ -validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id' ]); - $uploadedFile = $request->file('file'); $pageId = $request->get('uploaded_to'); + $page = $this->pageRepo->getById($pageId); + + $this->checkPermission('file-create-all'); + $this->checkOwnablePermission('page-update', $page); + + $uploadedFile = $request->file('file'); try { $file = $this->fileService->saveNewUpload($uploadedFile, $pageId); @@ -62,10 +61,10 @@ class FileController extends Controller * @param $pageId * @return mixed */ - public function getFilesForPage($pageId) + public function listForPage($pageId) { - // TODO - check view permission on page? $page = $this->pageRepo->getById($pageId); + $this->checkOwnablePermission('page-view', $page); return response()->json($page->files); } @@ -75,17 +74,47 @@ class FileController extends Controller * @param Request $request * @return mixed */ - public function sortFilesForPage($pageId, Request $request) + public function sortForPage($pageId, Request $request) { $this->validate($request, [ 'files' => 'required|array', 'files.*.id' => 'required|integer', ]); $page = $this->pageRepo->getById($pageId); + $this->checkOwnablePermission('page-update', $page); + $files = $request->get('files'); $this->fileService->updateFileOrderWithinPage($files, $pageId); return response()->json(['message' => 'File order updated']); } + /** + * Get a file from storage. + * @param $fileId + */ + public function get($fileId) + { + $file = $this->file->findOrFail($fileId); + $page = $this->pageRepo->getById($file->uploaded_to); + $this->checkOwnablePermission('page-view', $page); + $fileContents = $this->fileService->getFile($file); + return response($fileContents, 200, [ + 'Content-Type' => 'application/octet-stream', + 'Content-Disposition' => 'attachment; filename="'. $file->name .'"' + ]); + } + + /** + * Delete a specific file in the system. + * @param $fileId + * @return mixed + */ + public function delete($fileId) + { + $file = $this->file->findOrFail($fileId); + $this->checkOwnablePermission($file, 'file-delete'); + $this->fileService->deleteFile($file); + return response()->json(['message' => 'File deleted']); + } } diff --git a/app/Services/FileService.php b/app/Services/FileService.php index 689fa0600..7429f0e64 100644 --- a/app/Services/FileService.php +++ b/app/Services/FileService.php @@ -4,12 +4,24 @@ use BookStack\Exceptions\FileUploadException; use BookStack\File; use Exception; +use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Support\Collection; use Symfony\Component\HttpFoundation\File\UploadedFile; class FileService extends UploadService { + /** + * Get a file from storage. + * @param File $file + * @return string + */ + public function getFile(File $file) + { + $filePath = $this->getStorageBasePath() . $file->path; + return $this->getStorage()->get($filePath); + } + /** * Store a new file upon user upload. * @param UploadedFile $uploadedFile @@ -76,4 +88,22 @@ class FileService extends UploadService } } + /** + * Delete a file and any empty folders the deletion leaves. + * @param File $file + */ + public function deleteFile(File $file) + { + $storedFilePath = $this->getStorageBasePath() . $file->path; + $storage = $this->getStorage(); + $dirPath = dirname($storedFilePath); + + $storage->delete($storedFilePath); + if (count($storage->allFiles($dirPath)) === 0) { + $storage->deleteDirectory($dirPath); + } + + $file->delete(); + } + } \ No newline at end of file diff --git a/database/migrations/2016_10_09_142037_create_files_table.php b/database/migrations/2016_10_09_142037_create_files_table.php index 4eaa86aeb..57ddd1202 100644 --- a/database/migrations/2016_10_09_142037_create_files_table.php +++ b/database/migrations/2016_10_09_142037_create_files_table.php @@ -28,6 +28,26 @@ class CreateFilesTable extends Migration $table->index('uploaded_to'); $table->timestamps(); }); + + // Get roles with permissions we need to change + $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id; + + // Create & attach new entity permissions + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; + $entity = 'File'; + foreach ($ops as $op) { + $permissionId = DB::table('role_permissions')->insertGetId([ + 'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)), + 'display_name' => $op . ' ' . $entity . 's', + 'created_at' => \Carbon\Carbon::now()->toDateTimeString(), + 'updated_at' => \Carbon\Carbon::now()->toDateTimeString() + ]); + DB::table('permission_role')->insert([ + 'role_id' => $adminRoleId, + 'permission_id' => $permissionId + ]); + } + } /** @@ -38,5 +58,17 @@ class CreateFilesTable extends Migration public function down() { Schema::dropIfExists('files'); + + // Get roles with permissions we need to change + $adminRoleId = DB::table('roles')->where('system_name', '=', 'admin')->first()->id; + + // Create & attach new entity permissions + $ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own']; + $entity = 'File'; + foreach ($ops as $op) { + $permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); + $permission = DB::table('role_permissions')->where('name', '=', $permName)->get(); + DB::table('permission_role')->where('permission_id', '=', $permission->id)->delete(); + } } } diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index 52477a4ad..b5353e7d9 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -575,9 +575,9 @@ module.exports = function (ngApp, events) { */ function getFiles() { let url = window.baseUrl(`/files/get/page/${pageId}`) - $http.get(url).then(responseData => { - $scope.files = responseData.data; - currentOrder = responseData.data.map(file => {return file.id}).join(':'); + $http.get(url).then(resp => { + $scope.files = resp.data; + currentOrder = resp.data.map(file => {return file.id}).join(':'); }); } getFiles(); @@ -595,6 +595,17 @@ module.exports = function (ngApp, events) { events.emit('success', 'File uploaded'); }; + /** + * Delete a file from the server and, on success, the local listing. + * @param file + */ + $scope.deleteFile = function(file) { + $http.delete(`/files/${file.id}`).then(resp => { + events.emit('success', resp.data.message); + $scope.files.splice($scope.files.indexOf(file), 1); + }); + }; + }]); }; diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php index 3a344b651..9ebf223f0 100644 --- a/resources/views/pages/form-toolbox.blade.php +++ b/resources/views/pages/form-toolbox.blade.php @@ -46,7 +46,7 @@ - + diff --git a/resources/views/pages/sidebar-tree-list.blade.php b/resources/views/pages/sidebar-tree-list.blade.php index 5fcec8731..fa9cc84aa 100644 --- a/resources/views/pages/sidebar-tree-list.blade.php +++ b/resources/views/pages/sidebar-tree-list.blade.php @@ -1,6 +1,15 @@
+ @if ($page->files->count() > 0) +
Attachments
+ @foreach($page->files as $file) +
+ {{ $file->name }} +
+ @endforeach + @endif + @if (isset($pageNav) && $pageNav)
Page Navigation
- - @endif
Book Navigation
diff --git a/resources/views/settings/roles/form.blade.php b/resources/views/settings/roles/form.blade.php index 5e653f8de..4f1987c03 100644 --- a/resources/views/settings/roles/form.blade.php +++ b/resources/views/settings/roles/form.blade.php @@ -106,6 +106,19 @@ + + Attached
Files + @include('settings/roles/checkbox', ['permission' => 'file-create-all']) + Controlled by the asset they are uploaded to + + + + + + + + +
diff --git a/routes/web.php b/routes/web.php index 7e05af483..514f82f99 100644 --- a/routes/web.php +++ b/routes/web.php @@ -88,9 +88,11 @@ Route::group(['middleware' => 'auth'], function () { }); // File routes + Route::get('/files/{id}', 'FileController@get'); Route::post('/files/upload', 'FileController@upload'); - Route::get('/files/get/page/{pageId}', 'FileController@getFilesForPage'); - Route::put('/files/sort/page/{pageId}', 'FileController@sortFilesForPage'); + Route::get('/files/get/page/{pageId}', 'FileController@listForPage'); + Route::put('/files/sort/page/{pageId}', 'FileController@sortForPage'); + Route::delete('/files/{id}', 'FileController@delete'); // AJAX routes Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');