diff --git a/docs/fulltextsearch.md b/docs/fulltextsearch.md new file mode 100644 index 0000000..cedd804 --- /dev/null +++ b/docs/fulltextsearch.md @@ -0,0 +1,63 @@ +# Full Text Search NextCloud App(s) + +## Overview + +This document provides some information about the NextCloud Full Text Search App(s) and a step-by-step plan how to set this up on your local machine with the use of ElasticSearch as search platform. + +## Local Setup Steps + +1. Open a command-line interface (CLI), such as: + - **Windows:** Command Prompt (`cmd`), PowerShell, or Windows Terminal. + - **Linux/macOS:** Terminal. +2. Navigate to your local Nextcloud repository (where a docker-compose.yml file is present): + ```sh + cd {route to your local NC repo} + ``` +3. Start the necessary Docker containers: + ```sh + docker-compose up nextcloud proxy elasticsearch + ``` +4. In the Nextcloud front-end, go to **NC Apps > Search** and install the following three apps: + - **Full text search Files** + - **Full text search Elastic** + - **Full text search** +5. Under **Administrator settings**, go to **Full text search** in the sidebar. +6. Under **General**, configure the following: + - **Search Platform:** Set to **"Elasticsearch"**. + - **Navigation Icon:** Check this option. +7. Under **Elastic Search**, set the following: + - **Address of the Servlet:** + ``` + http://elastic:elastic@elasticsearch:9200 + ``` + - **Index:** + ``` + my_index + ``` + - **[Advanced] Analyzer tokenizer:** + ``` + standard + ``` +8. Under **Files**, configure the following + - **Check all checkboxes:** + - Local Files + - Group Folders + - Extract PDF + - Extract Office & Open Files + - **Maximum file size:** Set your prefered maximum file size (at least **64** is recommended). +9. Add some files to Nextcloud in the Files tab of NextCloud. +10. Run the indexing command in the `master-nextcloud-1` container in Docker Desktop: + ```sh + sudo -u www-data php ./occ fulltextsearch:index + ``` +11. Open the **search app** and search for files based on the text inside them. + +## More Information + +If you need more details or troubleshooting help, you can refer to the following resources: + +- [Nextcloud Full Text Search Wiki - Basic Installation](https://github.com/nextcloud/fulltextsearch/wiki/Basic-Installation) +- [Nextcloud Docker Development - Full Text Search](https://juliusknorr.github.io/nextcloud-docker-dev/services/more/#fulltextsearch) +- [YouTube Guide on Full Text Search for Nextcloud](https://www.youtube.com/watch?v=yPZkrzgue5c) + +These resources provide in-depth explanations, configurations, and troubleshooting tips. diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000..b203e82 Binary files /dev/null and b/docs/image.png differ diff --git a/lib/Db/ObjectEntity.php b/lib/Db/ObjectEntity.php index 3d5e8ab..7fc839e 100644 --- a/lib/Db/ObjectEntity.php +++ b/lib/Db/ObjectEntity.php @@ -7,6 +7,12 @@ use OCP\AppFramework\Db\Entity; use OCP\IUserSession; +/** + * Entity class representing an object in the OpenRegister system + * + * This class handles storage and manipulation of objects including their metadata, + * locking mechanisms, and serialization for API responses. + */ class ObjectEntity extends Entity implements JsonSerializable { protected ?string $uuid = null; @@ -25,6 +31,9 @@ class ObjectEntity extends Entity implements JsonSerializable protected ?DateTime $created = null; protected ?string $folder = null; // The folder path where this object is stored + /** + * Initialize the entity and define field types + */ public function __construct() { $this->addType(fieldName:'uuid', type: 'string'); $this->addType(fieldName:'uri', type: 'string'); @@ -43,6 +52,11 @@ public function __construct() { $this->addType(fieldName:'folder', type: 'string'); } + /** + * Get array of field names that are JSON type + * + * @return array List of field names that are JSON type + */ public function getJsonFields(): array { return array_keys( @@ -52,6 +66,12 @@ public function getJsonFields(): array ); } + /** + * Hydrate the entity from an array of data + * + * @param array $object Array of data to hydrate the entity with + * @return self Returns the hydrated entity + */ public function hydrate(array $object): self { $jsonFields = $this->getJsonFields(); @@ -76,12 +96,30 @@ public function hydrate(array $object): self return $this; } - + /** + * Serialize the entity to JSON format + * + * Creates a metadata array containing object properties except sensitive fields. + * Filters out 'object', 'textRepresentation' and 'authorization' fields and + * stores remaining properties under '@self' key for API responses. + * + * @return array Serialized object data + */ public function jsonSerialize(): array { - return $this->object; + $metadata = [ + '@self' => array_filter($this->getObjectArray(), function($key) { + return in_array($key, ['object', 'textRepresentation', 'authorization']) === false; + }, ARRAY_FILTER_USE_KEY) + ]; + return array_merge($metadata, $this->object); } + /** + * Get array representation of all object properties + * + * @return array Array containing all object properties + */ public function getObjectArray(): array { return [ diff --git a/lib/Service/FileService.php b/lib/Service/FileService.php index edd20d3..8590f3e 100644 --- a/lib/Service/FileService.php +++ b/lib/Service/FileService.php @@ -5,11 +5,11 @@ use DateTime; use Exception; use OCA\OpenRegister\Db\ObjectEntity; +use OCA\OpenRegister\Db\ObjectEntityMapper; use OCA\OpenRegister\Db\Register; use OCA\OpenRegister\Db\RegisterMapper; use OCA\OpenRegister\Db\Schema; use OCA\OpenRegister\Db\SchemaMapper; -use OCP\AppFramework\Http\JSONResponse; use OCP\Files\File; use OCP\Files\GenericFileException; use OCP\Files\InvalidPathException; @@ -19,13 +19,16 @@ use OCP\Files\NotPermittedException; use OCP\IConfig; use OCP\IGroupManager; -use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUserSession; use OCP\Lock\LockedException; use OCP\Share\IManager; use OCP\Share\IShare; use Psr\Log\LoggerInterface; +use OCP\IUser; +use OCP\IUserManager; +use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; /** * Service for handling file operations in OpenRegister. @@ -38,7 +41,8 @@ class FileService { const ROOT_FOLDER = 'Open Registers'; const APP_GROUP = 'openregister'; - + const APP_USER = 'OpenRegister'; + const FILE_TAG_TYPE = 'file'; /** * Constructor for FileService * @@ -57,6 +61,10 @@ public function __construct( private readonly RegisterMapper $registerMapper, private readonly SchemaMapper $schemaMapper, private readonly IGroupManager $groupManager, + private readonly IUserManager $userManager, + private readonly ObjectEntityMapper $objectEntityMapper, + private readonly ISystemTagManager $systemTagManager, + private readonly ISystemTagObjectMapper $systemTagMapper, ) { } @@ -78,6 +86,7 @@ public function createRegisterFolder(Register|int $register): string $registerFolderName = $this->getRegisterFolderName($register); // @todo maybe we want to use ShareLink here for register->folder as well? $register->setFolder($this::ROOT_FOLDER . "/$registerFolderName"); + $this->registerMapper->update($register); $folderPath = $this::ROOT_FOLDER . "/$registerFolderName"; $this->createFolder(folderPath: $folderPath); @@ -96,7 +105,7 @@ private function getRegisterFolderName(Register $register): string { $title = $register->getTitle(); - if (str_ends_with(strtolower($title), 'register')) { + if (str_ends_with(haystack: strtolower(rtrim($title)), needle: 'register')) { return $title; } @@ -126,6 +135,7 @@ public function createSchemaFolder(Register|int $register, Schema|int $schema): $registerFolderName = $this->getRegisterFolderName($register); // @todo maybe we want to use ShareLink here for register->folder as well? $register->setFolder($this::ROOT_FOLDER . "/$registerFolderName"); + $this->registerMapper->update($register); $schemaFolderName = $this->getSchemaFolderName($schema); @@ -200,12 +210,14 @@ public function getObjectFolder( Schema|int|null $schema = null ): ?Node { - if($objectEntity->getFolder() === null) { + if (empty($objectEntity->getFolder()) === true) { $folderPath = $this->getObjectFolderPath( objectEntity: $objectEntity, register: $register, schema: $schema ); + $objectEntity->setFolder($folderPath); + $this->objectEntityMapper->update($objectEntity); } else { $folderPath = $objectEntity->getFolder(); } @@ -268,6 +280,7 @@ private function getObjectFolderPath( $registerFolderName = $this->getRegisterFolderName($register); // @todo maybe we want to use ShareLink here for register->folder as well? $register->setFolder($this::ROOT_FOLDER . "/$registerFolderName"); + $this->registerMapper->update($register); $schemaFolderName = $this->getSchemaFolderName($schema); $objectFolderName = $this->getObjectFolderName($objectEntity); @@ -332,29 +345,114 @@ private function getCurrentDomain(): string return $baseUrl; } + /** + * Gets or creates the OpenCatalogi user for file operations + * + * @return IUser The OpenCatalogi user + * @throws Exception If OpenCatalogi user cannot be created + */ + private function getUser(): IUser + { + $openCatalogiUser = $this->userManager->get(self::APP_USER); + + if (!$openCatalogiUser) { + // Create OpenCatalogi user if it doesn't exist + $password = bin2hex(random_bytes(16)); // Generate random password + $openCatalogiUser = $this->userManager->createUser(self::APP_USER, $password); + + if (!$openCatalogiUser) { + throw new \Exception('Failed to create OpenCatalogi user account.'); + } + + // Add user to OpenCatalogi group + $group = $this->groupManager->get(self::APP_GROUP); + if (!$group) { + $group = $this->groupManager->createGroup(self::APP_GROUP); + } + $group->addUser($openCatalogiUser); + } + + return $openCatalogiUser; + } /** * Gets a NextCloud Node object for the given file or folder path. - * - * @param string $path A path to a file or folder in NextCloud. - * - * @return Node|null The Node object found for a file or folder. Or null if not found. - * @throws NotPermittedException When not allowed to get the user folder. */ private function getNode(string $path): ?Node { - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); - try { - return $node = $userFolder->get(path: $path); - } catch (NotFoundException $e) { + $userFolder = $this->rootFolder->getUserFolder($this->getUser()->getUID()); + return $userFolder->get(path: $path); + } catch (NotFoundException|NotPermittedException $e) { $this->logger->error(message: $e->getMessage()); return null; } } + /** + * Formats an array of Node files into an array of metadata arrays. + * + * See https://nextcloud-server.netlify.app/classes/ocp-files-file for the Nextcloud documentation on the File class + * See https://nextcloud-server.netlify.app/classes/ocp-files-node for the Nextcloud documentation on the Node superclass + * + * @param Node[] $files Array of Node files to format + * + * @return array Array of formatted file metadata arrays + * @throws InvalidPathException + * @throws NotFoundException + */ + public function formatFiles(array $files): array + { + $formattedFiles = []; + + foreach($files as $file) { + // IShare documentation see https://nextcloud-server.netlify.app/classes/ocp-share-ishare + $shares = $this->findShares($file); + + $formattedFile = [ + 'id' => $file->getId(), + 'path' => $file->getPath(), + 'title' => $file->getName(), + 'accessUrl' => count($shares) > 0 ? $this->getShareLink($shares[0]) : null, + 'downloadUrl' => count($shares) > 0 ? $this->getShareLink($shares[0]).'/download' : null, + 'type' => $file->getMimetype(), + 'extension' => $file->getExtension(), + 'size' => $file->getSize(), + 'hash' => $file->getEtag(), + 'published' => (new DateTime())->setTimestamp($file->getCreationTime())->format('c'), + 'modified' => (new DateTime())->setTimestamp($file->getUploadTime())->format('c'), + 'labels' => $this->getFileTags(fileId: $file->getId()) + ]; + + $formattedFiles[] = $formattedFile; + } + + return $formattedFiles; + } + + /** + * Get the tags associated with a file. + * + * @param string $fileId The ID of the file. + * + * @return array The list of tags associated with the file. + */ + private function getFileTags(string $fileId): array + { + $tagIds = $this->systemTagMapper->getTagIdsForObjects(objIds: [$fileId], objectType: $this::FILE_TAG_TYPE); + if (isset($tagIds[$fileId]) === false || empty($tagIds[$fileId]) === true) { + return []; + } + + $tags = $this->systemTagManager->getTagsByIds(tagIds: $tagIds[$fileId]); + + $tagNames = array_map(static function ($tag) { + return $tag->getName(); + }, $tags); + + return array_values($tagNames); + } + /** * @param Node $file * @param int $shareType @@ -380,15 +478,12 @@ public function findShares(Node $file, int $shareType = 3): array public function findShare(string $path, ?int $shareType = 3): ?IShare { $path = trim(string: $path, characters: '/'); + $userId = $this->getUser()->getUID(); - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userId = $currentUser ? $currentUser->getUID() : 'Guest'; try { $userFolder = $this->rootFolder->getUserFolder(userId: $userId); } catch (NotPermittedException) { $this->logger->error("Can't find share for $path because user (folder) for user $userId couldn't be found"); - return null; } @@ -411,36 +506,6 @@ public function findShare(string $path, ?int $shareType = 3): ?IShare return null; } - /** - * Share a file or folder with a user group in Nextcloud. - * - * @param int $nodeId The file or folder to share. - * @param string $nodeType 'file' or 'folder', the type of node. - * @param string $target The target folder to share the node in. - * @param int $permissions Permissions the group members will have in the folder. - * @param string $groupId The id of the group to share the folder with. - * - * @return IShare The resulting share - * @throws Exception - */ - private function shareWithGroup(int $nodeId, string $nodeType, string $target, int $permissions, string $groupId): IShare - { - $share = $this->shareManager->newShare(); - $share->setTarget(target: '/'. $target); - $share->setNodeId(fileId:$nodeId); - $share->setNodeType(type:$nodeType); - - $share->setShareType(shareType: 1); - $share->setPermissions(permissions: $permissions); - $share->setSharedBy(sharedBy:$this->userSession->getUser()->getUID()); - $share->setShareOwner(shareOwner:$this->userSession->getUser()->getUID()); - $share->setShareTime(shareTime: new DateTime()); - $share->setSharedWith(sharedWith: $groupId); - $share->setStatus(status: $share::STATUS_ACCEPTED); - - return $this->shareManager->createShare($share); - } - /** * Creates a IShare object using the $shareData array data. * @@ -449,20 +514,30 @@ private function shareWithGroup(int $nodeId, string $nodeType, string $target, i * @return IShare The Created IShare object. * @throws Exception */ - private function createShare(array $shareData) :IShare + private function createShare(array $shareData): IShare { + $userId = $this->getUser()->getUID(); + // Create a new share $share = $this->shareManager->newShare(); $share->setTarget(target: '/'.$shareData['path']); - $share->setNodeId(fileId: $shareData['file']->getId()); - $share->setNodeType(type: 'file'); + if (empty($shareData['file']) === false) { + $share->setNodeId(fileId: $shareData['file']->getId()); + } + if (empty($shareData['nodeId']) === false) { + $share->setNodeId(fileId: $shareData['nodeId']); + } + $share->setNodeType(type: $shareData['nodeType'] ?? 'file'); $share->setShareType(shareType: $shareData['shareType']); if ($shareData['permissions'] !== null) { $share->setPermissions(permissions: $shareData['permissions']); } - $share->setSharedBy(sharedBy: $shareData['userId']); - $share->setShareOwner(shareOwner: $shareData['userId']); + $share->setSharedBy(sharedBy: $userId); + $share->setShareOwner(shareOwner: $userId); $share->setShareTime(shareTime: new DateTime()); + if (empty($shareData['sharedWith']) === false) { + $share->setSharedWith(sharedWith: $shareData['sharedWith']); + } $share->setStatus(status: $share::STATUS_ACCEPTED); return $this->shareManager->createShare(share: $share); @@ -489,14 +564,12 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss } } - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userId = $currentUser ? $currentUser->getUID() : 'Guest'; + $userId = $this->getUser()->getUID(); + try { $userFolder = $this->rootFolder->getUserFolder(userId: $userId); } catch (NotPermittedException) { $this->logger->error("Can't create share link for $path because user (folder) for user $userId couldn't be found"); - return "User (folder) couldn't be found"; } @@ -536,33 +609,33 @@ public function createShareLink(string $path, ?int $shareType = 3, ?int $permiss */ public function createFolder(string $folderPath): bool { - $folderPath = trim(string: $folderPath, characters: '/'); // Get the current user. - $currentUser = $this->userSession->getUser(); - $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); + $userFolder = $this->rootFolder->getUserFolder($this->getUser()->getUID()); // Check if folder exists and if not create it. try { - // First, check if the root folder exists, and if not, create it and share it with the openregister group. - try { - $userFolder->get(self::ROOT_FOLDER); - } catch(NotFoundException $exception) { - $rootFolder = $userFolder->newFolder(self::ROOT_FOLDER); - - if($this->groupManager->groupExists(self::APP_GROUP) === false) { - $this->groupManager->createGroup(self::APP_GROUP); - } - - $this->shareWithGroup( - nodeId: $rootFolder->getId(), - nodeType: $rootFolder->getType() === 'file' ? $rootFolder->getType() : 'folder', - target: self::ROOT_FOLDER, - permissions: 31, - groupId: self::APP_GROUP - ); - } + // First, check if the root folder exists, and if not, create it and share it with the openregister group. + try { + $userFolder->get(self::ROOT_FOLDER); + } catch(NotFoundException $exception) { + $rootFolder = $userFolder->newFolder(self::ROOT_FOLDER); + + if ($this->groupManager->groupExists(self::APP_GROUP) === false) { + $this->groupManager->createGroup(self::APP_GROUP); + } + + $this->createShare([ + 'path' => self::ROOT_FOLDER, + 'nodeId' => $rootFolder->getId(), + 'nodeType' => $rootFolder->getType() === 'file' ? $rootFolder->getType() : 'folder', + 'shareType' => 1, + 'permissions' => 31, + 'userId' => $this->userSession->getUser()->getUID(), + 'sharedWith' => self::APP_GROUP + ]); + } try { $userFolder->get(path: $folderPath); @@ -596,28 +669,32 @@ public function uploadFile(mixed $content, string $filePath): bool { $filePath = trim(string: $filePath, characters: '/'); - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); - - // Check if file exists and create it if not. try { + $userFolder = $this->rootFolder->getUserFolder($this->getUser()->getUID()); + + // Check if file exists and create it if not. try { - $userFolder->get(path: $filePath); - } catch (NotFoundException $e) { - $userFolder->newFile(path: $filePath); - $file = $userFolder->get(path: $filePath); + try { + $userFolder->get(path: $filePath); + } catch (NotFoundException $e) { + $userFolder->newFile(path: $filePath); + $file = $userFolder->get(path: $filePath); - $file->putContent(data: $content); + $file->putContent(data: $content); - return true; - } + return true; + } - // File already exists. - $this->logger->warning("File $filePath already exists."); - return false; + // File already exists. + $this->logger->warning("File $filePath already exists."); + return false; + + } catch (NotPermittedException|GenericFileException|LockedException $e) { + $this->logger->error("Can't create file $filePath: " . $e->getMessage()); - } catch (NotPermittedException|GenericFileException|LockedException $e) { + throw new Exception("Can't write to file $filePath"); + } + } catch (NotPermittedException $e) { $this->logger->error("Can't create file $filePath: " . $e->getMessage()); throw new Exception("Can't write to file $filePath"); @@ -638,35 +715,39 @@ public function updateFile(mixed $content, string $filePath, bool $createNew = f { $filePath = trim(string: $filePath, characters: '/'); - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); - - // Check if file exists and overwrite it if it does. try { - try { - $file = $userFolder->get(path: $filePath); - - $file->putContent(data: $content); + $userFolder = $this->rootFolder->getUserFolder($this->getUser()->getUID()); - return true; - } catch (NotFoundException $e) { - if ($createNew === true) { - $userFolder->newFile(path: $filePath); + // Check if file exists and overwrite it if it does. + try { + try { $file = $userFolder->get(path: $filePath); $file->putContent(data: $content); - $this->logger->info("File $filePath did not exist, created a new file for it."); return true; + } catch (NotFoundException $e) { + if ($createNew === true) { + $userFolder->newFile(path: $filePath); + $file = $userFolder->get(path: $filePath); + + $file->putContent(data: $content); + + $this->logger->info("File $filePath did not exist, created a new file for it."); + return true; + } } - } - // File already exists. - $this->logger->warning("File $filePath already exists."); - return false; + // File already exists. + $this->logger->warning("File $filePath already exists."); + return false; + + } catch (NotPermittedException|GenericFileException|LockedException $e) { + $this->logger->error("Can't create file $filePath: " . $e->getMessage()); - } catch (NotPermittedException|GenericFileException|LockedException $e) { + throw new Exception("Can't write to file $filePath"); + } + } catch (NotPermittedException $e) { $this->logger->error("Can't create file $filePath: " . $e->getMessage()); throw new Exception("Can't write to file $filePath"); @@ -685,28 +766,74 @@ public function deleteFile(string $filePath): bool { $filePath = trim(string: $filePath, characters: '/'); - // Get the current user. - $currentUser = $this->userSession->getUser(); - $userFolder = $this->rootFolder->getUserFolder(userId: $currentUser ? $currentUser->getUID() : 'Guest'); - - // Check if file exists and delete it if it does. try { + $userFolder = $this->rootFolder->getUserFolder($this->getUser()->getUID()); + + // Check if file exists and delete it if it does. try { - $file = $userFolder->get(path: $filePath); - $file->delete(); + try { + $file = $userFolder->get(path: $filePath); + $file->delete(); - return true; - } catch (NotFoundException $e) { - // File does not exist. - $this->logger->warning("File $filePath does not exist."); + return true; + } catch (NotFoundException $e) { + // File does not exist. + $this->logger->warning("File $filePath does not exist."); - return false; + return false; + } + } catch (NotPermittedException|InvalidPathException $e) { + $this->logger->error("Can't delete file $filePath: " . $e->getMessage()); + + throw new Exception("Can't delete file $filePath"); } - } catch (NotPermittedException|InvalidPathException $e) { + } catch (NotPermittedException $e) { $this->logger->error("Can't delete file $filePath: " . $e->getMessage()); throw new Exception("Can't delete file $filePath"); } } + /** + * Adds a new file to an object's folder with the OpenCatalogi user as owner + * + * @param ObjectEntity $objectEntity The object entity to add the file to + * @param string $fileName The name of the file to create + * @param string $content The content to write to the file + * @return File The created file + * @throws NotPermittedException If file creation fails due to permissions + * @throws Exception If file creation fails for other reasons + */ + public function addFile(ObjectEntity $objectEntity, string $fileName, string $content): File + { + try { + // Create new file in the folder + $folder = $this->getObjectFolder( + objectEntity: $objectEntity, + register: $objectEntity->getRegister(), + schema: $objectEntity->getSchema() + ); + + // Set the OpenCatalogi user as the current user + $currentUser = $this->userSession->getUser(); + $this->userSession->setUser($this->getUser()); + + $file = $folder->newFile($fileName); + + // Write content to the file + $file->putContent($content); + + $this->userSession->setUser($currentUser); + + return $file; + + } catch (NotPermittedException $e) { + $this->logger->error("Permission denied creating file $fileName: " . $e->getMessage()); + throw new NotPermittedException("Cannot create file $fileName: " . $e->getMessage()); + } catch (\Exception $e) { + $this->logger->error("Failed to create file $fileName: " . $e->getMessage()); + throw new \Exception("Failed to create file $fileName: " . $e->getMessage()); + } + } + } diff --git a/lib/Service/ObjectService.php b/lib/Service/ObjectService.php index 5affa5c..c4dda3b 100644 --- a/lib/Service/ObjectService.php +++ b/lib/Service/ObjectService.php @@ -24,6 +24,8 @@ use OCP\App\IAppManager; use OCP\Files\Events\Node\NodeCreatedEvent; use OCP\Files\Folder; +use OCP\Files\InvalidPathException; +use OCP\Files\NotFoundException; use OCP\IAppConfig; use OCP\IURLGenerator; use Opis\JsonSchema\ValidationResult; @@ -538,7 +540,7 @@ public function getObjects( search: $search ); - if($files === false) { + if ($files === false) { return $objects; } @@ -1333,41 +1335,39 @@ public function getFiles(ObjectEntity|string $object): array return $files; } + /** + * Add a file to the object + * + * @param ObjectEntity|string $object The object to add the file to + * @param string $fileName The name of the file to add + * @param string $base64Content The base64 encoded content of the file + */ + public function addFile(ObjectEntity|string $object, string $fileName, string $base64Content) + { + // If string ID provided, try to find the object entity + if (is_string($object)) { + $object = $this->objectEntityMapper->find($object); + } + + return $file = $this->fileService->addFile($object, $fileName, $base64Content); + } + /** * Formats an array of Node files into an array of metadata arrays. + * Uses FileService formatFiles function, this function is here to be used by OpenCatalog or OpenConnector! * * See https://nextcloud-server.netlify.app/classes/ocp-files-file for the Nextcloud documentation on the File class * See https://nextcloud-server.netlify.app/classes/ocp-files-node for the Nextcloud documentation on the Node superclass - * + * * @param Node[] $files Array of Node files to format + * * @return array Array of formatted file metadata arrays + * @throws InvalidPathException + * @throws NotFoundException */ - public function formatFiles(array $files): array + public function formatFiles(array $files): array { - $formattedFiles = []; - - foreach($files as $file) { - // IShare documentation see https://nextcloud-server.netlify.app/classes/ocp-share-ishare - $shares = $this->fileService->findShares($file); - - $formattedFile = [ - 'id' => $file->getId(), - 'path' => $file->getPath(), - 'title' => $file->getName(), - 'accessUrl' => count($shares) > 0 ? $this->fileService->getShareLink($shares[0]) : null, - 'downloadUrl' => count($shares) > 0 ? $this->fileService->getShareLink($shares[0]).'/download' : null, - 'type' => $file->getMimetype(), - 'extension' => $file->getExtension(), - 'size' => $file->getSize(), - 'hash' => $file->getEtag(), - 'published' => (new DateTime())->setTimestamp($file->getCreationTime())->format('c'), - 'modified' => (new DateTime())->setTimestamp($file->getUploadTime())->format('c'), - ]; - - $formattedFiles[] = $formattedFile; - } - - return $formattedFiles; + return $this->fileService->formatFiles($files); } /** @@ -1382,7 +1382,12 @@ public function formatFiles(array $files): array */ public function hydrateFiles(ObjectEntity $object, array $files): ObjectEntity { - $formattedFiles = $this->formatFiles($files); + try { + $formattedFiles = $this->fileService->formatFiles($files); + } catch (InvalidPathException|NotFoundException $e) { + + } + $object->setFiles($formattedFiles); return $object; } @@ -1407,7 +1412,7 @@ public function getObject(Register $register, Schema $schema, string $uuid, ?arr if ($register->getSource() === 'internal' || $register->getSource() === '') { $object = $this->objectEntityMapper->findByUuid($register, $schema, $uuid); - if($files === false) { + if ($files === false) { return $object; } @@ -1886,7 +1891,7 @@ public function getUses(string $id, ?int $register = null, ?int $schema = null): foreach ($relations as $path => $relationId) { $referencedObjects[$path] = $this->objectEntityMapper->find($relationId); - if($referencedObjects[$path] === null){ + if ($referencedObjects[$path] === null){ $referencedObjects[$path] = $relationId; } } diff --git a/src/views/object/ObjectDetails.vue b/src/views/object/ObjectDetails.vue index ae466a1..3ec8533 100644 --- a/src/views/object/ObjectDetails.vue +++ b/src/views/object/ObjectDetails.vue @@ -144,7 +144,7 @@ import { objectStore, navigationStore } from '../../store/store.js'