From 867fc8be64ec41c41c5646499c1aa34c1e817d53 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 11 Oct 2016 20:39:11 +0100 Subject: [PATCH] Added basic attachment editing functionality --- app/Http/Controllers/FileController.php | 68 +++++++++++- app/Services/FileService.php | 110 +++++++++++++++---- resources/assets/js/controllers.js | 109 +++++++++++++++--- resources/assets/js/directives.js | 1 + resources/assets/sass/_pages.scss | 15 --- resources/views/pages/form-toolbox.blade.php | 81 +++++++++----- routes/web.php | 2 + 7 files changed, 307 insertions(+), 79 deletions(-) diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php index 9486298b2..4cdcf66dc 100644 --- a/app/Http/Controllers/FileController.php +++ b/app/Http/Controllers/FileController.php @@ -57,6 +57,70 @@ class FileController extends Controller return response()->json($file); } + /** + * Update an uploaded file. + * @param int $fileId + * @param Request $request + * @return mixed + */ + public function uploadUpdate($fileId, Request $request) + { + $this->validate($request, [ + 'uploaded_to' => 'required|integer|exists:pages,id', + 'file' => 'required|file' + ]); + + $pageId = $request->get('uploaded_to'); + $page = $this->pageRepo->getById($pageId); + $file = $this->file->findOrFail($fileId); + + $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission('file-create', $file); + + if (intval($pageId) !== intval($file->uploaded_to)) { + return $this->jsonError('Page mismatch during attached file update'); + } + + $uploadedFile = $request->file('file'); + + try { + $file = $this->fileService->saveUpdatedUpload($uploadedFile, $file); + } catch (FileUploadException $e) { + return response($e->getMessage(), 500); + } + + return response()->json($file); + } + + /** + * Update the details of an existing file. + * @param $fileId + * @param Request $request + * @return File|mixed + */ + public function update($fileId, Request $request) + { + $this->validate($request, [ + 'uploaded_to' => 'required|integer|exists:pages,id', + 'name' => 'string|max:255', + 'link' => 'url' + ]); + + $pageId = $request->get('uploaded_to'); + $page = $this->pageRepo->getById($pageId); + $file = $this->file->findOrFail($fileId); + + $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission('file-create', $file); + + if (intval($pageId) !== intval($file->uploaded_to)) { + return $this->jsonError('Page mismatch during attachment update'); + } + + $file = $this->fileService->updateFile($file, $request->all()); + return $file; + } + /** * Attach a link to a page as a file. * @param Request $request @@ -66,8 +130,8 @@ class FileController extends Controller { $this->validate($request, [ 'uploaded_to' => 'required|integer|exists:pages,id', - 'name' => 'string', - 'link' => 'url' + 'name' => 'string|max:255', + 'link' => 'url|max:255' ]); $pageId = $request->get('uploaded_to'); diff --git a/app/Services/FileService.php b/app/Services/FileService.php index 3674209a8..a04d84001 100644 --- a/app/Services/FileService.php +++ b/app/Services/FileService.php @@ -32,26 +32,7 @@ class FileService extends UploadService public function saveNewUpload(UploadedFile $uploadedFile, $page_id) { $fileName = $uploadedFile->getClientOriginalName(); - $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.'); - } - + $filePath = $this->putFileInStorage($fileName, $uploadedFile); $largestExistingOrder = File::where('uploaded_to', '=', $page_id)->max('order'); $file = File::forceCreate([ @@ -66,6 +47,30 @@ class FileService extends UploadService 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->save(); + return $file; + } + /** * Save a new File attachment from a given link and name. * @param string $name @@ -109,8 +114,29 @@ class FileService extends UploadService } } + /** - * Delete a file and any empty folders the deletion leaves. + * 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) @@ -120,6 +146,17 @@ class FileService extends UploadService 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); @@ -128,8 +165,37 @@ class FileService extends UploadService if (count($storage->allFiles($dirPath)) === 0) { $storage->deleteDirectory($dirPath); } + } - $file->delete(); + /** + * 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; } } \ No newline at end of file diff --git a/resources/assets/js/controllers.js b/resources/assets/js/controllers.js index bc2d43fc8..404668768 100644 --- a/resources/assets/js/controllers.js +++ b/resources/assets/js/controllers.js @@ -536,6 +536,14 @@ module.exports = function (ngApp, events) { const pageId = $scope.uploadedTo = $attrs.pageId; let currentOrder = ''; $scope.files = []; + $scope.editFile = false; + $scope.file = getCleanFile(); + + function getCleanFile() { + return { + page_id: pageId + }; + } // Angular-UI-Sort options $scope.sortOptions = { @@ -559,15 +567,16 @@ module.exports = function (ngApp, events) { currentOrder = newOrder; $http.put(`/files/sort/page/${pageId}`, {files: $scope.files}).then(resp => { events.emit('success', resp.data.message); - }); + }, checkError); } /** * Used by dropzone to get the endpoint to upload to. * @returns {string} */ - $scope.getUploadUrl = function () { - return window.baseUrl('/files/upload'); + $scope.getUploadUrl = function (file) { + let suffix = (typeof file !== 'undefined') ? `/${file.id}` : ''; + return window.baseUrl(`/files/upload${suffix}`); }; /** @@ -578,7 +587,7 @@ module.exports = function (ngApp, events) { $http.get(url).then(resp => { $scope.files = resp.data; currentOrder = resp.data.map(file => {return file.id}).join(':'); - }); + }, checkError); } getFiles(); @@ -595,6 +604,24 @@ module.exports = function (ngApp, events) { events.emit('success', 'File uploaded'); }; + /** + * Upload and overwrite an existing file. + * @param file + * @param data + */ + $scope.uploadSuccessUpdate = function (file, data) { + $scope.$apply(() => { + let search = filesIndexOf(data); + if (search !== -1) $scope.files[search] = file; + + if ($scope.editFile) { + $scope.editFile = data; + data.link = ''; + } + }); + events.emit('success', 'File updated'); + }; + /** * Delete a file from the server and, on success, the local listing. * @param file @@ -603,21 +630,77 @@ module.exports = function (ngApp, events) { $http.delete(`/files/${file.id}`).then(resp => { events.emit('success', resp.data.message); $scope.files.splice($scope.files.indexOf(file), 1); - }); + }, checkError); }; - $scope.attachLinkSubmit = function(fileName, fileLink) { - $http.post('/files/link', { - uploaded_to: pageId, - name: fileName, - link: fileLink - }).then(resp => { + /** + * Attach a link to a page. + * @param fileName + * @param fileLink + */ + $scope.attachLinkSubmit = function(file) { + $http.post('/files/link', file).then(resp => { $scope.files.unshift(resp.data); events.emit('success', 'Link attached'); - }); - $scope.fileName = $scope.fileLink = ''; + $scope.file = getCleanFile(); + }, checkError); }; + /** + * Start the edit mode for a file. + * @param fileId + */ + $scope.startEdit = function(file) { + $scope.editFile = angular.copy(file); + if (!file.external) $scope.editFile.link = ''; + }; + + /** + * Cancel edit mode + */ + $scope.cancelEdit = function() { + $scope.editFile = false; + }; + + /** + * Update the name and link of a file. + * @param file + */ + $scope.updateFile = function(file) { + $http.put(`/files/${file.id}`, file).then(resp => { + let search = filesIndexOf(resp.data); + if (search !== -1) $scope.files[search] = file; + + if ($scope.editFile && !file.external) { + $scope.editFile.link = ''; + } + events.emit('success', 'Attachment details updated'); + }); + }; + + /** + * Search the local files via another file object. + * Used to search via object copies. + * @param file + * @returns int + */ + function filesIndexOf(file) { + for (let i = 0; i < $scope.files.length; i++) { + if ($scope.files[i].id == file.id) return file.id; + } + return -1; + } + + /** + * Check for an error response in a ajax request. + * @param response + */ + function checkError(response) { + if (typeof response.data !== 'undefined' && typeof response.data.error !== 'undefined') { + events.emit('error', response.data.error); + } + } + }]); }; diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 933bbf5ff..82cb128f3 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -116,6 +116,7 @@ module.exports = function (ngApp, events) { uploadedTo: '@' }, link: function (scope, element, attrs) { + if (attrs.placeholder) element[0].querySelector('.dz-message').textContent = attrs.placeholder; var dropZone = new DropZone(element[0].querySelector('.dropzone-container'), { url: scope.uploadUrl, init: function () { diff --git a/resources/assets/sass/_pages.scss b/resources/assets/sass/_pages.scss index e608295a8..1f79c38c8 100755 --- a/resources/assets/sass/_pages.scss +++ b/resources/assets/sass/_pages.scss @@ -228,21 +228,6 @@ padding-top: $-s; position: relative; } - button.pos { - position: absolute; - bottom: 0; - display: block; - width: 100%; - padding: $-s; - height: 45px; - border: 0; - margin: 0; - box-shadow: none; - border-radius: 0; - &:hover{ - box-shadow: none; - } - } .handle { user-select: none; cursor: move; diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php index e1481f500..e6b761c28 100644 --- a/resources/views/pages/form-toolbox.blade.php +++ b/resources/views/pages/form-toolbox.blade.php @@ -4,7 +4,9 @@
- + @if(userCan('file-create-all')) + + @endif
@@ -35,35 +37,60 @@
-
-

Attached Files

-
-

Upload some files to display on your page. This are visible in the page sidebar.

- + @if(userCan('file-create-all')) +
+

Attached Files

+
-
+
+

Upload some files to display on your page. This are visible in the page sidebar.

+ + +
+ +
+ + +
+
+ + +
+ + + + + + + + + + + + +
+
+ +
+
Edit File
+
+ + +
+
+ +
+
+ + +
+ + + +
-
- -
-
- - -
- - - - - - - - - - - -
-
+ @endif
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index a55eb224f..45957ac62 100644 --- a/routes/web.php +++ b/routes/web.php @@ -90,7 +90,9 @@ Route::group(['middleware' => 'auth'], function () { // 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');