/**
* 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;
+ }
}
-<?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;
*/
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);
* @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);
}
* @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']);
+ }
}
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
}
}
+ /**
+ * 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
$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
+ ]);
+ }
+
}
/**
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();
+ }
}
}
*/
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();
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);
+ });
+ };
+
}]);
};
<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>
<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">
</li>
@endforeach
</div>
-
-
@endif
<h6 class="text-muted">Book Navigation</h6>
<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>
});
// 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');