aboutsummaryrefslogtreecommitdiff
path: root/app/routes/FileSystem.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/routes/FileSystem.php')
-rw-r--r--app/routes/FileSystem.php684
1 files changed, 684 insertions, 0 deletions
diff --git a/app/routes/FileSystem.php b/app/routes/FileSystem.php
new file mode 100644
index 0000000..591acfd
--- /dev/null
+++ b/app/routes/FileSystem.php
@@ -0,0 +1,684 @@
+<?php
+namespace RESTAPI\Routes;
+
+/**
+ * This class implements REST routes for the new Stud.IP file system.
+ *
+ * @author Moritz Strohm <strohm@data-quest.de>
+ * @license GNU General Public License Version 2 or later
+ * @deprecated Since Stud.IP 5.0. Will be removed in Stud.IP 5.2.
+ *
+ * Partially based upon the Files.php source code from Jan-Hendrik Willms
+ * (tleilax+studip@gmail.com) and mluzena@uos.de which is also
+ * licensed under the terms of the GNU General Public License Version 2
+ * or later.
+ */
+
+class FileSystem extends \RESTAPI\RouteMap
+{
+ // FILE REFERENCE AND FILE ROUTES:
+
+ /**
+ * Get a file reference object (metadata)
+ * @get /file/:file_ref_id
+ */
+ public function getFileRef($file_ref_id)
+ {
+ return $this->filerefToJSON(
+ $this->requireFileRef($file_ref_id),
+ (bool) \Request::int('extended')
+ );
+ }
+
+ /**
+ * Get the data of a file by the ID of an associated FileRef object
+ *
+ * @get /file/:file_ref_id/download
+ */
+ public function getFileRefData($file_ref_id)
+ {
+ $file_ref = $this->requireFileRef($file_ref_id);
+
+ // check if the current user has the permissions to read this file reference:
+ $user = \User::findCurrent();
+ if (!$file_ref->folder->getTypedFolder()->isFileDownloadable($file_ref_id, $user->id)) {
+ $this->error(403, "You may not download the file reference with the id {$file_ref_id}");
+ }
+
+ // check if file exists:
+ if (!$file_ref->file) {
+ $this->error(500, 'File reference has no associated file object!');
+ }
+
+ $data_path = $file_ref->file->getPath();
+ if (!file_exists($data_path)) {
+ $this->error(500, "File was not found in the operating system's file system!");
+ }
+
+ $this->lastModified($file_ref->file->chdate);
+ $this->sendFile($data_path, ['filename' => $file_ref->name]);
+ }
+
+ /**
+ * Update file data using a FileReference to it.
+ *
+ * @post /file/:file_ref_id/update
+ */
+ public function updateFileData($file_ref_id)
+ {
+ // We only update the first file:
+ $uploaded_file = array_shift($this->data['_FILES']);
+
+ // FileManager::updateFileRef handles the whole file upload
+ // and does all the necessary security checks:
+ $result = \FileManager::updateFileRef(
+ $this->requireFileRef($file_ref_id),
+ \User::findCurrent(),
+ $uploaded_file,
+ true,
+ false
+ );
+
+ if (!$result instanceof \FileRef) {
+ $this->error(500, 'Error while updating a file reference: ' . implode(' ', $result));
+ }
+
+ return $this->filerefToJSON($result);
+ }
+
+ /**
+ * Edit a file reference.
+ *
+ * @put /file/:file_ref_id
+ */
+ public function editFileRef($file_ref_id)
+ {
+ $result = \FileManager::editFileRef(
+ $this->requireFileRef($file_ref_id),
+ \User::findCurrent(),
+ $this->data['name'],
+ $this->data['description'],
+ $this->data['content_term_of_use_id'],
+ $this->data['license']
+ );
+
+ if (!$result instanceof \FileRef) {
+ $this->error(500, 'Error while editing a file reference: ' . implode(' ', $result));
+ }
+
+ return $this->filerefToJSON($result);
+ }
+
+ /**
+ * Copies a file reference.
+ *
+ * @post /file/:file_ref_id/copy/:destination_folder_id
+ */
+ public function copyFileRef($file_ref_id, $destination_folder_id)
+ {
+ $result = \FileManager::copyFileRef(
+ $this->requireFileRef($file_ref_id),
+ $this->requireFolder($destination_folder_id)->getTypedFolder(),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FileRef) {
+ $this->error(500, 'Error while copying a file reference: ' . implode(' ', $result));
+ }
+
+ return $this->filerefToJSON($result);
+ }
+
+ /**
+ * Moves a file reference.
+ *
+ * @post /file/:file_ref_id/move/:destination_folder_id
+ */
+ public function moveFileRef($file_ref_id, $destination_folder_id)
+ {
+ $result = \FileManager::moveFileRef(
+ $this->requireFileRef($file_ref_id),
+ $this->requireFolder($destination_folder_id)->getTypedFolder(),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FileRef) {
+ $this->error(500, 'Error while moving a file reference: ' . implode(' ', $result));
+ }
+
+ return $this->filerefToJSON($result);
+ }
+
+ /**
+ * Deletes a file reference.
+ *
+ * @delete /file/:file_ref_id
+ */
+ public function deleteFileRef($file_ref_id)
+ {
+ $result = \FileManager::deleteFileRef(
+ $this->requireFileRef($file_ref_id),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FileRef) {
+ $this->error(500, 'Error while deleting a file reference: ' . implode(' ', $result));
+ }
+
+ $this->halt(200);
+ }
+
+ /**
+ * Upload file to given folder.
+ * file data has to be attached as multipart/form-data
+ *
+ * @post /file/:folder_id
+ */
+ public function uploadFile($folder_id)
+ {
+ $typed_folder = $this->requireFolder($folder_id)->getTypedFolder();
+ if (isset($this->data['_FILES'])) {
+ $file_data = array_map(function ($a) {
+ return is_array($a) ? $a : [$a];
+ }, array_shift($this->data['_FILES']));
+ }
+ if (is_array($file_data)) {
+ $validated_files = \FileManager::handleFileUpload(
+ $file_data,
+ $typed_folder,
+ $this->requireUser()->id
+ );
+
+ if (count($validated_files['error']) > 0) {
+ $this->error(500, 'Error while uploading files: ' . implode(' ', $validated_files['error']));
+ }
+
+ $uploaded_files = \SimpleCollection::createFromArray($validated_files['files']);
+ $default_license = \ContentTermsOfUse::findDefault();
+ $uploaded_files->setValue('content_terms_of_use_id', $default_license->id);
+ $uploaded_files->store();
+ if (count($uploaded_files) === 1) {
+ $result = $this->filerefToJSON($uploaded_files->first());
+ } else {
+ $result = $uploaded_files->map(function ($f) {
+ return $this->filerefToJSON($f);
+ });
+ }
+ $this->halt(201, [], $result);
+ } else {
+ $this->error(400, 'No files found in request.');
+ }
+ }
+
+ // FOLDER ROUTES:
+
+ /**
+ * Returns a list of defined folder types, separated by range type.
+ * @get /studip/file_system/folder_types
+ */
+ public function getDefinedFolderTypes()
+ {
+ return \FileManager::getFolderTypes();
+ }
+
+ /**
+ * Get a folder object with its file references, subdirectories and the permissions for the user who has made the API call.
+ * @get /folder/:folder_id
+ */
+ public function getFolder($folder_id)
+ {
+ return $this->folderToJSON(
+ $this->requireFolder($folder_id),
+ true
+ );
+ }
+
+ /**
+ * Creates a new folder inside of another folder and returns the new object on success.
+ * @post /folder/:parent_folder_id/new_folder
+ */
+ public function createNewFolder($parent_folder_id)
+ {
+ $user = \User::findCurrent();
+ $parent = $this->requireTypedFolder($parent_folder_id);
+
+ if (!$parent->isWritable($user->id)) {
+ $this->error(403, 'You are not permitted to create a subfolder in the parent folder!');
+ }
+
+ $result = \FileManager::createSubFolder(
+ $parent,
+ $user,
+ 'StandardFolder', //to be extended
+ $this->data['name'],
+ $this->data['description']
+ );
+
+ if (!$result instanceof \FolderType) {
+ $this->error(500, 'Error while creating a folder: ' . implode(' ', $result));
+ }
+
+ return $this->folderToJSON(
+ $this->requireFolder($result->getId())
+ );
+ }
+
+ /**
+ * Get a list with all FileRef objects of a folder.
+ * @get /folder/:folder_id/files
+ */
+ public function getFileRefsOfFolder($folder_id)
+ {
+ $folder = $this->requireFolder($folder_id);
+
+ $query = "folder_id = :folder_id ORDER BY name ASC";
+ $parameters[':folder_id'] = $folder->id;
+
+ if ($limit || $offset) {
+ $query .= " LIMIT :limit OFFSET :offset";
+ $parameters[':limit'] = $limit;
+ $parameters[':offset'] = $offset;
+ }
+
+ $file_refs = \FileRef::findAndMapBySql(function (\FileRef $ref) {
+ return $this->filerefToJSON($ref);
+ }, $query, $parameters);
+
+ return $this->paginated(
+ $file_refs,
+ \FileRef::countByFolder_id($folder->id),
+ ['folder_id' => $folder->id]
+ );
+ }
+
+
+ /**
+ * Get a list with all FileRef objects of a folder.
+ * @get /folder/:folder_id/subfolders
+ */
+ public function getSubfoldersOfFolder($folder_id)
+ {
+ $user = $this->requireUser();
+ $folder = $this->requireFolder($folder_id);
+
+ $query = "parent_id = :parent_id ORDER BY name ASC";
+ $parameters = [':parent_id' => $folder->id];
+
+ if ($this->limit || $this->offset) {
+ $query .= " LIMIT :limit OFFSET :offset";
+ $parameters[':limit'] = $this->limit;
+ $parameters[':offset'] = $this->offset;
+ }
+
+ $subfolders = \Folder::findAndMapBySql(function (\Folder $subfolder) use ($user) {
+ $type = $subfolder->getTypedFolder();
+ if (!$type || !$type->isVisible($user->id)) {
+ return false;
+ }
+ return $this->folderToJSON($subfolder);
+ }, $query, $parameters);
+
+ return $this->paginated(
+ array_filter($subfolders),
+ \Folder::countByParent_id($folder_id),
+ ['folder_id' => $folder_id]
+ );
+ }
+
+ /**
+ * Get a list with permissions the current user has for a folder.
+ * @get /folder/:folder_id/permissions
+ */
+ public function getFolderPermissions($folder_id)
+ {
+ $user = $this->requireUser();
+ $folder = $this->requireFolder($folder_id);
+
+ // read permissions of the user and return them:
+ return array_merge([
+ 'folder_id' => $folder->id,
+ 'user_id' => $user->id,
+ ], $this->folderPermissionsToJSON($folder));
+ }
+
+ /**
+ * Allows editing the name or the description (or both) of a folder.
+ *
+ * @put /folder/:folder_id
+ */
+ public function editFolder($folder_id)
+ {
+ if (isset($this->data['name']) && !$this->data['name']) {
+ $this->error(400, "The name for the folder with the id {$folder_id} must not be empty!");
+ }
+
+ $user = $this->requireUser();
+ $typed_folder = $this->requireTypedFolder($folder_id);
+
+ if (!$typed_folder->isEditable($user->id)) {
+ $this->error(403, "You may not edit the folder with id {$folder_id}!");
+ }
+
+ if (!$typed_folder instanceof \StandardFolder) {
+ $this->error(501, "Editing is only allowed for folders of type StandardFolder for now!");
+ }
+
+ if ($this->data['name']) {
+ $typed_folder->name = $this->data['name'];
+ }
+ if (isset($this->data['description'])) {
+ $typed_folder->description = $this->data['description'] ?: '';
+ }
+
+ if (!$typed_folder->store()) {
+ $this->error(500, "Could not store folder with id {$folder_id}!");
+ }
+
+ return $this->folderToJSON(
+ $this->requireFolder($folder_id)
+ );
+ }
+
+ /**
+ * Copies a folder into another folder.
+ *
+ * @post /folder/:folder_id/copy/:destination_folder_id
+ */
+ public function copyFolder($folder_id, $destination_folder_id)
+ {
+ $result = \FileManager::copyFolder(
+ $this->requireTypedFolder($folder_id),
+ $this->requireTypedFolder($destination_folder_id),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FolderType) {
+ $this->error(500, 'Error while copying a folder: ' . implode(' ', $result));
+ }
+
+ return $this->folderToJSON(
+ $this->requireFolder($result->getId())
+ );
+ }
+
+
+ /**
+ * Move a folder into another folder.
+ * @post /folder/:folder_id/move/:destination_folder_id
+ */
+ public function moveFolder($folder_id, $destination_folder_id)
+ {
+ $result = \FileManager::moveFolder(
+ $this->requireTypedFolder($folder_id),
+ $this->requireTypedFolder($destination_folder_id),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FolderType) {
+ $this->error(500, 'Error while moving a folder: ' . implode(' ', $result));
+ }
+
+ return $this->folderToJSON(
+ $this->requireFolder($folder_id)
+ );
+ }
+
+
+ /**
+ * Deletes a folder.
+ *
+ * @delete /folder/:folder_id
+ */
+ public function deleteFolder($folder_id)
+ {
+ $result = \FileManager::deleteFolder(
+ $this->requireTypedFolder($folder_id),
+ \User::findCurrent()
+ );
+
+ if (!$result instanceof \FolderType) {
+ $this->error(500, 'Error while deleting a folder: ' . implode(' ', $result));
+ }
+
+ $this->halt(200);
+ }
+
+ // RELATED OBJECT ROUTES:
+
+ /**
+ * Get a collection of all ContentTermsOfUse objects
+ *
+ * @get /studip/content_terms_of_use_list
+ */
+ public function getContentTermsOfUseList()
+ {
+ $objects = \ContentTermsOfUse::findBySql(
+ '1 ORDER BY name ASC LIMIT :limit OFFSET :offset',
+ ['limit' => $this->limit, 'offset' => $this->offset]
+ );
+
+ return $this->paginated(
+ array_map([$this, 'termsOfUseToJSON'], $objects),
+ \ContentTermsOfUse::countBySql('1')
+ );
+ }
+
+ // UTILITY METHODS
+
+ /**
+ * Requires a valid user object.
+ * @return User object
+ */
+ private function requireUser()
+ {
+ return \User::findCurrent();
+ }
+
+ /**
+ * Requires a valid file reference object
+ * @param mixed $id_or_object Either a file reference id or object
+ * @return FileRef object
+ */
+ private function requireFileRef($id_or_object)
+ {
+ if ($id_or_object instanceof \FileRef) {
+ $file_ref = $id_or_object;
+ } else {
+ //check if the file_id references a file reference object:
+ $file_ref = \FileRef::find($id_or_object);
+ if (!$file_ref) {
+ $this->notFound("File reference with id {$id_or_object} not found!");
+ }
+ }
+
+ // check if the file reference is placed inside a folder.
+ // (must be present to check for permissions)
+ if (!$file_ref->folder) {
+ $this->error(500, "File reference with id {$file_ref->id} has no folder!");
+ }
+
+ $typed_folder = $file_ref->folder->getTypedFolder();
+ if (!$typed_folder) {
+ $this->error(500, "The folder of file reference with id {$file_ref->id} has no folder type!");
+ }
+
+ //check if the current user has the permissions to read this file reference:
+ if (!$typed_folder->isReadable($this->requireUser()->id)) {
+ $this->error(403, "You are not permitted to read the file reference with id {$file_ref->id}!");
+ }
+
+ return $file_ref;
+ }
+
+ /**
+ * Converts a file reference object to JSON.
+ * @param FileRef $ref File reference object
+ * @param boolean $extended Extended output? (includes folder, owner and terms of use)
+ * @return array representation for json encoding
+ */
+ private function filerefToJSON(\FileRef $ref, $extended = false)
+ {
+ $user = $this->requireUser();
+ $typed_folder = $ref->folder->getTypedFolder();
+ $filetype = $ref->getFileType();
+
+ $result = array_merge($ref->toRawArray(), [
+ 'size' => (int) $ref->file->size,
+ 'mime_type' => $ref->file->mime_type,
+ 'storage' => $ref->file->filetype === "URLFile" ? "url" : "disk",
+
+ 'is_readable' => $typed_folder->isReadable($user->id),
+ 'is_downloadable' => $filetype->isDownloadable($user->id),
+ 'is_editable' => $filetype->isEditable($user->id),
+ 'is_writable' => $filetype->isWritable($user->id),
+ ]);
+
+ $result['downloads'] = (int) $result['downloads'];
+ $result['mkdate'] = (int) $result['mkdate'];
+ $result['chdate'] = (int) $result['chdate'];
+
+ if ($result['storage'] === 'url') {
+ $result['url'] = $ref->getFileType()->getDownloadURL();
+ }
+
+ if ($extended) {
+ //folder does exist (since we checked for its existence above)
+ $result['folder'] = $this->folderToJSON($ref->folder);
+
+ if ($ref->owner) {
+ $result['owner'] = User::getMiniUser($this, $ref->owner);
+ }
+
+ //$result['license'] = $file_ref->license; //to be activated when licenses are defined
+
+ if ($ref->terms_of_use) {
+ $result['terms_of_use'] = $this->termsOfUseToJSON($ref->terms_of_use);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Requires a valid folder object
+ * @param mixed $id_or_object Either a folder id or object
+ * @return Folder object
+ */
+ private function requireFolder($id_or_object)
+ {
+ if ($id_or_object instanceof \Folder) {
+ $folder = $id_or_object;
+ } else {
+ $folder = \Folder::find($id_or_object);
+ if (!$folder) {
+ $this->notFound("Folder with id {$id_or_object} not found!");
+ }
+ }
+
+ $typed_folder = $folder->getTypedFolder();
+ if (!$typed_folder) {
+ $this->error(500, "Cannot find folder type of folder with id {$folder->id}!");
+ return;
+ }
+
+ if (!$typed_folder->isReadable($this->requireUser()->id)) {
+ $this->error(403, "You are not allowed to read the contents of the folder with the id {$folder->id}!");
+ }
+
+ return $folder;
+ }
+
+ /**
+ * Requires a valid typed folder object
+ * @param mixed $id_or_object Either a folder id or object
+ * @return FolderType instance
+ */
+ private function requireTypedFolder($id_or_object)
+ {
+ return $this->requireFolder($id_or_object)->getTypedFolder();
+ }
+
+ /**
+ * Converts a given folder to JSON.
+ * @param Folder $folder Folder object
+ * @param boolean $extended Extended output? (includes subfolders and file references)
+ * @return array representation for json encoding
+ */
+ private function folderToJSON(\Folder $folder, $extended = false)
+ {
+ $result = $this->folderPermissionsToJSON($folder);
+
+ if ($result['is_readable']) {
+ $result = array_merge($folder->toRawArray(), $result);
+
+ $result['mkdate'] = (int) $result['mkdate'];
+ $result['chdate'] = (int) $result['chdate'];
+
+ //The field "data_content" must be handled differently
+ //than the other fields since it contains JSON data.
+ $data_content = json_decode($folder->data_content);
+ $result['data_content'] = $data_content;
+
+ if ($extended) {
+ $user = $this->requireUser();
+
+ $result['subfolders'] = [];
+ foreach ($folder->subfolders as $subfolder) {
+ if (!$subfolder->getTypedFolder()->isVisible($user->id)) {
+ continue;
+ }
+ $result['subfolders'][] = $this->folderToJSON($subfolder);
+ }
+
+ $result['file_refs'] = [];
+ foreach ($folder->getTypedFolder()->getFiles() as $file) {
+ if (method_exists($file,"getFileRef")) {
+ $result['file_refs'][] = $this->filerefToJSON(
+ $file->getFileRef()
+ );
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Converts permissions of a folder to JSON.
+ * @param Folder $folder Folder object
+ * @param User $user User object to check permissions against
+ * @return array representation for json encoding
+ */
+ private function folderPermissionsToJSON(\Folder $folder)
+ {
+ $user = $this->requireUser();
+ $type = $folder = $folder->getTypedFolder();
+ if (!$type) {
+ $this->error(500, 'Folder type not found!');
+ }
+
+ return [
+ 'is_visible' => $type->isVisible($user->id),
+ 'is_readable' => $type->isReadable($user->id),
+ 'is_writable' => $type->isWritable($user->id),
+ ];
+ }
+
+ /**
+ * Converts a terms of use object to JSON.
+ * @param ContentTermsOfUse $object Object
+ * @return array representation for json encoding
+ */
+ private function termsOfUseToJSON(\ContentTermsOfUse $object)
+ {
+ $result = $object->toRawArray();
+
+ $result['is_default'] = (bool) $result['is_default'];
+
+ $result['mkdate'] = (int) $result['mkdate'];
+ $result['chdate'] = (int) $result['chdate'];
+
+ return $result;
+ }
+}