userSession = $userSession; $this->root = $root; $this->urlGenerator = $urlGenerator; $this->trans = $trans; $this->logger = $logger; $this->config = $config; $this->crypt = $crypt; $this->fileUtility = new FileUtility($AppName, $trans, $logger, $config, $shareManager, $session); } /** * Create new file in folder * * @param string $name - file name * @param string $dir - folder path * @param string $shareToken - access token * * @return array * * @NoAdminRequired * @PublicPage */ public function create($name, $dir, $shareToken = NULL) { $this->logger->debug("Create: $name", array("app" => $this->appName)); if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { return ["error" => $this->trans->t("Not permitted")]; } if (empty($shareToken)) { $userId = $this->userSession->getUser()->getUID(); $userFolder = $this->root->getUserFolder($userId); } else { list ($userFolder, $error, $share) = $this->fileUtility->getNodeByToken($shareToken); if (isset($error)) { $this->logger->error("Create: $error", array("app" => $this->appName)); return ["error" => $error]; } if ($userFolder instanceof File) { return ["error" => $this->trans->t("You don't have enough permission to create")]; } if (!empty($shareToken) && ($share->getPermissions() & Constants::PERMISSION_CREATE) === 0) { $this->logger->error("Create in public folder without access: $fileId", array("app" => $this->appName)); return ["error" => $this->trans->t("You do not have enough permissions to view the file")]; } } $folder = $userFolder->get($dir); if ($folder === NULL) { $this->logger->error("Folder for file creation was not found: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("The required folder was not found")]; } if (!$folder->isCreatable()) { $this->logger->error("Folder for file creation without permission: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("You don't have enough permission to create")]; } $ext = strtolower("." . pathinfo($name, PATHINFO_EXTENSION)); $lang = \OC::$server->getL10NFactory("")->get("")->getLanguageCode(); $templatePath = $this->getTemplatePath($lang, $ext); if (!file_exists($templatePath)) { $lang = "en"; $templatePath = $this->getTemplatePath($lang, $ext); } $template = file_get_contents($templatePath); if (!$template) { $this->logger->error("Template for file creation not found: $templatePath", array("app" => $this->appName)); return ["error" => $this->trans->t("Template not found")]; } $name = $folder->getNonExistingName($name); try { $file = $folder->newFile($name); $file->putContent($template); } catch (NotPermittedException $e) { $this->logger->error("Can't create file: $name", array("app" => $this->appName)); return ["error" => $this->trans->t("Can't create file")]; } $fileInfo = $file->getFileInfo(); $result = Helper::formatFileInfo($fileInfo); return $result; } /** * Get template path * * @param string $lang - language * @param string $ext - file extension * * @return string */ private function getTemplatePath($lang, $ext) { return dirname(__DIR__) . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . "new" . $ext; } /** * Conversion file to Office Open XML format * * @param integer $fileId - file identifier * @param string $shareToken - access token * * @return array * * @NoAdminRequired * @PublicPage */ public function convert($fileId, $shareToken = NULL) { $this->logger->debug("Convert: $fileId", array("app" => $this->appName)); if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { return ["error" => $this->trans->t("Not permitted")]; } $user = $this->userSession->getUser(); $userId = NULL; if (!empty($user)) { $userId = $user->getUID(); } list ($file, $error, $share) = empty($shareToken) ? $this->getFile($userId, $fileId) : $this->fileUtility->getFileByToken($fileId, $shareToken); if (isset($error)) { $this->logger->error("Convertion: $fileId $error", array("app" => $this->appName)); return ["error" => $error]; } if (!empty($shareToken) && ($share->getPermissions() & Constants::PERMISSION_CREATE) === 0) { $this->logger->error("Convertion in public folder without access: $fileId", array("app" => $this->appName)); return ["error" => $this->trans->t("You do not have enough permissions to view the file")]; } $fileName = $file->getName(); $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $format = $this->config->FormatsSetting()[$ext]; if (!isset($format)) { $this->logger->info("Format for convertion not supported: $fileName", array("app" => $this->appName)); return ["error" => $this->trans->t("Format is not supported")]; } if (!isset($format["conv"]) || $format["conv"] !== true) { $this->logger->info("Conversion is not required: $fileName", array("app" => $this->appName)); return ["error" => $this->trans->t("Conversion is not required")]; } $internalExtension = "docx"; switch ($format["type"]) { case "spreadsheet": $internalExtension = "xlsx"; break; case "presentation": $internalExtension = "pptx"; break; } $newFileUri; $documentService = new DocumentService($this->trans, $this->config); $key = $this->fileUtility->getKey($file); $fileUrl = $this->getUrl($file, $shareToken); try { $newFileUri = $documentService->GetConvertedUri($fileUrl, $ext, $internalExtension, $key); } catch (\Exception $e) { $this->logger->error("GetConvertedUri: " . $file->getId() . " " . $e->getMessage(), array("app" => $this->appName)); return ["error" => $e->getMessage()]; } $folder = $file->getParent(); if (!$folder->isCreatable()) { $folder = $this->root->getUserFolder($userId); } try { $newData = $documentService->Request($newFileUri); } catch (\Exception $e) { $this->logger->error("Failed to download converted file: $newFileUri " . $e->getMessage(), array("app" => $this->appName)); return ["error" => $this->trans->t("Failed to download converted file")]; } $fileNameWithoutExt = substr($fileName, 0, strlen($fileName) - strlen($ext) - 1); $newFileName = $folder->getNonExistingName($fileNameWithoutExt . "." . $internalExtension); try { $file = $folder->newFile($newFileName); $file->putContent($newData); } catch (NotPermittedException $e) { $this->logger->error("Can't create file: $newFileName", array("app" => $this->appName)); return ["error" => $this->trans->t("Can't create file")]; } $fileInfo = $file->getFileInfo(); $result = Helper::formatFileInfo($fileInfo); return $result; } /** * Save file to folder * * @param string $name - file name * @param string $dir - folder path * @param string $url - file url * * @return array * * @NoAdminRequired */ public function save($name, $dir, $url) { $this->logger->debug("Save: $name", array("app" => $this->appName)); if (!$this->config->isUserAllowedToUse()) { return ["error" => $this->trans->t("Not permitted")]; } $userId = $this->userSession->getUser()->getUID(); $userFolder = $this->root->getUserFolder($userId); $folder = $userFolder->get($dir); if ($folder === NULL) { $this->logger->error("Folder for saving file was not found: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("The required folder was not found")]; } if (!$folder->isCreatable()) { $this->logger->error("Folder for saving file without permission: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("You don't have enough permission to create")]; } $url = $this->config->ReplaceDocumentServerUrlToInternal($url); try { $documentService = new DocumentService($this->trans, $this->config); $newData = $documentService->Request($url); } catch (\Exception $e) { $this->logger->error("Failed to download file for saving: $url " . $e->getMessage(), array("app" => $this->appName)); return ["error" => $this->trans->t("Download failed")]; } $name = $folder->getNonExistingName($name); try { $file = $folder->newFile($name); $file->putContent($newData); } catch (NotPermittedException $e) { $this->logger->error("Can't save file: $name", array("app" => $this->appName)); return ["error" => $this->trans->t("Can't create file")]; } $fileInfo = $file->getFileInfo(); $result = Helper::formatFileInfo($fileInfo); return $result; } /** * Get presigned url to file * * @param string $filePath - file path * * @return array * * @NoAdminRequired */ public function url($filePath) { $this->logger->debug("Request url for: $filePath", array("app" => $this->appName)); if (!$this->config->isUserAllowedToUse()) { return ["error" => $this->trans->t("Not permitted")]; } $userId = $this->userSession->getUser()->getUID(); $userFolder = $this->root->getUserFolder($userId); $file = $userFolder->get($filePath); if ($file === NULL) { $this->logger->error("File for generate presigned url was not found: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("File not found")]; } if (!$file->isReadable()) { $this->logger->error("Folder for saving file without permission: $dir", array("app" => $this->appName)); return ["error" => $this->trans->t("You do not have enough permissions to view the file")]; } $fileName = $file->getName(); $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $fileUrl = $this->getUrl($file); $result = [ "fileType" => $ext, "url" => $fileUrl ]; if (!empty($this->config->GetDocumentServerSecret())) { $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret()); $result["token"] = $token; } return $result; } /** * Print editor section * * @param integer $fileId - file identifier * @param string $shareToken - access token * @param string $filePath - file path * * @return TemplateResponse|RedirectResponse * * @NoAdminRequired * @NoCSRFRequired */ public function index($fileId, $shareToken = NULL, $filePath = NULL) { $this->logger->debug("Open: $fileId $filePath", array("app" => $this->appName)); if (empty($shareToken) && !$this->userSession->isLoggedIn()) { $redirectUrl = $this->urlGenerator->linkToRoute("core.login.showLoginForm", [ "redirect_url" => $this->request->getRequestUri() ]); return new RedirectResponse($redirectUrl); } if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { return $this->renderError($this->trans->t("Not permitted")); } $documentServerUrl = $this->config->GetDocumentServerUrl(); if (empty($documentServerUrl)) { $this->logger->error("documentServerUrl is empty", array("app" => $this->appName)); return $this->renderError($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); } $params = [ "documentServerUrl" => $documentServerUrl, "fileId" => $fileId, "filePath" => $filePath, "shareToken" => $shareToken ]; $response = new TemplateResponse($this->appName, "editor", $params); $csp = new ContentSecurityPolicy(); $csp->allowInlineScript(true); if (preg_match("/^https?:\/\//i", $documentServerUrl)) { $csp->addAllowedScriptDomain($documentServerUrl); $csp->addAllowedFrameDomain($documentServerUrl); } else { $csp->addAllowedFrameDomain($this->urlGenerator->getAbsoluteURL("/")); } $response->setContentSecurityPolicy($csp); return $response; } /** * Print public editor section * * @param integer $fileId - file identifier * @param string $shareToken - access token * * @return TemplateResponse * * @NoAdminRequired * @NoCSRFRequired * @PublicPage */ public function PublicPage($fileId, $shareToken) { return $this->index($fileId, $shareToken); } /** * Collecting the file parameters for the document service * * @param integer $fileId - file identifier * @param string $filePath - file path * @param string $shareToken - access token * @param bool $desktop - desktop label * * @return array * * @NoAdminRequired * @PublicPage */ public function config($fileId, $filePath = NULL, $shareToken = NULL, $desktop = false) { if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { return ["error" => $this->trans->t("Not permitted")]; } $user = $this->userSession->getUser(); $userId = NULL; if (!empty($user)) { $userId = $user->getUID(); } list ($file, $error, $share) = empty($shareToken) ? $this->getFile($userId, $fileId, $filePath) : $this->fileUtility->getFileByToken($fileId, $shareToken); if (isset($error)) { $this->logger->error("Config: $fileId $error", array("app" => $this->appName)); return ["error" => $error]; } $fileName = $file->getName(); $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $format = $this->config->FormatsSetting()[$ext]; if (!isset($format)) { $this->logger->info("Format is not supported for editing: $fileName", array("app" => $this->appName)); return ["error" => $this->trans->t("Format is not supported")]; } $fileUrl = $this->getUrl($file, $shareToken); $key = $this->fileUtility->getKey($file, true); $key = DocumentService::GenerateRevisionId($key); $params = [ "document" => [ "fileType" => $ext, "key" => $key, "permissions" => [], "title" => $fileName, "url" => $fileUrl, ], "documentType" => $format["type"], "editorConfig" => [ "lang" => str_replace("_", "-", \OC::$server->getL10NFactory("")->get("")->getLanguageCode()), "region" => str_replace("_", "-", \OC::$server->getL10NFactory("")->findLocale()) ] ]; $permissions_modifyFilter = $this->config->GetSystemValue($this->config->_permissions_modifyFilter); if (isset($permissions_modifyFilter)) { $params["document"]["permissions"]["modifyFilter"] = $permissions_modifyFilter; } $canEdit = isset($format["edit"]) && $format["edit"]; $editable = $file->isUpdateable() && (empty($shareToken) || ($share->getPermissions() & Constants::PERMISSION_UPDATE) === Constants::PERMISSION_UPDATE); $params["document"]["permissions"]["edit"] = $editable; if ($editable && $canEdit) { $ownerId = NULL; $owner = $file->getOwner(); if (!empty($owner)) { $ownerId = $owner->getUID(); } $hashCallback = $this->crypt->GetHash(["fileId" => $file->getId(), "ownerId" => $ownerId, "shareToken" => $shareToken, "action" => "track"]); $callback = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.track", ["doc" => $hashCallback]); if (!empty($this->config->GetStorageUrl())) { $callback = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $callback); } $params["editorConfig"]["callbackUrl"] = $callback; } else { $params["editorConfig"]["mode"] = "view"; } if (\OC::$server->getRequest()->isUserAgent([$this::USER_AGENT_MOBILE])) { $params["type"] = "mobile"; } if (!empty($userId)) { $params["editorConfig"]["user"] = [ "id" => $userId, "name" => $user->getDisplayName() ]; } $folderLink = NULL; if (!empty($shareToken)) { if (method_exists($share, "getHideDownload") && $share->getHideDownload()) { $params["document"]["permissions"]["download"] = false; $params["document"]["permissions"]["print"] = false; } $node = $share->getNode(); if ($node instanceof Folder) { $sharedFolder = $node; $folderPath = $sharedFolder->getRelativePath($file->getParent()->getPath()); if (!empty($folderPath)) { $linkAttr = [ "path" => $folderPath, "scrollto" => $file->getName(), "token" => $shareToken ]; $folderLink = $this->urlGenerator->linkToRouteAbsolute("files_sharing.sharecontroller.showShare", $linkAttr); } } } else if (!empty($userId)) { $userFolder = $this->root->getUserFolder($userId); $folderPath = $userFolder->getRelativePath($file->getParent()->getPath()); if (!empty($folderPath)) { $linkAttr = [ "dir" => $folderPath, "scrollto" => $file->getName() ]; $folderLink = $this->urlGenerator->linkToRouteAbsolute("files.view.index", $linkAttr); } } if ($folderLink !== NULL) { $params["editorConfig"]["customization"]["goback"] = [ "url" => $folderLink ]; if (!$desktop) { if ($this->config->GetSameTab()) { $params["editorConfig"]["customization"]["goback"]["blank"] = false; } } } $params = $this->setCustomization($params); $params = $this->setWatermark($params, !empty($shareToken), $userId, $file); if ($this->config->UseDemo()) { $params["editorConfig"]["tenant"] = $this->config->GetSystemValue("instanceid", true); } if (!empty($this->config->GetDocumentServerSecret())) { $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret()); $params["token"] = $token; } $this->logger->debug("Config is generated for: $fileId with key $key", array("app" => $this->appName)); return $params; } /** * Getting file by identifier * * @param string $userId - user identifier * @param integer $fileId - file identifier * @param string $filePath - file path * * @return array */ private function getFile($userId, $fileId, $filePath = NULL) { if (empty($fileId)) { return [NULL, $this->trans->t("FileId is empty"), NULL]; } try { $files = $this->root->getUserFolder($userId)->getById($fileId); } catch (\Exception $e) { $this->logger->error("getFile: $fileId " . $e->getMessage(), array("app" => $this->appName)); return [NULL, $this->trans->t("Invalid request"), NULL]; } if (empty($files)) { $this->logger->info("Files not found: $fileId", array("app" => $this->appName)); return [NULL, $this->trans->t("File not found"), NULL]; } $file = $files[0]; if (count($files) > 1 && !empty($filePath)) { $filePath = "/" . $userId . "/files" . $filePath; foreach ($files as $curFile) { if ($curFile->getPath() === $filePath) { $file = $curFile; } } } if (!$file->isReadable()) { return [NULL, $this->trans->t("You do not have enough permissions to view the file"), NULL]; } return [$file, NULL, NULL]; } /** * Generate secure link to download document * * @param integer $file - file * @param string $shareToken - access token * * @return string */ private function getUrl($file, $shareToken = NULL) { $user = $this->userSession->getUser(); $userId = NULL; if (!empty($user)) { $userId = $user->getUID(); } $ownerId = NULL; $owner = $file->getOwner(); if (!empty($owner)) { $ownerId = $owner->getUID(); } $hashUrl = $this->crypt->GetHash(["fileId" => $file->getId(), "userId" => $userId, "ownerId" => $ownerId, "shareToken" => $shareToken, "action" => "download"]); $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); if (!empty($this->config->GetStorageUrl())) { $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); } return $fileUrl; } /** * Set customization parameters * * @param array params - file parameters * * @return array */ private function setCustomization($params) { //default is true if ($this->config->GetCustomizationChat() === false) { $params["editorConfig"]["customization"]["chat"] = false; } //default is false if ($this->config->GetCustomizationCompactHeader() === true) { $params["editorConfig"]["customization"]["compactHeader"] = true; } //default is false if ($this->config->GetCustomizationFeedback() === true) { $params["editorConfig"]["customization"]["feedback"] = true; } //default is true if ($this->config->GetCustomizationHelp() === false) { $params["editorConfig"]["customization"]["help"] = false; } //default is false if ($this->config->GetCustomizationToolbarNoTabs() === true) { $params["editorConfig"]["customization"]["toolbarNoTabs"] = true; } /* from system config */ $customer = $this->config->GetSystemValue($this->config->_customization_customer); if (isset($customer)) { $params["editorConfig"]["customization"]["customer"] = $customer; } $feedback = $this->config->GetSystemValue($this->config->_customization_feedback); if (isset($feedback)) { $params["editorConfig"]["customization"]["feedback"] = $feedback; } $loaderLogo = $this->config->GetSystemValue($this->config->_customization_loaderLogo); if (isset($loaderLogo)) { $params["editorConfig"]["customization"]["loaderLogo"] = $loaderLogo; } $loaderName = $this->config->GetSystemValue($this->config->_customization_loaderName); if (isset($loaderName)) { $params["editorConfig"]["customization"]["loaderName"] = $loaderName; } $logo = $this->config->GetSystemValue($this->config->_customization_logo); if (isset($logo)) { $params["editorConfig"]["customization"]["logo"] = $logo; } return $params; } /** * Set watermark parameters * * @param array params - file parameters * @param bool isPublic - with access token * @param string userId - user identifier * @param string file - file * * @return array */ private function setWatermark($params, $isPublic, $userId, $file) { $watermarkTemplate = $this->getWatermarkText($isPublic, $userId, $file, $params["document"]["permissions"]["edit"] !== false, !array_key_exists("download", $params["document"]["permissions"]) || $params["document"]["permissions"]["download"] !== false); if ($watermarkTemplate !== false) { $replacements = [ "userId" => $userId, "date" => (new \DateTime())->format("Y-m-d H:i:s"), "themingName" => \OC::$server->getThemingDefaults()->getName() ]; $watermarkTemplate = preg_replace_callback("/{(.+?)}/", function($matches) use ($replacements) { return $replacements[$matches[1]]; }, $watermarkTemplate); $params["document"]["options"] = [ "watermark_on_draw" => [ "align" => 1, "height" => 100, "paragraphs" => array([ "align" => 2, "runs" => array([ "fill" => [182, 182, 182], "font-size" => 70, "text" => $watermarkTemplate, ]) ]), "rotate" => -45, "width" => 250, ] ]; } return $params; } /** * Should watermark * * @param bool isPublic - with access token * @param string userId - user identifier * @param string file - file * @param bool canEdit - edit permission * @param bool canDownload - download permission * * @return bool|string */ private function getWatermarkText($isPublic, $userId, $file, $canEdit, $canDownload) { $watermarkSettings = $this->config->GetWatermarkSettings(); if (!$watermarkSettings["enabled"]) { return false; } $watermarkText = $watermarkSettings["text"]; $fileId = $file->getId(); if ($isPublic) { if ($watermarkSettings["linkAll"]) { return $watermarkText; } if ($watermarkSettings["linkRead"] && !$canEdit) { return $watermarkText; } if ($watermarkSettings["linkSecure"] && !$canDownload) { return $watermarkText; } if ($watermarkSettings["linkTags"]) { $tags = $watermarkSettings["linkTagsList"]; $fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], "files")[$fileId]; foreach ($fileTags as $tagId) { if (in_array($tagId, $tags, true)) { return $watermarkText; } } } } else { if ($watermarkSettings["shareAll"] && $file->getOwner()->getUID() !== $userId) { return $watermarkText; } if ($watermarkSettings["shareRead"] && !$canEdit) { return $watermarkText; } } if ($watermarkSettings["allGroups"]) { $groups = $watermarkSettings["allGroupsList"]; foreach ($groups as $group) { if (\OC::$server->getGroupManager()->isInGroup($userId, $group)) { return $watermarkText; } } } if ($watermarkSettings["allTags"]) { $tags = $watermarkSettings["allTagsList"]; $fileTags = \OC::$server->getSystemTagObjectMapper()->getTagIdsForObjects([$fileId], "files")[$fileId]; foreach ($fileTags as $tagId) { if (in_array($tagId, $tags, true)) { return $watermarkText; } } } return false; } /** * Print error page * * @param string $error - error message * @param string $hint - error hint * * @return TemplateResponse */ private function renderError($error, $hint = "") { return new TemplateResponse("", "error", array( "errors" => array(array( "error" => $error, "hint" => $hint )) ), "error"); } }