mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Renamed files to attachments
This commit is contained in:
parent
d439fb459b
commit
e639600ba5
@ -1,7 +1,7 @@
|
||||
<?php namespace BookStack;
|
||||
|
||||
|
||||
class File extends Ownable
|
||||
class Attachment extends Ownable
|
||||
{
|
||||
protected $fillable = ['name', 'order'];
|
||||
|
||||
@ -30,7 +30,7 @@ class File extends Ownable
|
||||
*/
|
||||
public function getUrl()
|
||||
{
|
||||
return baseUrl('/files/' . $this->id);
|
||||
return baseUrl('/attachments/' . $this->id);
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +1,36 @@
|
||||
<?php namespace BookStack\Http\Controllers;
|
||||
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\File;
|
||||
use BookStack\Attachment;
|
||||
use BookStack\Repos\PageRepo;
|
||||
use BookStack\Services\FileService;
|
||||
use BookStack\Services\AttachmentService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use BookStack\Http\Requests;
|
||||
|
||||
class FileController extends Controller
|
||||
class AttachmentController extends Controller
|
||||
{
|
||||
protected $fileService;
|
||||
protected $file;
|
||||
protected $attachmentService;
|
||||
protected $attachment;
|
||||
protected $pageRepo;
|
||||
|
||||
/**
|
||||
* FileController constructor.
|
||||
* @param FileService $fileService
|
||||
* @param File $file
|
||||
* AttachmentController constructor.
|
||||
* @param AttachmentService $attachmentService
|
||||
* @param Attachment $attachment
|
||||
* @param PageRepo $pageRepo
|
||||
*/
|
||||
public function __construct(FileService $fileService, File $file, PageRepo $pageRepo)
|
||||
public function __construct(AttachmentService $attachmentService, Attachment $attachment, PageRepo $pageRepo)
|
||||
{
|
||||
$this->fileService = $fileService;
|
||||
$this->file = $file;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->attachment = $attachment;
|
||||
$this->pageRepo = $pageRepo;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Endpoint at which files are uploaded to.
|
||||
* Endpoint at which attachments are uploaded to.
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function upload(Request $request)
|
||||
{
|
||||
@ -42,27 +42,27 @@ class FileController extends Controller
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
|
||||
$this->checkPermission('file-create-all');
|
||||
$this->checkPermission('attachment-create-all');
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$uploadedFile = $request->file('file');
|
||||
|
||||
try {
|
||||
$file = $this->fileService->saveNewUpload($uploadedFile, $pageId);
|
||||
$attachment = $this->attachmentService->saveNewUpload($uploadedFile, $pageId);
|
||||
} catch (FileUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($file);
|
||||
return response()->json($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an uploaded file.
|
||||
* @param int $fileId
|
||||
* Update an uploaded attachment.
|
||||
* @param int $attachmentId
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function uploadUpdate($fileId, Request $request)
|
||||
public function uploadUpdate($attachmentId, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'uploaded_to' => 'required|integer|exists:pages,id',
|
||||
@ -71,33 +71,33 @@ class FileController extends Controller
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$file = $this->file->findOrFail($fileId);
|
||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
||||
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('file-create', $file);
|
||||
$this->checkOwnablePermission('attachment-create', $attachment);
|
||||
|
||||
if (intval($pageId) !== intval($file->uploaded_to)) {
|
||||
if (intval($pageId) !== intval($attachment->uploaded_to)) {
|
||||
return $this->jsonError('Page mismatch during attached file update');
|
||||
}
|
||||
|
||||
$uploadedFile = $request->file('file');
|
||||
|
||||
try {
|
||||
$file = $this->fileService->saveUpdatedUpload($uploadedFile, $file);
|
||||
$attachment = $this->attachmentService->saveUpdatedUpload($uploadedFile, $attachment);
|
||||
} catch (FileUploadException $e) {
|
||||
return response($e->getMessage(), 500);
|
||||
}
|
||||
|
||||
return response()->json($file);
|
||||
return response()->json($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the details of an existing file.
|
||||
* @param $fileId
|
||||
* @param $attachmentId
|
||||
* @param Request $request
|
||||
* @return File|mixed
|
||||
* @return Attachment|mixed
|
||||
*/
|
||||
public function update($fileId, Request $request)
|
||||
public function update($attachmentId, Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'uploaded_to' => 'required|integer|exists:pages,id',
|
||||
@ -107,21 +107,21 @@ class FileController extends Controller
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$file = $this->file->findOrFail($fileId);
|
||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
||||
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('file-create', $file);
|
||||
$this->checkOwnablePermission('attachment-create', $attachment);
|
||||
|
||||
if (intval($pageId) !== intval($file->uploaded_to)) {
|
||||
if (intval($pageId) !== intval($attachment->uploaded_to)) {
|
||||
return $this->jsonError('Page mismatch during attachment update');
|
||||
}
|
||||
|
||||
$file = $this->fileService->updateFile($file, $request->all());
|
||||
return $file;
|
||||
$attachment = $this->attachmentService->updateFile($attachment, $request->all());
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a link to a page as a file.
|
||||
* Attach a link to a page.
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
@ -136,18 +136,18 @@ class FileController extends Controller
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
|
||||
$this->checkPermission('file-create-all');
|
||||
$this->checkPermission('attachment-create-all');
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$fileName = $request->get('name');
|
||||
$attachmentName = $request->get('name');
|
||||
$link = $request->get('link');
|
||||
$file = $this->fileService->saveNewFromLink($fileName, $link, $pageId);
|
||||
$attachment = $this->attachmentService->saveNewFromLink($attachmentName, $link, $pageId);
|
||||
|
||||
return response()->json($file);
|
||||
return response()->json($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files for a specific page.
|
||||
* Get the attachments for a specific page.
|
||||
* @param $pageId
|
||||
* @return mixed
|
||||
*/
|
||||
@ -159,7 +159,7 @@ class FileController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the file sorting.
|
||||
* Update the attachment sorting.
|
||||
* @param $pageId
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
@ -173,42 +173,43 @@ class FileController extends Controller
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$files = $request->get('files');
|
||||
$this->fileService->updateFileOrderWithinPage($files, $pageId);
|
||||
$attachments = $request->get('files');
|
||||
$this->attachmentService->updateFileOrderWithinPage($attachments, $pageId);
|
||||
return response()->json(['message' => 'Attachment order updated']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a file from storage.
|
||||
* @param $fileId
|
||||
* Get an attachment from storage.
|
||||
* @param $attachmentId
|
||||
* @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function get($fileId)
|
||||
public function get($attachmentId)
|
||||
{
|
||||
$file = $this->file->findOrFail($fileId);
|
||||
$page = $this->pageRepo->getById($file->uploaded_to);
|
||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
||||
$page = $this->pageRepo->getById($attachment->uploaded_to);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
|
||||
if ($file->external) {
|
||||
return redirect($file->path);
|
||||
if ($attachment->external) {
|
||||
return redirect($attachment->path);
|
||||
}
|
||||
|
||||
$fileContents = $this->fileService->getFile($file);
|
||||
return response($fileContents, 200, [
|
||||
$attachmentContents = $this->attachmentService->getAttachmentFromStorage($attachment);
|
||||
return response($attachmentContents, 200, [
|
||||
'Content-Type' => 'application/octet-stream',
|
||||
'Content-Disposition' => 'attachment; filename="'. $file->getFileName() .'"'
|
||||
'Content-Disposition' => 'attachment; filename="'. $attachment->getFileName() .'"'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific file in the system.
|
||||
* @param $fileId
|
||||
* Delete a specific attachment in the system.
|
||||
* @param $attachmentId
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete($fileId)
|
||||
public function delete($attachmentId)
|
||||
{
|
||||
$file = $this->file->findOrFail($fileId);
|
||||
$this->checkOwnablePermission('file-delete', $file);
|
||||
$this->fileService->deleteFile($file);
|
||||
$attachment = $this->attachment->findOrFail($attachmentId);
|
||||
$this->checkOwnablePermission('attachment-delete', $attachment);
|
||||
$this->attachmentService->deleteFile($attachment);
|
||||
return response()->json(['message' => 'Attachment deleted']);
|
||||
}
|
||||
}
|
@ -55,12 +55,12 @@ class Page extends Entity
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the files attached to this page.
|
||||
* Get the attachments assigned to this page.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function files()
|
||||
public function attachments()
|
||||
{
|
||||
return $this->hasMany(File::class, 'uploaded_to')->orderBy('order', 'asc');
|
||||
return $this->hasMany(Attachment::class, 'uploaded_to')->orderBy('order', 'asc');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@ use BookStack\Book;
|
||||
use BookStack\Chapter;
|
||||
use BookStack\Entity;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Services\FileService;
|
||||
use BookStack\Services\AttachmentService;
|
||||
use Carbon\Carbon;
|
||||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
@ -636,9 +636,9 @@ class PageRepo extends EntityRepo
|
||||
$this->permissionService->deleteJointPermissionsForEntity($page);
|
||||
|
||||
// Delete AttachedFiles
|
||||
$fileService = app(FileService::class);
|
||||
foreach ($page->files as $file) {
|
||||
$fileService->deleteFile($file);
|
||||
$attachmentService = app(AttachmentService::class);
|
||||
foreach ($page->attachments as $attachment) {
|
||||
$attachmentService->deleteFile($attachment);
|
||||
}
|
||||
|
||||
$page->delete();
|
||||
@ -647,6 +647,7 @@ class PageRepo extends EntityRepo
|
||||
/**
|
||||
* Get the latest pages added to the system.
|
||||
* @param $count
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRecentlyCreatedPaginated($count = 20)
|
||||
{
|
||||
@ -656,6 +657,7 @@ class PageRepo extends EntityRepo
|
||||
/**
|
||||
* Get the latest pages added to the system.
|
||||
* @param $count
|
||||
* @return mixed
|
||||
*/
|
||||
public function getRecentlyUpdatedPaginated($count = 20)
|
||||
{
|
||||
|
201
app/Services/AttachmentService.php
Normal file
201
app/Services/AttachmentService.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php namespace BookStack\Services;
|
||||
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Attachment;
|
||||
use Exception;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
|
||||
class AttachmentService extends UploadService
|
||||
{
|
||||
|
||||
/**
|
||||
* Get an attachment from storage.
|
||||
* @param Attachment $attachment
|
||||
* @return string
|
||||
*/
|
||||
public function getAttachmentFromStorage(Attachment $attachment)
|
||||
{
|
||||
$attachmentPath = $this->getStorageBasePath() . $attachment->path;
|
||||
return $this->getStorage()->get($attachmentPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new attachment upon user upload.
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @param int $page_id
|
||||
* @return Attachment
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
|
||||
{
|
||||
$attachmentName = $uploadedFile->getClientOriginalName();
|
||||
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
|
||||
$largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
|
||||
|
||||
$attachment = Attachment::forceCreate([
|
||||
'name' => $attachmentName,
|
||||
'path' => $attachmentPath,
|
||||
'extension' => $uploadedFile->getClientOriginalExtension(),
|
||||
'uploaded_to' => $page_id,
|
||||
'created_by' => user()->id,
|
||||
'updated_by' => user()->id,
|
||||
'order' => $largestExistingOrder + 1
|
||||
]);
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a upload, saving to a file and deleting any existing uploads
|
||||
* attached to that file.
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @param Attachment $attachment
|
||||
* @return Attachment
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
public function saveUpdatedUpload(UploadedFile $uploadedFile, Attachment $attachment)
|
||||
{
|
||||
if (!$attachment->external) {
|
||||
$this->deleteFileInStorage($attachment);
|
||||
}
|
||||
|
||||
$attachmentName = $uploadedFile->getClientOriginalName();
|
||||
$attachmentPath = $this->putFileInStorage($attachmentName, $uploadedFile);
|
||||
|
||||
$attachment->name = $attachmentName;
|
||||
$attachment->path = $attachmentPath;
|
||||
$attachment->external = false;
|
||||
$attachment->extension = $uploadedFile->getClientOriginalExtension();
|
||||
$attachment->save();
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new File attachment from a given link and name.
|
||||
* @param string $name
|
||||
* @param string $link
|
||||
* @param int $page_id
|
||||
* @return Attachment
|
||||
*/
|
||||
public function saveNewFromLink($name, $link, $page_id)
|
||||
{
|
||||
$largestExistingOrder = Attachment::where('uploaded_to', '=', $page_id)->max('order');
|
||||
return Attachment::forceCreate([
|
||||
'name' => $name,
|
||||
'path' => $link,
|
||||
'external' => true,
|
||||
'extension' => '',
|
||||
'uploaded_to' => $page_id,
|
||||
'created_by' => user()->id,
|
||||
'updated_by' => user()->id,
|
||||
'order' => $largestExistingOrder + 1
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file storage base path, amended for storage type.
|
||||
* This allows us to keep a generic path in the database.
|
||||
* @return string
|
||||
*/
|
||||
private function getStorageBasePath()
|
||||
{
|
||||
return $this->isLocal() ? 'storage/' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file ordering for a listing of attached files.
|
||||
* @param array $attachmentList
|
||||
* @param $pageId
|
||||
*/
|
||||
public function updateFileOrderWithinPage($attachmentList, $pageId)
|
||||
{
|
||||
foreach ($attachmentList as $index => $attachment) {
|
||||
Attachment::where('uploaded_to', '=', $pageId)->where('id', '=', $attachment['id'])->update(['order' => $index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the details of a file.
|
||||
* @param Attachment $attachment
|
||||
* @param $requestData
|
||||
* @return Attachment
|
||||
*/
|
||||
public function updateFile(Attachment $attachment, $requestData)
|
||||
{
|
||||
$attachment->name = $requestData['name'];
|
||||
if (isset($requestData['link']) && trim($requestData['link']) !== '') {
|
||||
$attachment->path = $requestData['link'];
|
||||
if (!$attachment->external) {
|
||||
$this->deleteFileInStorage($attachment);
|
||||
$attachment->external = true;
|
||||
}
|
||||
}
|
||||
$attachment->save();
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a File from the database and storage.
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function deleteFile(Attachment $attachment)
|
||||
{
|
||||
if ($attachment->external) {
|
||||
$attachment->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deleteFileInStorage($attachment);
|
||||
$attachment->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from the filesystem it sits on.
|
||||
* Cleans any empty leftover folders.
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
protected function deleteFileInStorage(Attachment $attachment)
|
||||
{
|
||||
$storedFilePath = $this->getStorageBasePath() . $attachment->path;
|
||||
$storage = $this->getStorage();
|
||||
$dirPath = dirname($storedFilePath);
|
||||
|
||||
$storage->delete($storedFilePath);
|
||||
if (count($storage->allFiles($dirPath)) === 0) {
|
||||
$storage->deleteDirectory($dirPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a file in storage with the given filename
|
||||
* @param $attachmentName
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @return string
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
protected function putFileInStorage($attachmentName, UploadedFile $uploadedFile)
|
||||
{
|
||||
$attachmentData = file_get_contents($uploadedFile->getRealPath());
|
||||
|
||||
$storage = $this->getStorage();
|
||||
$attachmentBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
|
||||
$storageBasePath = $this->getStorageBasePath() . $attachmentBasePath;
|
||||
|
||||
$uploadFileName = $attachmentName;
|
||||
while ($storage->exists($storageBasePath . $uploadFileName)) {
|
||||
$uploadFileName = str_random(3) . $uploadFileName;
|
||||
}
|
||||
|
||||
$attachmentPath = $attachmentBasePath . $uploadFileName;
|
||||
$attachmentStoragePath = $this->getStorageBasePath() . $attachmentPath;
|
||||
|
||||
try {
|
||||
$storage->put($attachmentStoragePath, $attachmentData);
|
||||
} catch (Exception $e) {
|
||||
throw new FileUploadException('File path ' . $attachmentStoragePath . ' could not be uploaded to. Ensure it is writable to the server.');
|
||||
}
|
||||
return $attachmentPath;
|
||||
}
|
||||
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
<?php namespace BookStack\Services;
|
||||
|
||||
|
||||
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
|
||||
* @param int $page_id
|
||||
* @return File
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
public function saveNewUpload(UploadedFile $uploadedFile, $page_id)
|
||||
{
|
||||
$fileName = $uploadedFile->getClientOriginalName();
|
||||
$filePath = $this->putFileInStorage($fileName, $uploadedFile);
|
||||
$largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
|
||||
|
||||
$file = File::forceCreate([
|
||||
'name' => $fileName,
|
||||
'path' => $filePath,
|
||||
'extension' => $uploadedFile->getClientOriginalExtension(),
|
||||
'uploaded_to' => $page_id,
|
||||
'created_by' => user()->id,
|
||||
'updated_by' => user()->id,
|
||||
'order' => $largestExistingOrder + 1
|
||||
]);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a upload, saving to a file and deleting any existing uploads
|
||||
* attached to that file.
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @param File $file
|
||||
* @return File
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
public function saveUpdatedUpload(UploadedFile $uploadedFile, File $file)
|
||||
{
|
||||
if (!$file->external) {
|
||||
$this->deleteFileInStorage($file);
|
||||
}
|
||||
|
||||
$fileName = $uploadedFile->getClientOriginalName();
|
||||
$filePath = $this->putFileInStorage($fileName, $uploadedFile);
|
||||
|
||||
$file->name = $fileName;
|
||||
$file->path = $filePath;
|
||||
$file->external = false;
|
||||
$file->extension = $uploadedFile->getClientOriginalExtension();
|
||||
$file->save();
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new File attachment from a given link and name.
|
||||
* @param string $name
|
||||
* @param string $link
|
||||
* @param int $page_id
|
||||
* @return File
|
||||
*/
|
||||
public function saveNewFromLink($name, $link, $page_id)
|
||||
{
|
||||
$largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order');
|
||||
return File::forceCreate([
|
||||
'name' => $name,
|
||||
'path' => $link,
|
||||
'external' => true,
|
||||
'extension' => '',
|
||||
'uploaded_to' => $page_id,
|
||||
'created_by' => user()->id,
|
||||
'updated_by' => user()->id,
|
||||
'order' => $largestExistingOrder + 1
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file storage base path, amended for storage type.
|
||||
* This allows us to keep a generic path in the database.
|
||||
* @return string
|
||||
*/
|
||||
private function getStorageBasePath()
|
||||
{
|
||||
return $this->isLocal() ? 'storage/' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file ordering for a listing of attached files.
|
||||
* @param array $fileList
|
||||
* @param $pageId
|
||||
*/
|
||||
public function updateFileOrderWithinPage($fileList, $pageId)
|
||||
{
|
||||
foreach ($fileList as $index => $file) {
|
||||
File::where('uploaded_to', '=', $pageId)->where('id', '=', $file['id'])->update(['order' => $index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the details of a file.
|
||||
* @param File $file
|
||||
* @param $requestData
|
||||
* @return File
|
||||
*/
|
||||
public function updateFile(File $file, $requestData)
|
||||
{
|
||||
$file->name = $requestData['name'];
|
||||
if (isset($requestData['link']) && trim($requestData['link']) !== '') {
|
||||
$file->path = $requestData['link'];
|
||||
if (!$file->external) {
|
||||
$this->deleteFileInStorage($file);
|
||||
$file->external = true;
|
||||
}
|
||||
}
|
||||
$file->save();
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a File from the database and storage.
|
||||
* @param File $file
|
||||
*/
|
||||
public function deleteFile(File $file)
|
||||
{
|
||||
if ($file->external) {
|
||||
$file->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->deleteFileInStorage($file);
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from the filesystem it sits on.
|
||||
* Cleans any empty leftover folders.
|
||||
* @param File $file
|
||||
*/
|
||||
protected function deleteFileInStorage(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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a file in storage with the given filename
|
||||
* @param $fileName
|
||||
* @param UploadedFile $uploadedFile
|
||||
* @return string
|
||||
* @throws FileUploadException
|
||||
*/
|
||||
protected function putFileInStorage($fileName, UploadedFile $uploadedFile)
|
||||
{
|
||||
$fileData = file_get_contents($uploadedFile->getRealPath());
|
||||
|
||||
$storage = $this->getStorage();
|
||||
$fileBasePath = 'uploads/files/' . Date('Y-m-M') . '/';
|
||||
$storageBasePath = $this->getStorageBasePath() . $fileBasePath;
|
||||
|
||||
$uploadFileName = $fileName;
|
||||
while ($storage->exists($storageBasePath . $uploadFileName)) {
|
||||
$uploadFileName = str_random(3) . $uploadFileName;
|
||||
}
|
||||
|
||||
$filePath = $fileBasePath . $uploadFileName;
|
||||
$fileStoragePath = $this->getStorageBasePath() . $filePath;
|
||||
|
||||
try {
|
||||
$storage->put($fileStoragePath, $fileData);
|
||||
} catch (Exception $e) {
|
||||
throw new FileUploadException('File path ' . $fileStoragePath . ' could not be uploaded to. Ensure it is writable to the server.');
|
||||
}
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFilesTable extends Migration
|
||||
class CreateAttachmentsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
@ -13,7 +13,7 @@ class CreateFilesTable extends Migration
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('files', function (Blueprint $table) {
|
||||
Schema::create('attachments', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->string('name');
|
||||
$table->string('path');
|
||||
@ -35,7 +35,7 @@ class CreateFilesTable extends Migration
|
||||
|
||||
// Create & attach new entity permissions
|
||||
$ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
|
||||
$entity = 'File';
|
||||
$entity = 'Attachment';
|
||||
foreach ($ops as $op) {
|
||||
$permissionId = DB::table('role_permissions')->insertGetId([
|
||||
'name' => strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)),
|
||||
@ -58,11 +58,11 @@ class CreateFilesTable extends Migration
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('files');
|
||||
Schema::dropIfExists('attachments');
|
||||
|
||||
// Create & attach new entity permissions
|
||||
$ops = ['Create All', 'Create Own', 'Update All', 'Update Own', 'Delete All', 'Delete Own'];
|
||||
$entity = 'File';
|
||||
$entity = 'Attachment';
|
||||
foreach ($ops as $op) {
|
||||
$permName = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
|
||||
DB::table('role_permissions')->where('name', '=', $permName)->delete();
|
@ -570,7 +570,7 @@ export default function (ngApp, events) {
|
||||
if (newOrder === currentOrder) return;
|
||||
|
||||
currentOrder = newOrder;
|
||||
$http.put(window.baseUrl(`/files/sort/page/${pageId}`), {files: $scope.files}).then(resp => {
|
||||
$http.put(window.baseUrl(`/attachments/sort/page/${pageId}`), {files: $scope.files}).then(resp => {
|
||||
events.emit('success', resp.data.message);
|
||||
}, checkError('sort'));
|
||||
}
|
||||
@ -581,14 +581,14 @@ export default function (ngApp, events) {
|
||||
*/
|
||||
$scope.getUploadUrl = function (file) {
|
||||
let suffix = (typeof file !== 'undefined') ? `/${file.id}` : '';
|
||||
return window.baseUrl(`/files/upload${suffix}`);
|
||||
return window.baseUrl(`/attachments/upload${suffix}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get files for the current page from the server.
|
||||
*/
|
||||
function getFiles() {
|
||||
let url = window.baseUrl(`/files/get/page/${pageId}`)
|
||||
let url = window.baseUrl(`/attachments/get/page/${pageId}`)
|
||||
$http.get(url).then(resp => {
|
||||
$scope.files = resp.data;
|
||||
currentOrder = resp.data.map(file => {return file.id}).join(':');
|
||||
@ -636,7 +636,7 @@ export default function (ngApp, events) {
|
||||
file.deleting = true;
|
||||
return;
|
||||
}
|
||||
$http.delete(window.baseUrl(`/files/${file.id}`)).then(resp => {
|
||||
$http.delete(window.baseUrl(`/attachments/${file.id}`)).then(resp => {
|
||||
events.emit('success', resp.data.message);
|
||||
$scope.files.splice($scope.files.indexOf(file), 1);
|
||||
}, checkError('delete'));
|
||||
@ -648,7 +648,7 @@ export default function (ngApp, events) {
|
||||
*/
|
||||
$scope.attachLinkSubmit = function(file) {
|
||||
file.uploaded_to = pageId;
|
||||
$http.post(window.baseUrl('/files/link'), file).then(resp => {
|
||||
$http.post(window.baseUrl('/attachments/link'), file).then(resp => {
|
||||
$scope.files.push(resp.data);
|
||||
events.emit('success', 'Link attached');
|
||||
$scope.file = getCleanFile();
|
||||
@ -676,7 +676,7 @@ export default function (ngApp, events) {
|
||||
* @param file
|
||||
*/
|
||||
$scope.updateFile = function(file) {
|
||||
$http.put(window.baseUrl(`/files/${file.id}`), file).then(resp => {
|
||||
$http.put(window.baseUrl(`/attachments/${file.id}`), file).then(resp => {
|
||||
let search = filesIndexOf(resp.data);
|
||||
if (search !== -1) $scope.files[search] = resp.data;
|
||||
|
||||
@ -692,7 +692,7 @@ export default function (ngApp, events) {
|
||||
* Get the url of a file.
|
||||
*/
|
||||
$scope.getFileUrl = function(file) {
|
||||
return window.baseUrl('/files/' + file.id);
|
||||
return window.baseUrl('/attachments/' + file.id);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="tabs primary-background-light">
|
||||
<span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
|
||||
<span toolbox-tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
|
||||
@if(userCan('file-create-all'))
|
||||
@if(userCan('attachment-create-all'))
|
||||
<span toolbox-tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span>
|
||||
@endif
|
||||
</div>
|
||||
@ -37,7 +37,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(userCan('file-create-all'))
|
||||
@if(userCan('attachment-create-all'))
|
||||
<div toolbox-tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
|
||||
<h4>Attachments</h4>
|
||||
<div class="padded files">
|
||||
|
@ -1,11 +1,11 @@
|
||||
|
||||
<div class="book-tree" ng-non-bindable>
|
||||
|
||||
@if (isset($page) && $page->files->count() > 0)
|
||||
@if (isset($page) && $page->attachments->count() > 0)
|
||||
<h6 class="text-muted">Attachments</h6>
|
||||
@foreach($page->files as $file)
|
||||
@foreach($page->attachments as $attachment)
|
||||
<div class="attachment">
|
||||
<a href="{{ $file->getUrl() }}" @if($file->external) target="_blank" @endif><i class="zmdi zmdi-{{ $file->external ? 'open-in-new' : 'file' }}"></i> {{ $file->name }}</a>
|
||||
<a href="{{ $attachment->getUrl() }}" @if($attachment->external) target="_blank" @endif><i class="zmdi zmdi-{{ $attachment->external ? 'open-in-new' : 'file' }}"></i> {{ $attachment->name }}</a>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
|
@ -107,16 +107,16 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Attached <br>Files</td>
|
||||
<td>@include('settings/roles/checkbox', ['permission' => 'file-create-all'])</td>
|
||||
<td>Attachments</td>
|
||||
<td>@include('settings/roles/checkbox', ['permission' => 'attachment-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>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'attachment-update-own']) Own</label>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'attachment-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>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-own']) Own</label>
|
||||
<label>@include('settings/roles/checkbox', ['permission' => 'attachment-delete-all']) All</label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -87,15 +87,15 @@ Route::group(['middleware' => 'auth'], function () {
|
||||
Route::delete('/{imageId}', 'ImageController@destroy');
|
||||
});
|
||||
|
||||
// File routes
|
||||
Route::get('/files/{id}', 'FileController@get');
|
||||
Route::post('/files/upload', 'FileController@upload');
|
||||
Route::post('/files/upload/{id}', 'FileController@uploadUpdate');
|
||||
Route::post('/files/link', 'FileController@attachLink');
|
||||
Route::put('/files/{id}', 'FileController@update');
|
||||
Route::get('/files/get/page/{pageId}', 'FileController@listForPage');
|
||||
Route::put('/files/sort/page/{pageId}', 'FileController@sortForPage');
|
||||
Route::delete('/files/{id}', 'FileController@delete');
|
||||
// Attachments routes
|
||||
Route::get('/attachments/{id}', 'AttachmentController@get');
|
||||
Route::post('/attachments/upload', 'AttachmentController@upload');
|
||||
Route::post('/attachments/upload/{id}', 'AttachmentController@uploadUpdate');
|
||||
Route::post('/attachments/link', 'AttachmentController@attachLink');
|
||||
Route::put('/attachments/{id}', 'AttachmentController@update');
|
||||
Route::get('/attachments/get/page/{pageId}', 'AttachmentController@listForPage');
|
||||
Route::put('/attachments/sort/page/{pageId}', 'AttachmentController@sortForPage');
|
||||
Route::delete('/attachments/{id}', 'AttachmentController@delete');
|
||||
|
||||
// AJAX routes
|
||||
Route::put('/ajax/page/{id}/save-draft', 'PageController@saveDraft');
|
||||
|
@ -21,7 +21,7 @@ class AttachmentTest extends TestCase
|
||||
protected function uploadFile($name, $uploadedTo = 0)
|
||||
{
|
||||
$file = $this->getTestFile($name);
|
||||
return $this->call('POST', '/files/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []);
|
||||
return $this->call('POST', '/attachments/upload', ['uploaded_to' => $uploadedTo], [], ['file' => $file], []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,8 +40,8 @@ class AttachmentTest extends TestCase
|
||||
*/
|
||||
protected function deleteUploads()
|
||||
{
|
||||
$fileService = $this->app->make(\BookStack\Services\FileService::class);
|
||||
foreach (\BookStack\File::all() as $file) {
|
||||
$fileService = $this->app->make(\BookStack\Services\AttachmentService::class);
|
||||
foreach (\BookStack\Attachment::all() as $file) {
|
||||
$fileService->deleteFile($file);
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,7 @@ class AttachmentTest extends TestCase
|
||||
$this->uploadFile($fileName, $page->id);
|
||||
$this->assertResponseOk();
|
||||
$this->seeJsonContains($expectedResp);
|
||||
$this->seeInDatabase('files', $expectedResp);
|
||||
$this->seeInDatabase('attachments', $expectedResp);
|
||||
|
||||
$this->deleteUploads();
|
||||
}
|
||||
@ -94,7 +94,7 @@ class AttachmentTest extends TestCase
|
||||
$admin = $this->getAdmin();
|
||||
$this->asAdmin();
|
||||
|
||||
$this->call('POST', 'files/link', [
|
||||
$this->call('POST', 'attachments/link', [
|
||||
'link' => 'https://example.com',
|
||||
'name' => 'Example Attachment Link',
|
||||
'uploaded_to' => $page->id,
|
||||
@ -113,7 +113,7 @@ class AttachmentTest extends TestCase
|
||||
|
||||
$this->assertResponseOk();
|
||||
$this->seeJsonContains($expectedResp);
|
||||
$this->seeInDatabase('files', $expectedResp);
|
||||
$this->seeInDatabase('attachments', $expectedResp);
|
||||
|
||||
$this->visit($page->getUrl())->seeLink('Example Attachment Link')
|
||||
->click('Example Attachment Link')->seePageIs('https://example.com');
|
||||
@ -126,15 +126,15 @@ class AttachmentTest extends TestCase
|
||||
$page = \BookStack\Page::first();
|
||||
$this->asAdmin();
|
||||
|
||||
$this->call('POST', 'files/link', [
|
||||
$this->call('POST', 'attachments/link', [
|
||||
'link' => 'https://example.com',
|
||||
'name' => 'Example Attachment Link',
|
||||
'uploaded_to' => $page->id,
|
||||
]);
|
||||
|
||||
$attachmentId = \BookStack\File::first()->id;
|
||||
$attachmentId = \BookStack\Attachment::first()->id;
|
||||
|
||||
$this->call('PUT', 'files/' . $attachmentId, [
|
||||
$this->call('PUT', 'attachments/' . $attachmentId, [
|
||||
'uploaded_to' => $page->id,
|
||||
'name' => 'My new attachment name',
|
||||
'link' => 'https://test.example.com'
|
||||
@ -148,7 +148,7 @@ class AttachmentTest extends TestCase
|
||||
|
||||
$this->assertResponseOk();
|
||||
$this->seeJsonContains($expectedResp);
|
||||
$this->seeInDatabase('files', $expectedResp);
|
||||
$this->seeInDatabase('attachments', $expectedResp);
|
||||
|
||||
$this->deleteUploads();
|
||||
}
|
||||
@ -164,10 +164,10 @@ class AttachmentTest extends TestCase
|
||||
|
||||
$this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
|
||||
|
||||
$attachmentId = \BookStack\File::first()->id;
|
||||
$this->call('DELETE', 'files/' . $attachmentId);
|
||||
$attachmentId = \BookStack\Attachment::first()->id;
|
||||
$this->call('DELETE', 'attachments/' . $attachmentId);
|
||||
|
||||
$this->dontSeeInDatabase('files', [
|
||||
$this->dontSeeInDatabase('attachments', [
|
||||
'name' => $fileName
|
||||
]);
|
||||
$this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected');
|
||||
@ -185,13 +185,13 @@ class AttachmentTest extends TestCase
|
||||
$filePath = base_path('storage/' . $this->getUploadPath($fileName));
|
||||
|
||||
$this->assertTrue(file_exists($filePath), 'File at path ' . $filePath . ' does not exist');
|
||||
$this->seeInDatabase('files', [
|
||||
$this->seeInDatabase('attachments', [
|
||||
'name' => $fileName
|
||||
]);
|
||||
|
||||
$this->call('DELETE', $page->getUrl());
|
||||
|
||||
$this->dontSeeInDatabase('files', [
|
||||
$this->dontSeeInDatabase('attachments', [
|
||||
'name' => $fileName
|
||||
]);
|
||||
$this->assertFalse(file_exists($filePath), 'File at path ' . $filePath . ' was not deleted as expected');
|
||||
|
Loading…
Reference in New Issue
Block a user