Added view, deletion and permissions for files

This commit is contained in:
Dan Brown 2016-10-10 20:30:27 +01:00
parent 673c74ddfc
commit ac0b29fb6d
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
9 changed files with 152 additions and 20 deletions

View File

@ -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;
}
}

View File

@ -1,10 +1,7 @@
<?php
namespace BookStack\Http\Controllers;
<?php namespace BookStack\Http\Controllers;
use BookStack\Exceptions\FileUploadException;
use BookStack\File;
use BookStack\Page;
use BookStack\Repos\PageRepo;
use BookStack\Services\FileService;
use Illuminate\Http\Request;
@ -37,16 +34,18 @@ class FileController extends Controller
*/
public function upload(Request $request)
{
// TODO - Add file upload permission check
// TODO - ensure user has permission to edit relevant page.
// TODO - ensure uploads are deleted on page delete.
$this->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']);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View File

@ -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);
});
};
}]);
};

View File

@ -46,7 +46,7 @@
<tr ng-repeat="file in files track by $index">
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
<td ng-bind="file.name"></td>
<td width="10" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
<td width="10" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
</tr>
</tbody>
</table>

View File

@ -1,6 +1,15 @@
<div class="book-tree" ng-non-bindable>
@if ($page->files->count() > 0)
<h6 class="text-muted">Attachments</h6>
@foreach($page->files as $file)
<div class="attachment">
<a href="{{ $file->getUrl() }}"><i class="zmdi zmdi-file"></i> {{ $file->name }}</a>
</div>
@endforeach
@endif
@if (isset($pageNav) && $pageNav)
<h6 class="text-muted">Page Navigation</h6>
<div class="sidebar-page-nav menu">
@ -10,8 +19,6 @@
</li>
@endforeach
</div>
@endif
<h6 class="text-muted">Book Navigation</h6>

View File

@ -106,6 +106,19 @@
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
</td>
</tr>
<tr>
<td>Attached <br>Files</td>
<td>@include('settings/roles/checkbox', ['permission' => 'file-create-all'])</td>
<td style="line-height:1.2;"><small class="faded">Controlled by the asset they are uploaded to</small></td>
<td>
<label>@include('settings/roles/checkbox', ['permission' => 'file-update-own']) Own</label>
<label>@include('settings/roles/checkbox', ['permission' => 'file-update-all']) All</label>
</td>
<td>
<label>@include('settings/roles/checkbox', ['permission' => 'file-delete-own']) Own</label>
<label>@include('settings/roles/checkbox', ['permission' => 'file-delete-all']) All</label>
</td>
</tr>
</table>
</div>
</div>

View File

@ -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');