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
{
$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');
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([
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
}
}
+
+ /**
+ * 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 and any empty folders the deletion leaves.
+ * Delete a File from the database and storage.
* @param File $file
*/
public function deleteFile(File $file)
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);
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
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 = {
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}`);
};
/**
$http.get(url).then(resp => {
$scope.files = resp.data;
currentOrder = resp.data.map(file => {return file.id}).join(':');
- });
+ }, checkError);
}
getFiles();
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
$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.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');
});
- $scope.fileName = $scope.fileLink = '';
};
+ /**
+ * 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);
+ }
+ }
+
}]);
};
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 () {
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;
<div class="tabs primary-background-light">
<span toolbox-toggle><i class="zmdi zmdi-caret-left-circle"></i></span>
<span tab-button="tags" title="Page Tags" class="active"><i class="zmdi zmdi-tag"></i></span>
- <span tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span>
+ @if(userCan('file-create-all'))
+ <span tab-button="files" title="Attachments"><i class="zmdi zmdi-attachment"></i></span>
+ @endif
</div>
<div tab-content="tags" ng-controller="PageTagController" page-id="{{ $page->id or 0 }}">
</div>
</div>
- <div tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
- <h4>Attached Files</h4>
- <div class="padded files">
- <p class="muted small">Upload some files to display on your page. This are visible in the page sidebar.</p>
- <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
+ @if(userCan('file-create-all'))
+ <div tab-content="files" ng-controller="PageAttachmentController" page-id="{{ $page->id or 0 }}">
+ <h4>Attached Files</h4>
+ <div class="padded files">
- <hr class="even">
+ <div id="file-list" ng-show="!editFile">
+ <p class="muted small">Upload some files to display on your page. This are visible in the page sidebar.</p>
+ <drop-zone upload-url="@{{getUploadUrl()}}" uploaded-to="@{{uploadedTo}}" event-success="uploadSuccess"></drop-zone>
- <div class="form-group">
- <label for="attachment-via-link">File Name</label>
- <input type="text" placeholder="File name" ng-model="fileName">
- </div>
- <div class="form-group">
- <label for="attachment-via-link">Link to file</label>
- <input type="text" placeholder="File url" ng-model="fileLink">
- </div>
- <button type="button" ng-click="attachLinkSubmit(fileName, fileLink)" class="button pos">Attach</button>
+ <hr class="even">
+ <div class="form-group">
+ <label for="attachment-via-link">File Name</label>
+ <input type="text" placeholder="File name" ng-model="file.name">
+ </div>
+ <div class="form-group">
+ <label for="attachment-via-link">Link to file</label>
+ <input type="text" placeholder="File url" ng-model="file.link">
+ </div>
+ <button type="button" ng-click="attachLinkSubmit(file)" class="button pos">Attach</button>
- <table class="no-style" tag-autosuggestions style="width: 100%;">
- <tbody ui-sortable="sortOptions" ng-model="files" >
- <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" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
- </tr>
- </tbody>
- </table>
+
+ <table class="no-style" tag-autosuggestions style="width: 100%;">
+ <tbody ui-sortable="sortOptions" ng-model="files" >
+ <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" ng-click="deleteFile(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-close"></i></td>
+ <td width="10" ng-click="startEdit(file)" class="text-center text-neg" style="padding: 0;"><i class="zmdi zmdi-edit"></i></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+
+ <div id="file-edit" ng-if="editFile">
+ <h5>Edit File</h5>
+ <div class="form-group">
+ <label for="attachment-name-edit">File Name</label>
+ <input type="text" id="attachment-name-edit" placeholder="File name" ng-model="editFile.name">
+ </div>
+ <hr class="even">
+ <drop-zone upload-url="@{{getUploadUrl(editFile)}}" uploaded-to="@{{uploadedTo}}" placeholder="Drop files or click here to upload and overwrite" event-success="uploadSuccessUpdate"></drop-zone>
+ <hr class="even">
+ <div class="form-group">
+ <label for="attachment-link-edit">Link to file</label>
+ <input type="text" id="attachment-link-edit" placeholder="File url" ng-model="editFile.link">
+ </div>
+
+ <button type="button" class="button" ng-click="cancelEdit()">Back</button>
+ <button type="button" class="button pos" ng-click="updateFile(editFile)">Save</button>
+ </div>
+
+ </div>
</div>
- </div>
+ @endif
</div>
\ No newline at end of file
// 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');