mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added view, deletion and permissions for files
This commit is contained in:
parent
673c74ddfc
commit
ac0b29fb6d
10
app/File.php
10
app/File.php
@ -7,12 +7,20 @@ class File extends Ownable
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the page this file was uploaded to.
|
* Get the page this file was uploaded to.
|
||||||
* @return mixed
|
* @return Page
|
||||||
*/
|
*/
|
||||||
public function page()
|
public function page()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Page::class, 'uploaded_to');
|
return $this->belongsTo(Page::class, 'uploaded_to');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the url of this file.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUrl()
|
||||||
|
{
|
||||||
|
return '/files/' . $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
<?php
|
<?php namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
namespace BookStack\Http\Controllers;
|
|
||||||
|
|
||||||
use BookStack\Exceptions\FileUploadException;
|
use BookStack\Exceptions\FileUploadException;
|
||||||
use BookStack\File;
|
use BookStack\File;
|
||||||
use BookStack\Page;
|
|
||||||
use BookStack\Repos\PageRepo;
|
use BookStack\Repos\PageRepo;
|
||||||
use BookStack\Services\FileService;
|
use BookStack\Services\FileService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -37,16 +34,18 @@ class FileController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function upload(Request $request)
|
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.
|
// TODO - ensure uploads are deleted on page delete.
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'uploaded_to' => 'required|integer|exists:pages,id'
|
'uploaded_to' => 'required|integer|exists:pages,id'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$uploadedFile = $request->file('file');
|
|
||||||
$pageId = $request->get('uploaded_to');
|
$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 {
|
try {
|
||||||
$file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
|
$file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
|
||||||
@ -62,10 +61,10 @@ class FileController extends Controller
|
|||||||
* @param $pageId
|
* @param $pageId
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function getFilesForPage($pageId)
|
public function listForPage($pageId)
|
||||||
{
|
{
|
||||||
// TODO - check view permission on page?
|
|
||||||
$page = $this->pageRepo->getById($pageId);
|
$page = $this->pageRepo->getById($pageId);
|
||||||
|
$this->checkOwnablePermission('page-view', $page);
|
||||||
return response()->json($page->files);
|
return response()->json($page->files);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,17 +74,47 @@ class FileController extends Controller
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function sortFilesForPage($pageId, Request $request)
|
public function sortForPage($pageId, Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'files' => 'required|array',
|
'files' => 'required|array',
|
||||||
'files.*.id' => 'required|integer',
|
'files.*.id' => 'required|integer',
|
||||||
]);
|
]);
|
||||||
$page = $this->pageRepo->getById($pageId);
|
$page = $this->pageRepo->getById($pageId);
|
||||||
|
$this->checkOwnablePermission('page-update', $page);
|
||||||
|
|
||||||
$files = $request->get('files');
|
$files = $request->get('files');
|
||||||
$this->fileService->updateFileOrderWithinPage($files, $pageId);
|
$this->fileService->updateFileOrderWithinPage($files, $pageId);
|
||||||
return response()->json(['message' => 'File order updated']);
|
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']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,24 @@
|
|||||||
use BookStack\Exceptions\FileUploadException;
|
use BookStack\Exceptions\FileUploadException;
|
||||||
use BookStack\File;
|
use BookStack\File;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||||
|
|
||||||
class FileService extends UploadService
|
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.
|
* Store a new file upon user upload.
|
||||||
* @param UploadedFile $uploadedFile
|
* @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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -28,6 +28,26 @@ class CreateFilesTable extends Migration
|
|||||||
$table->index('uploaded_to');
|
$table->index('uploaded_to');
|
||||||
$table->timestamps();
|
$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()
|
public function down()
|
||||||
{
|
{
|
||||||
Schema::dropIfExists('files');
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -575,9 +575,9 @@ module.exports = function (ngApp, events) {
|
|||||||
*/
|
*/
|
||||||
function getFiles() {
|
function getFiles() {
|
||||||
let url = window.baseUrl(`/files/get/page/${pageId}`)
|
let url = window.baseUrl(`/files/get/page/${pageId}`)
|
||||||
$http.get(url).then(responseData => {
|
$http.get(url).then(resp => {
|
||||||
$scope.files = responseData.data;
|
$scope.files = resp.data;
|
||||||
currentOrder = responseData.data.map(file => {return file.id}).join(':');
|
currentOrder = resp.data.map(file => {return file.id}).join(':');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getFiles();
|
getFiles();
|
||||||
@ -595,6 +595,17 @@ module.exports = function (ngApp, events) {
|
|||||||
events.emit('success', 'File uploaded');
|
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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
<tr ng-repeat="file in files track by $index">
|
<tr ng-repeat="file in files track by $index">
|
||||||
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
<td width="20" ><i class="handle zmdi zmdi-menu"></i></td>
|
||||||
<td ng-bind="file.name"></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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
|
|
||||||
<div class="book-tree" ng-non-bindable>
|
<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)
|
@if (isset($pageNav) && $pageNav)
|
||||||
<h6 class="text-muted">Page Navigation</h6>
|
<h6 class="text-muted">Page Navigation</h6>
|
||||||
<div class="sidebar-page-nav menu">
|
<div class="sidebar-page-nav menu">
|
||||||
@ -10,8 +19,6 @@
|
|||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h6 class="text-muted">Book Navigation</h6>
|
<h6 class="text-muted">Book Navigation</h6>
|
||||||
|
@ -106,6 +106,19 @@
|
|||||||
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
|
<label>@include('settings/roles/checkbox', ['permission' => 'image-delete-all']) All</label>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,9 +88,11 @@ Route::group(['middleware' => 'auth'], function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// File routes
|
// File routes
|
||||||
|
Route::get('/files/{id}', 'FileController@get');
|
||||||
Route::post('/files/upload', 'FileController@upload');
|
Route::post('/files/upload', 'FileController@upload');
|
||||||
Route::get('/files/get/page/{pageId}', 'FileController@getFilesForPage');
|
Route::get('/files/get/page/{pageId}', 'FileController@listForPage');
|
||||||
Route::put('/files/sort/page/{pageId}', 'FileController@sortFilesForPage');
|
Route::put('/files/sort/page/{pageId}', 'FileController@sortForPage');
|
||||||
|
Route::delete('/files/{id}', 'FileController@delete');
|
||||||
|
|
||||||
// AJAX routes
|
// AJAX routes
|
||||||
Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
|
Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
|
||||||
|
Loading…
Reference in New Issue
Block a user