diff --git a/appinfo/application.php b/appinfo/application.php index 5fd530a1..1d21f078 100644 --- a/appinfo/application.php +++ b/appinfo/application.php @@ -42,215 +42,215 @@ use OCA\Onlyoffice\Preview; class Application extends App { - /** - * Application configuration - * - * @var AppConfig - */ - public $appConfig; - - /** - * Hash generator - * - * @var Crypt - */ - public $crypt; - - public function __construct(array $urlParams = []) { - $appName = "onlyoffice"; - - parent::__construct($appName, $urlParams); - - $this->appConfig = new AppConfig($appName); - $this->crypt = new Crypt($this->appConfig); - - // Default script and style if configured - $eventDispatcher = \OC::$server->getEventDispatcher(); - $eventDispatcher->addListener( - "OCA\Files::loadAdditionalScripts", - function () { - if (!empty($this->appConfig->GetDocumentServerUrl()) - && $this->appConfig->SettingsAreSuccessful() - && $this->appConfig->isUserAllowedToUse()) { - Util::addScript("onlyoffice", "desktop"); - Util::addScript("onlyoffice", "main"); - Util::addScript("onlyoffice", "share"); - Util::addScript("onlyoffice", "template"); - - if ($this->appConfig->GetSameTab()) { - Util::addScript("onlyoffice", "listener"); - } - - Util::addStyle("onlyoffice", "template"); - Util::addStyle("onlyoffice", "main"); - } - } - ); - - Util::connectHook("OCP\Share", "share_link_access", Hookhandler::class, "PublicPage"); - - require_once __DIR__ . "/../3rdparty/jwt/BeforeValidException.php"; - require_once __DIR__ . "/../3rdparty/jwt/ExpiredException.php"; - require_once __DIR__ . "/../3rdparty/jwt/SignatureInvalidException.php"; - require_once __DIR__ . "/../3rdparty/jwt/CachedKeySet.php"; - require_once __DIR__ . "/../3rdparty/jwt/JWT.php"; - require_once __DIR__ . "/../3rdparty/jwt/JWK.php"; - require_once __DIR__ . "/../3rdparty/jwt/Key.php"; - - // Set the leeway for the JWT library in case the system clock is a second off - \Firebase\JWT\JWT::$leeway = $this->appConfig->GetJwtLeeway(); - - $container = $this->getContainer(); - - $detector = $container->query(IMimeTypeDetector::class); - $detector->getAllMappings(); - $detector->registerType("ott", "application/vnd.oasis.opendocument.text-template"); - $detector->registerType("ots", "application/vnd.oasis.opendocument.spreadsheet-template"); - $detector->registerType("otp", "application/vnd.oasis.opendocument.presentation-template"); - $detector->registerType("docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf"); - $detector->registerType("oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform"); - - $previewManager = $container->query(IPreview::class); - if ($this->appConfig->GetPreview()) { - $previewManager->registerProvider(Preview::getMimeTypeRegex(), function () use ($container) { - return $container->query(Preview::class); - }); - } - - $notificationManager = \OC::$server->getNotificationManager(); - $notificationManager->registerNotifier(function () use ($appName) { - return new Notifier( - $appName, - \OC::$server->getL10NFactory(), - \OC::$server->getURLGenerator(), - \OC::$server->getLogger(), - \OC::$server->getUserManager() - ); - }, function () use ($appName) { - return [ - "id" => $appName, - "name" => $appName, - ]; - }); - - $container->registerService("L10N", function ($c) { - return $c->query("ServerContainer")->getL10N($c->query("AppName")); - }); - - $container->registerService("RootStorage", function ($c) { - return $c->query("ServerContainer")->getRootFolder(); - }); - - $container->registerService("UserSession", function ($c) { - return $c->query("ServerContainer")->getUserSession(); - }); - - $container->registerService("Logger", function ($c) { - return $c->query("ServerContainer")->getLogger(); - }); - - $container->registerService("URLGenerator", function ($c) { - return $c->query("ServerContainer")->getURLGenerator(); - }); - - // Controllers - $container->registerService("SettingsController", function ($c) { - return new SettingsController( - $c->query("AppName"), - $c->query("Request"), - $c->query("URLGenerator"), - $c->query("L10N"), - $c->query("Logger"), - $this->appConfig, - $this->crypt - ); - }); - - $container->registerService("SettingsApiController", function ($c) { - return new SettingsApiController( - $c->query("AppName"), - $c->query("Request"), - $c->query("URLGenerator"), - $this->appConfig - ); - }); - - $container->registerService("EditorController", function ($c) { - return new EditorController( - $c->query("AppName"), - $c->query("Request"), - $c->query("RootStorage"), - $c->query("UserSession"), - $c->query("ServerContainer")->getUserManager(), - $c->query("URLGenerator"), - $c->query("L10N"), - $c->query("Logger"), - $this->appConfig, - $this->crypt, - $c->query("IManager"), - $c->query("Session"), - $c->query("ServerContainer")->getGroupManager() - ); - }); - - $container->registerService("EditorApiController", function ($c) { - return new EditorApiController( - $c->query("AppName"), - $c->query("Request"), - $c->query("RootStorage"), - $c->query("UserSession"), - $c->query("URLGenerator"), - $c->query("L10N"), - $c->query("Logger"), - $this->appConfig, - $this->crypt, - $c->query("IManager"), - $c->query("Session"), - $c->get(ITagManager::class) - ); - }); - - $container->registerService("CallbackController", function ($c) { - return new CallbackController( - $c->query("AppName"), - $c->query("Request"), - $c->query("RootStorage"), - $c->query("UserSession"), - $c->query("ServerContainer")->getUserManager(), - $c->query("L10N"), - $c->query("Logger"), - $this->appConfig, - $this->crypt, - $c->query("IManager") - ); - }); - - $container->registerService("TemplateController", function ($c) { - return new TemplateController( - $c->query("AppName"), - $c->query("Request"), - $c->query("L10N"), - $c->query("Logger") - ); - }); - - $container->registerService("WebAssetController", function ($c) { - return new WebAssetController( - $c->query("AppName"), - $c->query("Request"), - $c->query("Logger") - ); - }); - - $checkBackgroundJobs = new JobListController( - $container->query("AppName"), - $container->query("Request"), - $container->query("Logger"), - $this->appConfig, - $container->query(IJobList::class) - ); - $checkBackgroundJobs->checkAllJobs(); - - Hooks::connectHooks(); - } + /** + * Application configuration + * + * @var AppConfig + */ + public $appConfig; + + /** + * Hash generator + * + * @var Crypt + */ + public $crypt; + + public function __construct(array $urlParams = []) { + $appName = "onlyoffice"; + + parent::__construct($appName, $urlParams); + + $this->appConfig = new AppConfig($appName); + $this->crypt = new Crypt($this->appConfig); + + // Default script and style if configured + $eventDispatcher = \OC::$server->getEventDispatcher(); + $eventDispatcher->addListener( + "OCA\Files::loadAdditionalScripts", + function () { + if (!empty($this->appConfig->GetDocumentServerUrl()) + && $this->appConfig->SettingsAreSuccessful() + && $this->appConfig->isUserAllowedToUse()) { + Util::addScript("onlyoffice", "desktop"); + Util::addScript("onlyoffice", "main"); + Util::addScript("onlyoffice", "share"); + Util::addScript("onlyoffice", "template"); + + if ($this->appConfig->GetSameTab()) { + Util::addScript("onlyoffice", "listener"); + } + + Util::addStyle("onlyoffice", "template"); + Util::addStyle("onlyoffice", "main"); + } + } + ); + + Util::connectHook("OCP\Share", "share_link_access", Hookhandler::class, "PublicPage"); + + require_once __DIR__ . "/../3rdparty/jwt/BeforeValidException.php"; + require_once __DIR__ . "/../3rdparty/jwt/ExpiredException.php"; + require_once __DIR__ . "/../3rdparty/jwt/SignatureInvalidException.php"; + require_once __DIR__ . "/../3rdparty/jwt/CachedKeySet.php"; + require_once __DIR__ . "/../3rdparty/jwt/JWT.php"; + require_once __DIR__ . "/../3rdparty/jwt/JWK.php"; + require_once __DIR__ . "/../3rdparty/jwt/Key.php"; + + // Set the leeway for the JWT library in case the system clock is a second off + \Firebase\JWT\JWT::$leeway = $this->appConfig->GetJwtLeeway(); + + $container = $this->getContainer(); + + $detector = $container->query(IMimeTypeDetector::class); + $detector->getAllMappings(); + $detector->registerType("ott", "application/vnd.oasis.opendocument.text-template"); + $detector->registerType("ots", "application/vnd.oasis.opendocument.spreadsheet-template"); + $detector->registerType("otp", "application/vnd.oasis.opendocument.presentation-template"); + $detector->registerType("docxf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf"); + $detector->registerType("oform", "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform"); + + $previewManager = $container->query(IPreview::class); + if ($this->appConfig->GetPreview()) { + $previewManager->registerProvider(Preview::getMimeTypeRegex(), function () use ($container) { + return $container->query(Preview::class); + }); + } + + $notificationManager = \OC::$server->getNotificationManager(); + $notificationManager->registerNotifier(function () use ($appName) { + return new Notifier( + $appName, + \OC::$server->getL10NFactory(), + \OC::$server->getURLGenerator(), + \OC::$server->getLogger(), + \OC::$server->getUserManager() + ); + }, function () use ($appName) { + return [ + "id" => $appName, + "name" => $appName, + ]; + }); + + $container->registerService("L10N", function ($c) { + return $c->query("ServerContainer")->getL10N($c->query("AppName")); + }); + + $container->registerService("RootStorage", function ($c) { + return $c->query("ServerContainer")->getRootFolder(); + }); + + $container->registerService("UserSession", function ($c) { + return $c->query("ServerContainer")->getUserSession(); + }); + + $container->registerService("Logger", function ($c) { + return $c->query("ServerContainer")->getLogger(); + }); + + $container->registerService("URLGenerator", function ($c) { + return $c->query("ServerContainer")->getURLGenerator(); + }); + + // Controllers + $container->registerService("SettingsController", function ($c) { + return new SettingsController( + $c->query("AppName"), + $c->query("Request"), + $c->query("URLGenerator"), + $c->query("L10N"), + $c->query("Logger"), + $this->appConfig, + $this->crypt + ); + }); + + $container->registerService("SettingsApiController", function ($c) { + return new SettingsApiController( + $c->query("AppName"), + $c->query("Request"), + $c->query("URLGenerator"), + $this->appConfig + ); + }); + + $container->registerService("EditorController", function ($c) { + return new EditorController( + $c->query("AppName"), + $c->query("Request"), + $c->query("RootStorage"), + $c->query("UserSession"), + $c->query("ServerContainer")->getUserManager(), + $c->query("URLGenerator"), + $c->query("L10N"), + $c->query("Logger"), + $this->appConfig, + $this->crypt, + $c->query("IManager"), + $c->query("Session"), + $c->query("ServerContainer")->getGroupManager() + ); + }); + + $container->registerService("EditorApiController", function ($c) { + return new EditorApiController( + $c->query("AppName"), + $c->query("Request"), + $c->query("RootStorage"), + $c->query("UserSession"), + $c->query("URLGenerator"), + $c->query("L10N"), + $c->query("Logger"), + $this->appConfig, + $this->crypt, + $c->query("IManager"), + $c->query("Session"), + $c->get(ITagManager::class) + ); + }); + + $container->registerService("CallbackController", function ($c) { + return new CallbackController( + $c->query("AppName"), + $c->query("Request"), + $c->query("RootStorage"), + $c->query("UserSession"), + $c->query("ServerContainer")->getUserManager(), + $c->query("L10N"), + $c->query("Logger"), + $this->appConfig, + $this->crypt, + $c->query("IManager") + ); + }); + + $container->registerService("TemplateController", function ($c) { + return new TemplateController( + $c->query("AppName"), + $c->query("Request"), + $c->query("L10N"), + $c->query("Logger") + ); + }); + + $container->registerService("WebAssetController", function ($c) { + return new WebAssetController( + $c->query("AppName"), + $c->query("Request"), + $c->query("Logger") + ); + }); + + $checkBackgroundJobs = new JobListController( + $container->query("AppName"), + $container->query("Request"), + $container->query("Logger"), + $this->appConfig, + $container->query(IJobList::class) + ); + $checkBackgroundJobs->checkAllJobs(); + + Hooks::connectHooks(); + } } diff --git a/appinfo/routes.php b/appinfo/routes.php index 1f9b0199..82e0270a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -18,40 +18,40 @@ */ return [ - "routes" => [ - ["name" => "callback#download", "url" => "/download", "verb" => "GET"], - ["name" => "callback#emptyfile", "url" => "/empty", "verb" => "GET"], - ["name" => "callback#track", "url" => "/track", "verb" => "POST"], - ["name" => "editor#create_new", "url" => "/new", "verb" => "GET"], - ["name" => "editor#download", "url" => "/downloadas", "verb" => "GET"], - ["name" => "editor#index", "url" => "/{fileId}", "verb" => "GET"], - ["name" => "editor#public_page", "url" => "/s/{shareToken}", "verb" => "GET"], - ["name" => "editor#users", "url" => "/ajax/users", "verb" => "GET"], - ["name" => "editor#mention", "url" => "/ajax/mention", "verb" => "POST"], - ["name" => "editor#reference", "url" => "/ajax/reference", "verb" => "POST"], - ["name" => "editor#create", "url" => "/ajax/new", "verb" => "POST"], - ["name" => "editor#convert", "url" => "/ajax/convert", "verb" => "POST"], - ["name" => "editor#save", "url" => "/ajax/save", "verb" => "POST"], - ["name" => "editor#url", "url" => "/ajax/url", "verb" => "GET"], - ["name" => "editor#history", "url" => "/ajax/history", "verb" => "GET"], - ["name" => "editor#version", "url" => "/ajax/version", "verb" => "GET"], - ["name" => "editor#restore", "url" => "/ajax/restore", "verb" => "PUT"], - ["name" => "settings#save_address", "url" => "/ajax/settings/address", "verb" => "PUT"], - ["name" => "settings#save_common", "url" => "/ajax/settings/common", "verb" => "PUT"], - ["name" => "settings#save_security", "url" => "/ajax/settings/security", "verb" => "PUT"], - ["name" => "settings#get_settings", "url" => "/ajax/settings", "verb" => "GET"], - ["name" => "settings#clear_history", "url" => "/ajax/settings/history", "verb" => "DELETE"], - ["name" => "template#add_template", "url" => "/ajax/template", "verb" => "POST"], - ["name" => "template#get_templates", "url" => "/ajax/template", "verb" => "GET"], - ["name" => "template#delete_template", "url" => "/ajax/template", "verb" => "DELETE"], - ["name" => "webasset#get", "url" => "/js/onlyoffice.js", "verb" => "GET"], - ], - "ocs" => [ - ["name" => "federation#key", "url" => "/api/v1/key", "verb" => "POST"], - ["name" => "federation#keylock", "url" => "/api/v1/keylock", "verb" => "POST"], - ["name" => "federation#healthcheck", "url" => "/api/v1/healthcheck", "verb" => "GET"], - ["name" => "editorapi#config", "url" => "/api/v1/config/{fileId}", "verb" => "GET"], - ["name" => "editorapi#fillempty", "url" => "/api/v1/empty/{fileId}", "verb" => "GET"], - ["name" => "settingsapi#get_doc_server_url", "url" => "/api/v1/settings/docserver", "verb" => "GET"], - ] + "routes" => [ + ["name" => "callback#download", "url" => "/download", "verb" => "GET"], + ["name" => "callback#emptyfile", "url" => "/empty", "verb" => "GET"], + ["name" => "callback#track", "url" => "/track", "verb" => "POST"], + ["name" => "editor#create_new", "url" => "/new", "verb" => "GET"], + ["name" => "editor#download", "url" => "/downloadas", "verb" => "GET"], + ["name" => "editor#index", "url" => "/{fileId}", "verb" => "GET"], + ["name" => "editor#public_page", "url" => "/s/{shareToken}", "verb" => "GET"], + ["name" => "editor#users", "url" => "/ajax/users", "verb" => "GET"], + ["name" => "editor#mention", "url" => "/ajax/mention", "verb" => "POST"], + ["name" => "editor#reference", "url" => "/ajax/reference", "verb" => "POST"], + ["name" => "editor#create", "url" => "/ajax/new", "verb" => "POST"], + ["name" => "editor#convert", "url" => "/ajax/convert", "verb" => "POST"], + ["name" => "editor#save", "url" => "/ajax/save", "verb" => "POST"], + ["name" => "editor#url", "url" => "/ajax/url", "verb" => "GET"], + ["name" => "editor#history", "url" => "/ajax/history", "verb" => "GET"], + ["name" => "editor#version", "url" => "/ajax/version", "verb" => "GET"], + ["name" => "editor#restore", "url" => "/ajax/restore", "verb" => "PUT"], + ["name" => "settings#save_address", "url" => "/ajax/settings/address", "verb" => "PUT"], + ["name" => "settings#save_common", "url" => "/ajax/settings/common", "verb" => "PUT"], + ["name" => "settings#save_security", "url" => "/ajax/settings/security", "verb" => "PUT"], + ["name" => "settings#get_settings", "url" => "/ajax/settings", "verb" => "GET"], + ["name" => "settings#clear_history", "url" => "/ajax/settings/history", "verb" => "DELETE"], + ["name" => "template#add_template", "url" => "/ajax/template", "verb" => "POST"], + ["name" => "template#get_templates", "url" => "/ajax/template", "verb" => "GET"], + ["name" => "template#delete_template", "url" => "/ajax/template", "verb" => "DELETE"], + ["name" => "webasset#get", "url" => "/js/onlyoffice.js", "verb" => "GET"], + ], + "ocs" => [ + ["name" => "federation#key", "url" => "/api/v1/key", "verb" => "POST"], + ["name" => "federation#keylock", "url" => "/api/v1/keylock", "verb" => "POST"], + ["name" => "federation#healthcheck", "url" => "/api/v1/healthcheck", "verb" => "GET"], + ["name" => "editorapi#config", "url" => "/api/v1/config/{fileId}", "verb" => "GET"], + ["name" => "editorapi#fillempty", "url" => "/api/v1/empty/{fileId}", "verb" => "GET"], + ["name" => "settingsapi#get_doc_server_url", "url" => "/api/v1/settings/docserver", "verb" => "GET"], + ] ]; diff --git a/controller/callbackcontroller.php b/controller/callbackcontroller.php index fec40cea..f9e1509e 100644 --- a/controller/callbackcontroller.php +++ b/controller/callbackcontroller.php @@ -52,735 +52,735 @@ * Save the file without authentication. */ class CallbackController extends Controller { - /** - * Root folder - * - * @var IRootFolder - */ - private $root; - - /** - * User session - * - * @var IUserSession - */ - private $userSession; - - /** - * User manager - * - * @var IUserManager - */ - private $userManager; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Logger - * - * @var OCP\ILogger - */ - private $logger; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * Share manager - * - * @var IManager - */ - private $shareManager; - - /** - * File version manager - * - * @var VersionManager - */ - private $versionManager; - - /** - * Status of the document - */ - private const TrackerStatus_Editing = 1; - private const TrackerStatus_MustSave = 2; - private const TrackerStatus_Corrupted = 3; - private const TrackerStatus_Closed = 4; - private const TrackerStatus_ForceSave = 6; - private const TrackerStatus_CorruptedForceSave = 7; - - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IRootFolder $root - root folder - * @param IUserSession $userSession - user session - * @param IUserManager $userManager - user manager - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param AppConfig $config - application configuration - * @param Crypt $crypt - hash generator - * @param IManager $shareManager - Share manager - */ - public function __construct( - $AppName, - IRequest $request, - IRootFolder $root, - IUserSession $userSession, - IUserManager $userManager, - IL10N $trans, - ILogger $logger, - AppConfig $config, - Crypt $crypt, - IManager $shareManager - ) { - parent::__construct($AppName, $request); - - $this->root = $root; - $this->userSession = $userSession; - $this->userManager = $userManager; - $this->trans = $trans; - $this->logger = $logger; - $this->config = $config; - $this->crypt = $crypt; - $this->shareManager = $shareManager; - - $this->versionManager = new VersionManager($AppName, $root); - } - - /** - * Downloading file by the document service - * - * @param string $doc - verification token with the file identifier - * - * @return DataDownloadResponse|JSONResponse - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * @CORS - */ - public function download($doc) { - list($hashData, $error) = $this->crypt->ReadHash($doc); - if ($hashData === null) { - $this->logger->error("Download with empty or not correct hash: $error", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - if ($hashData->action !== "download") { - $this->logger->error("Download with other action", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); - } - - $fileId = $hashData->fileId; - $version = isset($hashData->version) ? $hashData->version : null; - $changes = isset($hashData->changes) ? $hashData->changes : false; - $template = isset($hashData->template) ? $hashData->template : false; - $this->logger->debug("Download: $fileId ($version)" . ($changes ? " changes" : ""), ["app" => $this->appName]); - - if (!$this->userSession->isLoggedIn() - && !$changes) { - if (!empty($this->config->GetDocumentServerSecret())) { - $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); - if (empty($header)) { - $this->logger->error("Download without jwt", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - - $header = substr($header, \strlen("Bearer ")); - - try { - $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); - } catch (\UnexpectedValueException $e) { - $this->logger->logException($e, ["message" => "Download with invalid jwt", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - } - } - - $userId = null; - - $user = null; - if ($this->userSession->isLoggedIn()) { - $user = $this->userSession->getUser(); - $userId = $user->getUID(); - } else { - \OC_Util::tearDownFS(); - - if (isset($hashData->userId)) { - $userId = $hashData->userId; - - $user = $this->userManager->get($userId); - if (!empty($user)) { - \OC_User::setUserId($userId); - } - - if ($this->config->checkEncryptionModule() === "master") { - \OC_User::setIncognitoMode(true); - } else { - if (!empty($user)) { - \OC_Util::setupFS($userId); - } - } - } - } - - $shareToken = isset($hashData->shareToken) ? $hashData->shareToken : null; - list($file, $error) = empty($shareToken) ? $this->getFile($userId, $fileId, null, $changes ? null : $version, $template) : $this->getFileByToken($fileId, $shareToken, $changes ? null : $version); - - if (isset($error)) { - return $error; - } - - if ($this->userSession->isLoggedIn() && !$file->isReadable()) { - $this->logger->error("Download without access right", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - - if (empty($user) - && $this->config->checkEncryptionModule() !== "master") { - $owner = $file->getFileInfo()->getOwner(); - if ($owner !== null) { - \OC_Util::setupFS($owner->getUID()); - } - } - - if ($changes) { - if ($this->versionManager->available !== true) { - $this->logger->error("Download changes: versionManager is null", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); - } - - $owner = $file->getFileInfo()->getOwner(); - if ($owner === null) { - $this->logger->error("Download: changes owner of $fileId was not found", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND); - } - - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - - $versionId = null; - if ($version > \count($versions)) { - $versionId = $file->getFileInfo()->getMtime(); - } else { - $fileVersion = array_values($versions)[$version - 1]; - - $versionId = $fileVersion->getRevisionId(); - } - - $changesFile = FileVersions::getChangesFile($owner->getUID(), $fileId, $versionId); - if ($changesFile === null) { - $this->logger->error("Download: changes $fileId ($version) was not found", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND); - } - - $file = $changesFile; - } - - try { - $response = new DataDownloadResponse($file->getContent(), $file->getName(), $file->getMimeType()); - - if ($changes) { - $response = \OC_Response::setOptionsRequestHeaders($response); - } - - return $response; - } catch (NotPermittedException $e) { - $this->logger->logException($e, ["message" => "Download Not permitted: $fileId ($version)", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Not permitted")], Http::STATUS_FORBIDDEN); - } - return new JSONResponse(["message" => $this->trans->t("Download failed")], Http::STATUS_INTERNAL_SERVER_ERROR); - } - - /** - * Downloading empty file by the document service - * - * @param string $doc - verification token with the file identifier - * - * @return DataDownloadResponse|JSONResponse - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * @CORS - */ - public function emptyfile($doc) { - $this->logger->debug("Download empty", ["app" => $this->appName]); - - list($hashData, $error) = $this->crypt->ReadHash($doc); - if ($hashData === null) { - $this->logger->error("Download empty with empty or not correct hash: $error", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - if ($hashData->action !== "empty") { - $this->logger->error("Download empty with other action", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); - } - - if (!empty($this->config->GetDocumentServerSecret())) { - $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); - if (empty($header)) { - $this->logger->error("Download empty without jwt", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - - $header = substr($header, \strlen("Bearer ")); - - try { - $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); - } catch (\UnexpectedValueException $e) { - $this->logger->logException($e, ["message" => "Download empty with invalid jwt", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - } - - $templatePath = TemplateManager::GetEmptyTemplatePath("en", ".docx"); - - $template = file_get_contents($templatePath); - if (!$template) { - $this->logger->info("Template for download empty not found: $templatePath", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND); - } - - try { - return new DataDownloadResponse($template, "new.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); - } catch (NotPermittedException $e) { - $this->logger->logException($e, ["message" => "Download Not permitted", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Not permitted")], Http::STATUS_FORBIDDEN); - } - return new JSONResponse(["message" => $this->trans->t("Download failed")], Http::STATUS_INTERNAL_SERVER_ERROR); - } - - /** - * Handle request from the document server with the document status information - * - * @param string $doc - verification token with the file identifier - * @param array $users - the list of the identifiers of the users - * @param string $key - the edited document identifier - * @param integer $status - the edited status - * @param string $url - the link to the edited document to be saved - * @param string $token - request signature - * @param array $history - file history - * @param string $changesurl - link to file changes - * @param integer $forcesavetype - the type of force save action - * @param array $actions - the array of action - * @param string $filetype - extension of the document that is downloaded from the link specified with the url parameter - * - * @return array - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - * @CORS - */ - public function track($doc, $users, $key, $status, $url, $token, $history, $changesurl, $forcesavetype, $actions, $filetype) { - list($hashData, $error) = $this->crypt->ReadHash($doc); - if ($hashData === null) { - $this->logger->error("Track with empty or not correct hash: $error", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - if ($hashData->action !== "track") { - $this->logger->error("Track with other action", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); - } - - $fileId = $hashData->fileId; - $this->logger->debug("Track: $fileId status $status", ["app" => $this->appName]); - - if (!empty($this->config->GetDocumentServerSecret())) { - if (!empty($token)) { - try { - $payload = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); - } catch (\UnexpectedValueException $e) { - $this->logger->logException($e, ["message" => "Track with invalid jwt in body", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - } else { - $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); - if (empty($header)) { - $this->logger->error("Track without jwt", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - - $header = substr($header, \strlen("Bearer ")); - - try { - $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); - - $payload = $decodedHeader->payload; - } catch (\UnexpectedValueException $e) { - $this->logger->logException($e, ["message" => "Track with invalid jwt", "app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - } - - $users = isset($payload->users) ? $payload->users : null; - $key = $payload->key; - $status = $payload->status; - $url = isset($payload->url) ? $payload->url : null; - } - - $result = 1; - switch ($status) { - case self::TrackerStatus_MustSave: - case self::TrackerStatus_Corrupted: - case self::TrackerStatus_ForceSave: - case self::TrackerStatus_CorruptedForceSave: - if (empty($url)) { - $this->logger->error("Track without url: $fileId status $status", ["app" => $this->appName]); - return new JSONResponse(["message" => "Url not found"], Http::STATUS_BAD_REQUEST); - } - - try { - $shareToken = isset($hashData->shareToken) ? $hashData->shareToken : null; - $filePath = null; - - \OC_Util::tearDownFS(); - - $isForcesave = $status === self::TrackerStatus_ForceSave || $status === self::TrackerStatus_CorruptedForceSave; - - // author of the latest changes - $userId = $this->parseUserId($users[0]); - - if ($isForcesave - && $forcesavetype === 1 - && !empty($actions)) { - // the user who clicked Save - $userId = $this->parseUserId($actions[0]["userid"]); - } - - $user = $this->userManager->get($userId); - if (!empty($user)) { - \OC_User::setUserId($userId); - } else { - if (empty($shareToken)) { - $this->logger->error("Track without token: $fileId status $status", ["app" => $this->appName]); - return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); - } - - $this->logger->debug("Track $fileId by token for $userId", ["app" => $this->appName]); - } - - // owner of file from the callback link - $ownerId = $hashData->ownerId; - $owner = $this->userManager->get($ownerId); - - if (!empty($owner)) { - $userId = $ownerId; - } else { - $callbackUserId = $hashData->userId; - $callbackUser = $this->userManager->get($callbackUserId); - - if (!empty($callbackUser)) { - // author of the callback link - $userId = $callbackUserId; - - // path for author of the callback link - $filePath = $hashData->filePath; - } - } - - if ($this->config->checkEncryptionModule() === "master") { - \OC_User::setIncognitoMode(true); - } elseif (!empty($userId)) { - \OC_Util::setupFS($userId); - } - - list($file, $error) = empty($shareToken) ? $this->getFile($userId, $fileId, $filePath) : $this->getFileByToken($fileId, $shareToken); - - if (isset($error)) { - $this->logger->error("track error: $fileId " . json_encode($error->getData()), ["app" => $this->appName]); - return $error; - } - - $url = $this->config->ReplaceDocumentServerUrlToInternal($url); - - $prevVersion = $file->getFileInfo()->getMtime(); - $fileName = $file->getName(); - $curExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); - $downloadExt = $filetype; - - $documentService = new DocumentService($this->trans, $this->config); - if ($downloadExt !== $curExt) { - $key = DocumentService::GenerateRevisionId($fileId . $url); - - try { - $this->logger->debug("Converted from $downloadExt to $curExt", ["app" => $this->appName]); - $url = $documentService->GetConvertedUri($url, $downloadExt, $curExt, $key); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Converted on save error", "app" => $this->appName]); - return new JSONResponse(["message" => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); - } - } - - $newData = $documentService->Request($url); - - $prevIsForcesave = KeyManager::wasForcesave($fileId); - - if (RemoteInstance::isRemoteFile($file)) { - $isLock = RemoteInstance::lockRemoteKey($file, $isForcesave, null); - if ($isForcesave && !$isLock) { - break; - } - } else { - KeyManager::lock($fileId, $isForcesave); - } - - $this->logger->debug("Track put content " . $file->getPath(), ["app" => $this->appName]); - $this->retryOperation(function () use ($file, $newData) { - return $file->putContent($newData); - }); - - if (RemoteInstance::isRemoteFile($file)) { - if ($isForcesave) { - RemoteInstance::lockRemoteKey($file, false, $isForcesave); - } - } else { - KeyManager::lock($fileId, false); - KeyManager::setForcesave($fileId, $isForcesave); - } - - if (!$isForcesave - && !$prevIsForcesave - && $this->versionManager->available - && $this->config->GetVersionHistory()) { - $changes = null; - if (!empty($changesurl)) { - $changesurl = $this->config->ReplaceDocumentServerUrlToInternal($changesurl); - $changes = $documentService->Request($changesurl); - } - FileVersions::saveHistory($file->getFileInfo(), $history, $changes, $prevVersion); - } - - if (!empty($user) && $this->config->GetVersionHistory()) { - FileVersions::saveAuthor($file->getFileInfo(), $user); - } - - if ($this->config->checkEncryptionModule() === "master" - && !$isForcesave) { - KeyManager::delete($fileId); - } - - $result = 0; - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Track: $fileId status $status error", "app" => $this->appName]); - } - break; - - case self::TrackerStatus_Editing: - case self::TrackerStatus_Closed: - $result = 0; - break; - } - - $this->logger->debug("Track: $fileId status $status result $result", ["app" => $this->appName]); - - return new JSONResponse(["error" => $result], Http::STATUS_OK); - } - - /** - * Getting file by identifier - * - * @param string $userId - user identifier - * @param integer $fileId - file identifier - * @param string $filePath - file path - * @param integer $version - file version - * @param bool $template - file is template - * - * @return array - */ - private function getFile($userId, $fileId, $filePath = null, $version = 0, $template = false) { - if (empty($fileId)) { - return [null, new JSONResponse(["message" => $this->trans->t("FileId is empty")], Http::STATUS_BAD_REQUEST)]; - } - - try { - $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); - $files = $folder->getById($fileId); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); - return [null, new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST)]; - } - - if (empty($files)) { - $this->logger->error("Files not found: $fileId", ["app" => $this->appName]); - return [null, new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND)]; - } - - $file = $files[0]; - - if (\count($files) > 1 && !empty($filePath)) { - $filePath = "/" . $userId . "/files" . $filePath; - foreach ($files as $curFile) { - if ($curFile->getPath() === $filePath) { - $file = $curFile; - break; - } - } - } - - if (!($file instanceof File)) { - $this->logger->error("File not found: $fileId", ["app" => $this->appName]); - return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; - } - - if ($version > 0 && $this->versionManager->available) { - $owner = $file->getFileInfo()->getOwner(); - - if ($owner !== null) { - if ($owner->getUID() !== $userId) { - list($file, $error) = $this->getFile($owner->getUID(), $file->getId()); - - if (isset($error)) { - return [null, $error]; - } - } - - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - - if ($version <= \count($versions)) { - $fileVersion = array_values($versions)[$version - 1]; - $file = $this->versionManager->getVersionFile($owner, $file->getFileInfo(), $fileVersion->getRevisionId()); - } - } - } - - return [$file, null]; - } - - /** - * Getting file by token - * - * @param integer $fileId - file identifier - * @param string $shareToken - access token - * @param integer $version - file version - * - * @return array - */ - private function getFileByToken($fileId, $shareToken, $version = 0) { - list($share, $error) = $this->getShare($shareToken); - - if (isset($error)) { - return [null, $error]; - } - - try { - $node = $share->getNode(); - } catch (NotFoundException $e) { - $this->logger->logException($e, ["message" => "getFileByToken error", "app" => $this->appName]); - return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; - } - - if ($node instanceof Folder) { - try { - $files = $node->getById($fileId); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFileByToken: $fileId", "app" => $this->appName]); - return [null, new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_NOT_FOUND)]; - } - - if (empty($files)) { - return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; - } - $file = $files[0]; - } else { - $file = $node; - } - - if ($version > 0 && $this->versionManager->available) { - $owner = $file->getFileInfo()->getOwner(); - - if ($owner !== null) { - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - - if ($version <= \count($versions)) { - $fileVersion = array_values($versions)[$version - 1]; - $file = $this->versionManager->getVersionFile($owner, $file->getFileInfo(), $fileVersion->getRevisionId()); - } - } - } - - return [$file, null]; - } - - /** - * Getting share by token - * - * @param string $shareToken - access token - * - * @return array - */ - private function getShare($shareToken) { - if (empty($shareToken)) { - return [null, new JSONResponse(["message" => $this->trans->t("FileId is empty")], Http::STATUS_BAD_REQUEST)]; - } - - $share = null; - try { - $share = $this->shareManager->getShareByToken($shareToken); - } catch (ShareNotFound $e) { - $this->logger->logException($e, ["message" => "getShare error", "app" => $this->appName]); - $share = null; - } - - if ($share === null || $share === false) { - return [null, new JSONResponse(["message" => $this->trans->t("You do not have enough permissions to view the file")], Http::STATUS_FORBIDDEN)]; - } - - return [$share, null]; - } - - /** - * Parse user identifier for current instance - * - * @param string $userId - unique user identifier - * - * @return string - */ - private function parseUserId($userId) { - $instanceId = $this->config->GetSystemValue("instanceid", true); - $instanceId = $instanceId . "_"; - - if (substr($userId, 0, \strlen($instanceId)) === $instanceId) { - return substr($userId, \strlen($instanceId)); - } - - return $userId; - } - - /** - * Retry operation if a LockedException occurred - * Other exceptions will still be thrown - * - * @param callable $operation - * - * @throws LockedException - */ - private function retryOperation(callable $operation) { - $i = 0; - while (true) { - try { - return $operation(); - } catch (LockedException $e) { - if (++$i === 4) { - throw $e; - } - } - usleep(500000); - } - } + /** + * Root folder + * + * @var IRootFolder + */ + private $root; + + /** + * User session + * + * @var IUserSession + */ + private $userSession; + + /** + * User manager + * + * @var IUserManager + */ + private $userManager; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Logger + * + * @var OCP\ILogger + */ + private $logger; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * Share manager + * + * @var IManager + */ + private $shareManager; + + /** + * File version manager + * + * @var VersionManager + */ + private $versionManager; + + /** + * Status of the document + */ + private const TrackerStatus_Editing = 1; + private const TrackerStatus_MustSave = 2; + private const TrackerStatus_Corrupted = 3; + private const TrackerStatus_Closed = 4; + private const TrackerStatus_ForceSave = 6; + private const TrackerStatus_CorruptedForceSave = 7; + + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IRootFolder $root - root folder + * @param IUserSession $userSession - user session + * @param IUserManager $userManager - user manager + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param AppConfig $config - application configuration + * @param Crypt $crypt - hash generator + * @param IManager $shareManager - Share manager + */ + public function __construct( + $AppName, + IRequest $request, + IRootFolder $root, + IUserSession $userSession, + IUserManager $userManager, + IL10N $trans, + ILogger $logger, + AppConfig $config, + Crypt $crypt, + IManager $shareManager + ) { + parent::__construct($AppName, $request); + + $this->root = $root; + $this->userSession = $userSession; + $this->userManager = $userManager; + $this->trans = $trans; + $this->logger = $logger; + $this->config = $config; + $this->crypt = $crypt; + $this->shareManager = $shareManager; + + $this->versionManager = new VersionManager($AppName, $root); + } + + /** + * Downloading file by the document service + * + * @param string $doc - verification token with the file identifier + * + * @return DataDownloadResponse|JSONResponse + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * @CORS + */ + public function download($doc) { + list($hashData, $error) = $this->crypt->ReadHash($doc); + if ($hashData === null) { + $this->logger->error("Download with empty or not correct hash: $error", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + if ($hashData->action !== "download") { + $this->logger->error("Download with other action", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); + } + + $fileId = $hashData->fileId; + $version = isset($hashData->version) ? $hashData->version : null; + $changes = isset($hashData->changes) ? $hashData->changes : false; + $template = isset($hashData->template) ? $hashData->template : false; + $this->logger->debug("Download: $fileId ($version)" . ($changes ? " changes" : ""), ["app" => $this->appName]); + + if (!$this->userSession->isLoggedIn() + && !$changes) { + if (!empty($this->config->GetDocumentServerSecret())) { + $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); + if (empty($header)) { + $this->logger->error("Download without jwt", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + + $header = substr($header, \strlen("Bearer ")); + + try { + $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); + } catch (\UnexpectedValueException $e) { + $this->logger->logException($e, ["message" => "Download with invalid jwt", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + } + } + + $userId = null; + + $user = null; + if ($this->userSession->isLoggedIn()) { + $user = $this->userSession->getUser(); + $userId = $user->getUID(); + } else { + \OC_Util::tearDownFS(); + + if (isset($hashData->userId)) { + $userId = $hashData->userId; + + $user = $this->userManager->get($userId); + if (!empty($user)) { + \OC_User::setUserId($userId); + } + + if ($this->config->checkEncryptionModule() === "master") { + \OC_User::setIncognitoMode(true); + } else { + if (!empty($user)) { + \OC_Util::setupFS($userId); + } + } + } + } + + $shareToken = isset($hashData->shareToken) ? $hashData->shareToken : null; + list($file, $error) = empty($shareToken) ? $this->getFile($userId, $fileId, null, $changes ? null : $version, $template) : $this->getFileByToken($fileId, $shareToken, $changes ? null : $version); + + if (isset($error)) { + return $error; + } + + if ($this->userSession->isLoggedIn() && !$file->isReadable()) { + $this->logger->error("Download without access right", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + + if (empty($user) + && $this->config->checkEncryptionModule() !== "master") { + $owner = $file->getFileInfo()->getOwner(); + if ($owner !== null) { + \OC_Util::setupFS($owner->getUID()); + } + } + + if ($changes) { + if ($this->versionManager->available !== true) { + $this->logger->error("Download changes: versionManager is null", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); + } + + $owner = $file->getFileInfo()->getOwner(); + if ($owner === null) { + $this->logger->error("Download: changes owner of $fileId was not found", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND); + } + + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + + $versionId = null; + if ($version > \count($versions)) { + $versionId = $file->getFileInfo()->getMtime(); + } else { + $fileVersion = array_values($versions)[$version - 1]; + + $versionId = $fileVersion->getRevisionId(); + } + + $changesFile = FileVersions::getChangesFile($owner->getUID(), $fileId, $versionId); + if ($changesFile === null) { + $this->logger->error("Download: changes $fileId ($version) was not found", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND); + } + + $file = $changesFile; + } + + try { + $response = new DataDownloadResponse($file->getContent(), $file->getName(), $file->getMimeType()); + + if ($changes) { + $response = \OC_Response::setOptionsRequestHeaders($response); + } + + return $response; + } catch (NotPermittedException $e) { + $this->logger->logException($e, ["message" => "Download Not permitted: $fileId ($version)", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Not permitted")], Http::STATUS_FORBIDDEN); + } + return new JSONResponse(["message" => $this->trans->t("Download failed")], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + /** + * Downloading empty file by the document service + * + * @param string $doc - verification token with the file identifier + * + * @return DataDownloadResponse|JSONResponse + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * @CORS + */ + public function emptyfile($doc) { + $this->logger->debug("Download empty", ["app" => $this->appName]); + + list($hashData, $error) = $this->crypt->ReadHash($doc); + if ($hashData === null) { + $this->logger->error("Download empty with empty or not correct hash: $error", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + if ($hashData->action !== "empty") { + $this->logger->error("Download empty with other action", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); + } + + if (!empty($this->config->GetDocumentServerSecret())) { + $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); + if (empty($header)) { + $this->logger->error("Download empty without jwt", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + + $header = substr($header, \strlen("Bearer ")); + + try { + $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); + } catch (\UnexpectedValueException $e) { + $this->logger->logException($e, ["message" => "Download empty with invalid jwt", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + } + + $templatePath = TemplateManager::GetEmptyTemplatePath("en", ".docx"); + + $template = file_get_contents($templatePath); + if (!$template) { + $this->logger->info("Template for download empty not found: $templatePath", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND); + } + + try { + return new DataDownloadResponse($template, "new.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + } catch (NotPermittedException $e) { + $this->logger->logException($e, ["message" => "Download Not permitted", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Not permitted")], Http::STATUS_FORBIDDEN); + } + return new JSONResponse(["message" => $this->trans->t("Download failed")], Http::STATUS_INTERNAL_SERVER_ERROR); + } + + /** + * Handle request from the document server with the document status information + * + * @param string $doc - verification token with the file identifier + * @param array $users - the list of the identifiers of the users + * @param string $key - the edited document identifier + * @param integer $status - the edited status + * @param string $url - the link to the edited document to be saved + * @param string $token - request signature + * @param array $history - file history + * @param string $changesurl - link to file changes + * @param integer $forcesavetype - the type of force save action + * @param array $actions - the array of action + * @param string $filetype - extension of the document that is downloaded from the link specified with the url parameter + * + * @return array + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + * @CORS + */ + public function track($doc, $users, $key, $status, $url, $token, $history, $changesurl, $forcesavetype, $actions, $filetype) { + list($hashData, $error) = $this->crypt->ReadHash($doc); + if ($hashData === null) { + $this->logger->error("Track with empty or not correct hash: $error", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + if ($hashData->action !== "track") { + $this->logger->error("Track with other action", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST); + } + + $fileId = $hashData->fileId; + $this->logger->debug("Track: $fileId status $status", ["app" => $this->appName]); + + if (!empty($this->config->GetDocumentServerSecret())) { + if (!empty($token)) { + try { + $payload = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); + } catch (\UnexpectedValueException $e) { + $this->logger->logException($e, ["message" => "Track with invalid jwt in body", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + } else { + $header = \OC::$server->getRequest()->getHeader($this->config->JwtHeader()); + if (empty($header)) { + $this->logger->error("Track without jwt", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + + $header = substr($header, \strlen("Bearer ")); + + try { + $decodedHeader = \Firebase\JWT\JWT::decode($header, new \Firebase\JWT\Key($this->config->GetDocumentServerSecret(), "HS256")); + + $payload = $decodedHeader->payload; + } catch (\UnexpectedValueException $e) { + $this->logger->logException($e, ["message" => "Track with invalid jwt", "app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + } + + $users = isset($payload->users) ? $payload->users : null; + $key = $payload->key; + $status = $payload->status; + $url = isset($payload->url) ? $payload->url : null; + } + + $result = 1; + switch ($status) { + case self::TrackerStatus_MustSave: + case self::TrackerStatus_Corrupted: + case self::TrackerStatus_ForceSave: + case self::TrackerStatus_CorruptedForceSave: + if (empty($url)) { + $this->logger->error("Track without url: $fileId status $status", ["app" => $this->appName]); + return new JSONResponse(["message" => "Url not found"], Http::STATUS_BAD_REQUEST); + } + + try { + $shareToken = isset($hashData->shareToken) ? $hashData->shareToken : null; + $filePath = null; + + \OC_Util::tearDownFS(); + + $isForcesave = $status === self::TrackerStatus_ForceSave || $status === self::TrackerStatus_CorruptedForceSave; + + // author of the latest changes + $userId = $this->parseUserId($users[0]); + + if ($isForcesave + && $forcesavetype === 1 + && !empty($actions)) { + // the user who clicked Save + $userId = $this->parseUserId($actions[0]["userid"]); + } + + $user = $this->userManager->get($userId); + if (!empty($user)) { + \OC_User::setUserId($userId); + } else { + if (empty($shareToken)) { + $this->logger->error("Track without token: $fileId status $status", ["app" => $this->appName]); + return new JSONResponse(["message" => $this->trans->t("Access denied")], Http::STATUS_FORBIDDEN); + } + + $this->logger->debug("Track $fileId by token for $userId", ["app" => $this->appName]); + } + + // owner of file from the callback link + $ownerId = $hashData->ownerId; + $owner = $this->userManager->get($ownerId); + + if (!empty($owner)) { + $userId = $ownerId; + } else { + $callbackUserId = $hashData->userId; + $callbackUser = $this->userManager->get($callbackUserId); + + if (!empty($callbackUser)) { + // author of the callback link + $userId = $callbackUserId; + + // path for author of the callback link + $filePath = $hashData->filePath; + } + } + + if ($this->config->checkEncryptionModule() === "master") { + \OC_User::setIncognitoMode(true); + } elseif (!empty($userId)) { + \OC_Util::setupFS($userId); + } + + list($file, $error) = empty($shareToken) ? $this->getFile($userId, $fileId, $filePath) : $this->getFileByToken($fileId, $shareToken); + + if (isset($error)) { + $this->logger->error("track error: $fileId " . json_encode($error->getData()), ["app" => $this->appName]); + return $error; + } + + $url = $this->config->ReplaceDocumentServerUrlToInternal($url); + + $prevVersion = $file->getFileInfo()->getMtime(); + $fileName = $file->getName(); + $curExt = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + $downloadExt = $filetype; + + $documentService = new DocumentService($this->trans, $this->config); + if ($downloadExt !== $curExt) { + $key = DocumentService::GenerateRevisionId($fileId . $url); + + try { + $this->logger->debug("Converted from $downloadExt to $curExt", ["app" => $this->appName]); + $url = $documentService->GetConvertedUri($url, $downloadExt, $curExt, $key); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Converted on save error", "app" => $this->appName]); + return new JSONResponse(["message" => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); + } + } + + $newData = $documentService->Request($url); + + $prevIsForcesave = KeyManager::wasForcesave($fileId); + + if (RemoteInstance::isRemoteFile($file)) { + $isLock = RemoteInstance::lockRemoteKey($file, $isForcesave, null); + if ($isForcesave && !$isLock) { + break; + } + } else { + KeyManager::lock($fileId, $isForcesave); + } + + $this->logger->debug("Track put content " . $file->getPath(), ["app" => $this->appName]); + $this->retryOperation(function () use ($file, $newData) { + return $file->putContent($newData); + }); + + if (RemoteInstance::isRemoteFile($file)) { + if ($isForcesave) { + RemoteInstance::lockRemoteKey($file, false, $isForcesave); + } + } else { + KeyManager::lock($fileId, false); + KeyManager::setForcesave($fileId, $isForcesave); + } + + if (!$isForcesave + && !$prevIsForcesave + && $this->versionManager->available + && $this->config->GetVersionHistory()) { + $changes = null; + if (!empty($changesurl)) { + $changesurl = $this->config->ReplaceDocumentServerUrlToInternal($changesurl); + $changes = $documentService->Request($changesurl); + } + FileVersions::saveHistory($file->getFileInfo(), $history, $changes, $prevVersion); + } + + if (!empty($user) && $this->config->GetVersionHistory()) { + FileVersions::saveAuthor($file->getFileInfo(), $user); + } + + if ($this->config->checkEncryptionModule() === "master" + && !$isForcesave) { + KeyManager::delete($fileId); + } + + $result = 0; + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Track: $fileId status $status error", "app" => $this->appName]); + } + break; + + case self::TrackerStatus_Editing: + case self::TrackerStatus_Closed: + $result = 0; + break; + } + + $this->logger->debug("Track: $fileId status $status result $result", ["app" => $this->appName]); + + return new JSONResponse(["error" => $result], Http::STATUS_OK); + } + + /** + * Getting file by identifier + * + * @param string $userId - user identifier + * @param integer $fileId - file identifier + * @param string $filePath - file path + * @param integer $version - file version + * @param bool $template - file is template + * + * @return array + */ + private function getFile($userId, $fileId, $filePath = null, $version = 0, $template = false) { + if (empty($fileId)) { + return [null, new JSONResponse(["message" => $this->trans->t("FileId is empty")], Http::STATUS_BAD_REQUEST)]; + } + + try { + $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); + $files = $folder->getById($fileId); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); + return [null, new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_BAD_REQUEST)]; + } + + if (empty($files)) { + $this->logger->error("Files not found: $fileId", ["app" => $this->appName]); + return [null, new JSONResponse(["message" => $this->trans->t("Files not found")], Http::STATUS_NOT_FOUND)]; + } + + $file = $files[0]; + + if (\count($files) > 1 && !empty($filePath)) { + $filePath = "/" . $userId . "/files" . $filePath; + foreach ($files as $curFile) { + if ($curFile->getPath() === $filePath) { + $file = $curFile; + break; + } + } + } + + if (!($file instanceof File)) { + $this->logger->error("File not found: $fileId", ["app" => $this->appName]); + return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; + } + + if ($version > 0 && $this->versionManager->available) { + $owner = $file->getFileInfo()->getOwner(); + + if ($owner !== null) { + if ($owner->getUID() !== $userId) { + list($file, $error) = $this->getFile($owner->getUID(), $file->getId()); + + if (isset($error)) { + return [null, $error]; + } + } + + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + + if ($version <= \count($versions)) { + $fileVersion = array_values($versions)[$version - 1]; + $file = $this->versionManager->getVersionFile($owner, $file->getFileInfo(), $fileVersion->getRevisionId()); + } + } + } + + return [$file, null]; + } + + /** + * Getting file by token + * + * @param integer $fileId - file identifier + * @param string $shareToken - access token + * @param integer $version - file version + * + * @return array + */ + private function getFileByToken($fileId, $shareToken, $version = 0) { + list($share, $error) = $this->getShare($shareToken); + + if (isset($error)) { + return [null, $error]; + } + + try { + $node = $share->getNode(); + } catch (NotFoundException $e) { + $this->logger->logException($e, ["message" => "getFileByToken error", "app" => $this->appName]); + return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; + } + + if ($node instanceof Folder) { + try { + $files = $node->getById($fileId); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFileByToken: $fileId", "app" => $this->appName]); + return [null, new JSONResponse(["message" => $this->trans->t("Invalid request")], Http::STATUS_NOT_FOUND)]; + } + + if (empty($files)) { + return [null, new JSONResponse(["message" => $this->trans->t("File not found")], Http::STATUS_NOT_FOUND)]; + } + $file = $files[0]; + } else { + $file = $node; + } + + if ($version > 0 && $this->versionManager->available) { + $owner = $file->getFileInfo()->getOwner(); + + if ($owner !== null) { + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + + if ($version <= \count($versions)) { + $fileVersion = array_values($versions)[$version - 1]; + $file = $this->versionManager->getVersionFile($owner, $file->getFileInfo(), $fileVersion->getRevisionId()); + } + } + } + + return [$file, null]; + } + + /** + * Getting share by token + * + * @param string $shareToken - access token + * + * @return array + */ + private function getShare($shareToken) { + if (empty($shareToken)) { + return [null, new JSONResponse(["message" => $this->trans->t("FileId is empty")], Http::STATUS_BAD_REQUEST)]; + } + + $share = null; + try { + $share = $this->shareManager->getShareByToken($shareToken); + } catch (ShareNotFound $e) { + $this->logger->logException($e, ["message" => "getShare error", "app" => $this->appName]); + $share = null; + } + + if ($share === null || $share === false) { + return [null, new JSONResponse(["message" => $this->trans->t("You do not have enough permissions to view the file")], Http::STATUS_FORBIDDEN)]; + } + + return [$share, null]; + } + + /** + * Parse user identifier for current instance + * + * @param string $userId - unique user identifier + * + * @return string + */ + private function parseUserId($userId) { + $instanceId = $this->config->GetSystemValue("instanceid", true); + $instanceId = $instanceId . "_"; + + if (substr($userId, 0, \strlen($instanceId)) === $instanceId) { + return substr($userId, \strlen($instanceId)); + } + + return $userId; + } + + /** + * Retry operation if a LockedException occurred + * Other exceptions will still be thrown + * + * @param callable $operation + * + * @throws LockedException + */ + private function retryOperation(callable $operation) { + $i = 0; + while (true) { + try { + return $operation(); + } catch (LockedException $e) { + if (++$i === 4) { + throw $e; + } + } + usleep(500000); + } + } } diff --git a/controller/editorapicontroller.php b/controller/editorapicontroller.php index d1c5851e..bd212e99 100644 --- a/controller/editorapicontroller.php +++ b/controller/editorapicontroller.php @@ -49,723 +49,723 @@ * Controller with the main functions */ class EditorApiController extends OCSController { - /** - * Current user session - * - * @var IUserSession - */ - private $userSession; - - /** - * Root folder - * - * @var IRootFolder - */ - private $root; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * File utility - * - * @var FileUtility - */ - private $fileUtility; - - /** - * File version manager - * - * @var VersionManager - */ - private $versionManager; - - /** - * Tag manager - * - * @var ITagManager - */ - private $tagManager; - - /** - * Mobile regex from https://github.com/ONLYOFFICE/CommunityServer/blob/v9.1.1/web/studio/ASC.Web.Studio/web.appsettings.config#L35 - */ - public const USER_AGENT_MOBILE = "/android|avantgo|playbook|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i"; - - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IRootFolder $root - root folder - * @param IUserSession $userSession - current user session - * @param IURLGenerator $urlGenerator - url generator service - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param AppConfig $config - application configuration - * @param Crypt $crypt - hash generator - * @param IManager $shareManager - Share manager - * @param ISession $ISession - Session - * @param ITagManager $tagManager - Tag manager - */ - public function __construct( - $AppName, - IRequest $request, - IRootFolder $root, - IUserSession $userSession, - IURLGenerator $urlGenerator, - IL10N $trans, - ILogger $logger, - AppConfig $config, - Crypt $crypt, - IManager $shareManager, - ISession $session, - ITagManager $tagManager - ) { - parent::__construct($AppName, $request); - - $this->userSession = $userSession; - $this->root = $root; - $this->urlGenerator = $urlGenerator; - $this->trans = $trans; - $this->logger = $logger; - $this->config = $config; - $this->crypt = $crypt; - $this->tagManager = $tagManager; - - $this->versionManager = new VersionManager($AppName, $root); - - $this->fileUtility = new FileUtility($AppName, $trans, $logger, $config, $shareManager, $session); - } - - /** - * Filling empty file an template - * - * @param int $fileId - file identificator - * - * @return JSONResponse - * - * @NoAdminRequired - * @PublicPage - */ - public function fillempty($fileId) { - $this->logger->debug("Fill empty: $fileId", ["app" => $this->appName]); - - if (empty($fileId)) { - $this->logger->error("File for filling was not found: $fileId", ["app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("FileId is empty")]); - } - - $userId = $this->userSession->getUser()->getUID(); - - list($file, $error, $share) = $this->getFile($userId, $fileId); - if (isset($error)) { - $this->logger->error("Fill empty: $fileId $error", ["app" => $this->appName]); - return new JSONResponse(["error" => $error]); - } - - if ($file->getSize() > 0) { - $this->logger->error("File is't empty: $fileId", ["app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("Not permitted")]); - } - - if (!$file->isUpdateable()) { - $this->logger->error("File without permission: $fileId", ["app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("Not permitted")]); - } - - $name = $file->getName(); - $template = TemplateManager::GetEmptyTemplate($name); - - if (!$template) { - $this->logger->error("Template for file filling not found: $name ($fileId)", ["app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("Template not found")]); - } - - try { - $file->putContent($template); - } catch (NotPermittedException $e) { - $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("Can't create file")]); - } - - return new JSONResponse([ - ]); - } - - /** - * Collecting the file parameters for the document service - * - * @param integer $fileId - file identifier - * @param string $filePath - file path - * @param string $shareToken - access token - * @param integer $version - file version - * @param bool $inframe - open in frame - * @param bool $desktop - desktop label - * @param bool $template - file is template - * - * @return JSONResponse - * - * @NoAdminRequired - * @PublicPage - * @CORS - */ - public function config($fileId, $filePath = null, $shareToken = null, $version = 0, $inframe = false, $desktop = false, $template = false, $anchor = null) { - $user = $this->userSession->getUser(); - $userId = null; - $accountId = null; - if (!empty($user)) { - $userId = $user->getUID(); - $accountId = $user->getAccountId(); - } - - list($file, $error, $share) = empty($shareToken) ? $this->getFile($userId, $fileId, $filePath, $template) : $this->fileUtility->getFileByToken($fileId, $shareToken); - - if (isset($error)) { - $this->logger->error("Config: $fileId $error", ["app" => $this->appName]); - return new JSONResponse(["error" => $error]); - } - - $checkUserAllowGroups = $userId; - if (!empty($share)) { - $checkUserAllowGroups = $share->getSharedBy(); - } - if (!$this->config->isUserAllowedToUse($checkUserAllowGroups)) { - return new JSONResponse(["error" => $this->trans->t("Not permitted")]); - } - - $fileName = $file->getName(); - $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); - $format = !empty($ext) && \array_key_exists($ext, $this->config->FormatsSetting()) ? $this->config->FormatsSetting()[$ext] : null; - if (!isset($format)) { - $this->logger->info("Format is not supported for editing: $fileName", ["app" => $this->appName]); - return new JSONResponse(["error" => $this->trans->t("Format is not supported")]); - } - - $fileUrl = $this->getUrl($file, $user, $shareToken, $version, null, $template); - - $key = null; - if ($version > 0 - && $this->versionManager->available) { - $owner = $file->getFileInfo()->getOwner(); - if ($owner !== null) { - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - - if ($version <= \count($versions)) { - $fileVersion = array_values($versions)[$version - 1]; - - $key = $this->fileUtility->getVersionKey($fileVersion); - } - } - } - if ($key === null) { - $key = $this->fileUtility->getKey($file, true); - } - $key = DocumentService::GenerateRevisionId($key); - - $params = [ - "document" => [ - "fileType" => $ext, - "key" => $key, - "permissions" => [], - "title" => $fileName, - "url" => $fileUrl, - "referenceData" => [ - "fileKey" => $file->getId(), - "instanceId" => $this->config->GetSystemValue("instanceid", true), - ], - ], - "documentType" => $format["type"], - "editorConfig" => [ - "lang" => str_replace("_", "-", \OC::$server->getL10NFactory("")->get("")->getLanguageCode()) - ] - ]; - - $restrictedEditing = false; - $fileStorage = $file->getStorage(); - if (empty($shareToken) && $fileStorage->instanceOfStorage("\OCA\Files_Sharing\SharedStorage")) { - $storageShare = $fileStorage->getShare(); - if (method_exists($storageShare, "getAttributes")) { - $attributes = $storageShare->getAttributes(); - - $permissionsDownload = $attributes->getAttribute("permissions", "download"); - if ($permissionsDownload !== null) { - $params["document"]["permissions"]["download"] = $params["document"]["permissions"]["print"] = $params["document"]["permissions"]["copy"] = $permissionsDownload === true; - } - - if (isset($format["review"]) && $format["review"]) { - $permissionsReviewOnly = $attributes->getAttribute($this->appName, "review"); - if ($permissionsReviewOnly !== null && $permissionsReviewOnly === true) { - $restrictedEditing = true; - $params["document"]["permissions"]["review"] = true; - } - } - - if (isset($format["fillForms"]) && $format["fillForms"]) { - $permissionsFillFormsOnly = $attributes->getAttribute($this->appName, "fillForms"); - if ($permissionsFillFormsOnly !== null && $permissionsFillFormsOnly === true) { - $restrictedEditing = true; - $params["document"]["permissions"]["fillForms"] = true; - } - } - - if (isset($format["comment"]) && $format["comment"]) { - $permissionsCommentOnly = $attributes->getAttribute($this->appName, "comment"); - if ($permissionsCommentOnly !== null && $permissionsCommentOnly === true) { - $restrictedEditing = true; - $params["document"]["permissions"]["comment"] = true; - } - } - - if (isset($format["modifyFilter"]) && $format["modifyFilter"]) { - $permissionsModifyFilter = $attributes->getAttribute($this->appName, "modifyFilter"); - if ($permissionsModifyFilter !== null) { - $params["document"]["permissions"]["modifyFilter"] = $permissionsModifyFilter === true; - } - } - } - } - - $isPersistentLock = false; - if ($version < 1 - && (\OC::$server->getConfig()->getAppValue("files", "enable_lock_file_action", "no") === "yes") - && $fileStorage->instanceOfStorage(IPersistentLockingStorage::class)) { - $locks = $fileStorage->getLocks($file->getFileInfo()->getInternalPath(), false); - if (\count($locks) > 0) { - $activeLock = $locks[0]; - - if ($accountId !== $activeLock->getOwnerAccountId()) { - $isPersistentLock = true; - $lockOwner = $activeLock->getOwner(); - $this->logger->debug("File $fileId is locked by $lockOwner", ["app" => $this->appName]); - } - } - } - - $canEdit = isset($format["edit"]) && $format["edit"]; - $canFillForms = isset($format["fillForms"]) && $format["fillForms"]; - $editable = $version < 1 - && !$template - && $file->isUpdateable() - && !$isPersistentLock - && (empty($shareToken) || ($share->getPermissions() & Constants::PERMISSION_UPDATE) === Constants::PERMISSION_UPDATE); - $params["document"]["permissions"]["edit"] = $editable; - if (($editable || $restrictedEditing) && $canEdit || $canFillForms) { - $ownerId = null; - $owner = $file->getOwner(); - if (!empty($owner)) { - $ownerId = $owner->getUID(); - } - - $canProtect = true; - if ($this->config->GetProtection() === "owner") { - $canProtect = $ownerId === $userId; - } - $params["document"]["permissions"]["protect"] = $canProtect; - - if (isset($shareToken)) { - $params["document"]["permissions"]["chat"] = false; - $params["document"]["permissions"]["protect"] = false; - } - - $hashCallback = $this->crypt->GetHash(["userId" => $userId, "ownerId" => $ownerId, "fileId" => $file->getId(), "filePath" => $filePath, "shareToken" => $shareToken, "action" => "track"]); - $callback = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.track", ["doc" => $hashCallback]); - - if (!$this->config->UseDemo() && !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 (!$template - && $file->isUpdateable() - && !$isPersistentLock - && (empty($shareToken) || ($share->getPermissions() & Constants::PERMISSION_UPDATE) === Constants::PERMISSION_UPDATE)) { - $params["document"]["permissions"]["changeHistory"] = true; - } - - if (!empty($userId)) { - $params["editorConfig"]["user"] = [ - "id" => $this->buildUserId($userId), - "name" => $user->getDisplayName() - ]; - } - - $folderLink = null; - - if (!empty($shareToken)) { - $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); - } - } - } elseif (!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); - } - - switch ($params["documentType"]) { - case "word": - $createName = $this->trans->t("Document") . ".docx"; - break; - case "cell": - $createName = $this->trans->t("Spreadsheet") . ".xlsx"; - break; - case "slide": - $createName = $this->trans->t("Presentation") . ".pptx"; - break; - } - - $createParam = [ - "dir" => "/", - "name" => $createName - ]; - - if (!empty($folderPath)) { - $folder = $userFolder->get($folderPath); - if (!empty($folder) && $folder->isCreatable()) { - $createParam["dir"] = $folderPath; - } - } - - $createUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.create_new", $createParam); - - $params["editorConfig"]["createUrl"] = urldecode($createUrl); - - $templatesList = TemplateManager::GetGlobalTemplates($file->getMimeType()); - if (!empty($templatesList)) { - $templates = []; - foreach ($templatesList as $templateItem) { - $createParam["templateId"] = $templateItem->getId(); - $createParam["name"] = $templateItem->getName(); - - array_push($templates, [ - "image" => "", - "title" => $templateItem->getName(), - "url" => urldecode($this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.create_new", $createParam)) - ]); - } - - $params["editorConfig"]["templates"] = $templates; - } - - if (!$template) { - $params["document"]["info"]["favorite"] = $this->isFavorite($fileId); - } - $params["_file_path"] = $userFolder->getRelativePath($file->getPath()); - } - - if ($folderLink !== null - && $this->config->GetSystemValue($this->config->_customization_goback) !== false) { - $params["editorConfig"]["customization"]["goback"] = [ - "url" => $folderLink - ]; - - if (!$desktop) { - if ($this->config->GetSameTab()) { - $params["editorConfig"]["customization"]["goback"]["blank"] = false; - if ($inframe === true) { - $params["editorConfig"]["customization"]["goback"]["requestClose"] = true; - } - } - } - } - - if ($inframe === true) { - $params["_files_sharing"] = \OC::$server->getAppManager()->isEnabledForUser("files_sharing"); - } - - $params = $this->setCustomization($params); - - if ($this->config->UseDemo()) { - $params["editorConfig"]["tenant"] = $this->config->GetSystemValue("instanceid", true); - } - - if ($anchor !== null) { - try { - $actionLink = json_decode($anchor, true); - - $params["editorConfig"]["actionLink"] = $actionLink; - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Config: $fileId decode $anchor", "app" => $this->appName]); - } - } - - if (!empty($this->config->GetDocumentServerSecret())) { - $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); - $params["token"] = $token; - } - - $this->logger->debug("Config is generated for: $fileId ($version) with key $key", ["app" => $this->appName]); - - return new JSONResponse($params); - } - - /** - * Getting file by identifier - * - * @param string $userId - user identifier - * @param integer $fileId - file identifier - * @param string $filePath - file path - * @param bool $template - file is template - * - * @return array - */ - private function getFile($userId, $fileId, $filePath = null, $template = false) { - if (empty($fileId)) { - return [null, $this->trans->t("FileId is empty"), null]; - } - - try { - $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); - $files = $folder->getById($fileId); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); - return [null, $this->trans->t("Invalid request"), null]; - } - - if (empty($files)) { - $this->logger->info("Files not found: $fileId", ["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; - break; - } - } - } - - 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 File $file - file - * @param IUser $user - user with access - * @param string $shareToken - access token - * @param integer $version - file version - * @param bool $changes - is required url to file changes - * @param bool $template - file is template - * - * @return string - */ - private function getUrl($file, $user = null, $shareToken = null, $version = 0, $changes = false, $template = false) { - $data = [ - "action" => "download", - "fileId" => $file->getId() - ]; - - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - $data["userId"] = $userId; - } - if (!empty($shareToken)) { - $data["shareToken"] = $shareToken; - } - if ($version > 0) { - $data["version"] = $version; - } - if ($changes) { - $data["changes"] = true; - } - if ($template) { - $data["template"] = true; - } - - $hashUrl = $this->crypt->GetHash($data); - - $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); - - if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { - $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); - } - - return $fileUrl; - } - - /** - * Generate unique user identifier - * - * @param string $userId - current user identifier - * - * @return string - */ - private function buildUserId($userId) { - $instanceId = $this->config->GetSystemValue("instanceid", true); - $userId = $instanceId . "_" . $userId; - return $userId; - } - - /** - * 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 false - if ($this->config->GetCustomizationForcesave() === true) { - $params["editorConfig"]["customization"]["forcesave"] = true; - } - - //default is true - if ($this->config->GetCustomizationHelp() === false) { - $params["editorConfig"]["customization"]["help"] = false; - } - - //default is original - $reviewDisplay = $this->config->GetCustomizationReviewDisplay(); - if ($reviewDisplay !== "original") { - $params["editorConfig"]["customization"]["reviewDisplay"] = $reviewDisplay; - } - - $theme = $this->config->GetCustomizationTheme(); - if (isset($theme)) { - $params["editorConfig"]["customization"]["uiTheme"] = $theme; - } - - //default is false - if ($this->config->GetCustomizationToolbarNoTabs() === true) { - $params["editorConfig"]["customization"]["toolbarNoTabs"] = true; - } - - //default is true - if ($this->config->GetCustomizationMacros() === false) { - $params["editorConfig"]["customization"]["macros"] = false; - } - - //default is true - if ($this->config->GetCustomizationPlugins() === false) { - $params["editorConfig"]["customization"]["plugins"] = false; - } - - /* from system config */ - - $autosave = $this->config->GetSystemValue($this->config->_customization_autosave); - if (isset($autosave)) { - $params["editorConfig"]["customization"]["autosave"] = $autosave; - } - - $customer = $this->config->GetSystemValue($this->config->_customization_customer); - if (isset($customer)) { - $params["editorConfig"]["customization"]["customer"] = $customer; - } - - $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; - } - - $zoom = $this->config->GetSystemValue($this->config->_customization_zoom); - if (isset($zoom)) { - $params["editorConfig"]["customization"]["zoom"] = $zoom; - } - - return $params; - } - - /** - * Check file favorite - * - * @param integer $fileId - file identifier - * - * @return bool - */ - private function isFavorite($fileId) { - $currentTags = $this->tagManager->load("files")->getTagsForObjects([$fileId]); - if ($currentTags) { - return \in_array(Tags::TAG_FAVORITE, $currentTags[$fileId]); - } - - return false; - } + /** + * Current user session + * + * @var IUserSession + */ + private $userSession; + + /** + * Root folder + * + * @var IRootFolder + */ + private $root; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * File utility + * + * @var FileUtility + */ + private $fileUtility; + + /** + * File version manager + * + * @var VersionManager + */ + private $versionManager; + + /** + * Tag manager + * + * @var ITagManager + */ + private $tagManager; + + /** + * Mobile regex from https://github.com/ONLYOFFICE/CommunityServer/blob/v9.1.1/web/studio/ASC.Web.Studio/web.appsettings.config#L35 + */ + public const USER_AGENT_MOBILE = "/android|avantgo|playbook|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|symbian|treo|up\\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i"; + + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IRootFolder $root - root folder + * @param IUserSession $userSession - current user session + * @param IURLGenerator $urlGenerator - url generator service + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param AppConfig $config - application configuration + * @param Crypt $crypt - hash generator + * @param IManager $shareManager - Share manager + * @param ISession $ISession - Session + * @param ITagManager $tagManager - Tag manager + */ + public function __construct( + $AppName, + IRequest $request, + IRootFolder $root, + IUserSession $userSession, + IURLGenerator $urlGenerator, + IL10N $trans, + ILogger $logger, + AppConfig $config, + Crypt $crypt, + IManager $shareManager, + ISession $session, + ITagManager $tagManager + ) { + parent::__construct($AppName, $request); + + $this->userSession = $userSession; + $this->root = $root; + $this->urlGenerator = $urlGenerator; + $this->trans = $trans; + $this->logger = $logger; + $this->config = $config; + $this->crypt = $crypt; + $this->tagManager = $tagManager; + + $this->versionManager = new VersionManager($AppName, $root); + + $this->fileUtility = new FileUtility($AppName, $trans, $logger, $config, $shareManager, $session); + } + + /** + * Filling empty file an template + * + * @param int $fileId - file identificator + * + * @return JSONResponse + * + * @NoAdminRequired + * @PublicPage + */ + public function fillempty($fileId) { + $this->logger->debug("Fill empty: $fileId", ["app" => $this->appName]); + + if (empty($fileId)) { + $this->logger->error("File for filling was not found: $fileId", ["app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("FileId is empty")]); + } + + $userId = $this->userSession->getUser()->getUID(); + + list($file, $error, $share) = $this->getFile($userId, $fileId); + if (isset($error)) { + $this->logger->error("Fill empty: $fileId $error", ["app" => $this->appName]); + return new JSONResponse(["error" => $error]); + } + + if ($file->getSize() > 0) { + $this->logger->error("File is't empty: $fileId", ["app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("Not permitted")]); + } + + if (!$file->isUpdateable()) { + $this->logger->error("File without permission: $fileId", ["app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("Not permitted")]); + } + + $name = $file->getName(); + $template = TemplateManager::GetEmptyTemplate($name); + + if (!$template) { + $this->logger->error("Template for file filling not found: $name ($fileId)", ["app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("Template not found")]); + } + + try { + $file->putContent($template); + } catch (NotPermittedException $e) { + $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("Can't create file")]); + } + + return new JSONResponse([ + ]); + } + + /** + * Collecting the file parameters for the document service + * + * @param integer $fileId - file identifier + * @param string $filePath - file path + * @param string $shareToken - access token + * @param integer $version - file version + * @param bool $inframe - open in frame + * @param bool $desktop - desktop label + * @param bool $template - file is template + * + * @return JSONResponse + * + * @NoAdminRequired + * @PublicPage + * @CORS + */ + public function config($fileId, $filePath = null, $shareToken = null, $version = 0, $inframe = false, $desktop = false, $template = false, $anchor = null) { + $user = $this->userSession->getUser(); + $userId = null; + $accountId = null; + if (!empty($user)) { + $userId = $user->getUID(); + $accountId = $user->getAccountId(); + } + + list($file, $error, $share) = empty($shareToken) ? $this->getFile($userId, $fileId, $filePath, $template) : $this->fileUtility->getFileByToken($fileId, $shareToken); + + if (isset($error)) { + $this->logger->error("Config: $fileId $error", ["app" => $this->appName]); + return new JSONResponse(["error" => $error]); + } + + $checkUserAllowGroups = $userId; + if (!empty($share)) { + $checkUserAllowGroups = $share->getSharedBy(); + } + if (!$this->config->isUserAllowedToUse($checkUserAllowGroups)) { + return new JSONResponse(["error" => $this->trans->t("Not permitted")]); + } + + $fileName = $file->getName(); + $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + $format = !empty($ext) && \array_key_exists($ext, $this->config->FormatsSetting()) ? $this->config->FormatsSetting()[$ext] : null; + if (!isset($format)) { + $this->logger->info("Format is not supported for editing: $fileName", ["app" => $this->appName]); + return new JSONResponse(["error" => $this->trans->t("Format is not supported")]); + } + + $fileUrl = $this->getUrl($file, $user, $shareToken, $version, null, $template); + + $key = null; + if ($version > 0 + && $this->versionManager->available) { + $owner = $file->getFileInfo()->getOwner(); + if ($owner !== null) { + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + + if ($version <= \count($versions)) { + $fileVersion = array_values($versions)[$version - 1]; + + $key = $this->fileUtility->getVersionKey($fileVersion); + } + } + } + if ($key === null) { + $key = $this->fileUtility->getKey($file, true); + } + $key = DocumentService::GenerateRevisionId($key); + + $params = [ + "document" => [ + "fileType" => $ext, + "key" => $key, + "permissions" => [], + "title" => $fileName, + "url" => $fileUrl, + "referenceData" => [ + "fileKey" => $file->getId(), + "instanceId" => $this->config->GetSystemValue("instanceid", true), + ], + ], + "documentType" => $format["type"], + "editorConfig" => [ + "lang" => str_replace("_", "-", \OC::$server->getL10NFactory("")->get("")->getLanguageCode()) + ] + ]; + + $restrictedEditing = false; + $fileStorage = $file->getStorage(); + if (empty($shareToken) && $fileStorage->instanceOfStorage("\OCA\Files_Sharing\SharedStorage")) { + $storageShare = $fileStorage->getShare(); + if (method_exists($storageShare, "getAttributes")) { + $attributes = $storageShare->getAttributes(); + + $permissionsDownload = $attributes->getAttribute("permissions", "download"); + if ($permissionsDownload !== null) { + $params["document"]["permissions"]["download"] = $params["document"]["permissions"]["print"] = $params["document"]["permissions"]["copy"] = $permissionsDownload === true; + } + + if (isset($format["review"]) && $format["review"]) { + $permissionsReviewOnly = $attributes->getAttribute($this->appName, "review"); + if ($permissionsReviewOnly !== null && $permissionsReviewOnly === true) { + $restrictedEditing = true; + $params["document"]["permissions"]["review"] = true; + } + } + + if (isset($format["fillForms"]) && $format["fillForms"]) { + $permissionsFillFormsOnly = $attributes->getAttribute($this->appName, "fillForms"); + if ($permissionsFillFormsOnly !== null && $permissionsFillFormsOnly === true) { + $restrictedEditing = true; + $params["document"]["permissions"]["fillForms"] = true; + } + } + + if (isset($format["comment"]) && $format["comment"]) { + $permissionsCommentOnly = $attributes->getAttribute($this->appName, "comment"); + if ($permissionsCommentOnly !== null && $permissionsCommentOnly === true) { + $restrictedEditing = true; + $params["document"]["permissions"]["comment"] = true; + } + } + + if (isset($format["modifyFilter"]) && $format["modifyFilter"]) { + $permissionsModifyFilter = $attributes->getAttribute($this->appName, "modifyFilter"); + if ($permissionsModifyFilter !== null) { + $params["document"]["permissions"]["modifyFilter"] = $permissionsModifyFilter === true; + } + } + } + } + + $isPersistentLock = false; + if ($version < 1 + && (\OC::$server->getConfig()->getAppValue("files", "enable_lock_file_action", "no") === "yes") + && $fileStorage->instanceOfStorage(IPersistentLockingStorage::class)) { + $locks = $fileStorage->getLocks($file->getFileInfo()->getInternalPath(), false); + if (\count($locks) > 0) { + $activeLock = $locks[0]; + + if ($accountId !== $activeLock->getOwnerAccountId()) { + $isPersistentLock = true; + $lockOwner = $activeLock->getOwner(); + $this->logger->debug("File $fileId is locked by $lockOwner", ["app" => $this->appName]); + } + } + } + + $canEdit = isset($format["edit"]) && $format["edit"]; + $canFillForms = isset($format["fillForms"]) && $format["fillForms"]; + $editable = $version < 1 + && !$template + && $file->isUpdateable() + && !$isPersistentLock + && (empty($shareToken) || ($share->getPermissions() & Constants::PERMISSION_UPDATE) === Constants::PERMISSION_UPDATE); + $params["document"]["permissions"]["edit"] = $editable; + if (($editable || $restrictedEditing) && $canEdit || $canFillForms) { + $ownerId = null; + $owner = $file->getOwner(); + if (!empty($owner)) { + $ownerId = $owner->getUID(); + } + + $canProtect = true; + if ($this->config->GetProtection() === "owner") { + $canProtect = $ownerId === $userId; + } + $params["document"]["permissions"]["protect"] = $canProtect; + + if (isset($shareToken)) { + $params["document"]["permissions"]["chat"] = false; + $params["document"]["permissions"]["protect"] = false; + } + + $hashCallback = $this->crypt->GetHash(["userId" => $userId, "ownerId" => $ownerId, "fileId" => $file->getId(), "filePath" => $filePath, "shareToken" => $shareToken, "action" => "track"]); + $callback = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.track", ["doc" => $hashCallback]); + + if (!$this->config->UseDemo() && !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 (!$template + && $file->isUpdateable() + && !$isPersistentLock + && (empty($shareToken) || ($share->getPermissions() & Constants::PERMISSION_UPDATE) === Constants::PERMISSION_UPDATE)) { + $params["document"]["permissions"]["changeHistory"] = true; + } + + if (!empty($userId)) { + $params["editorConfig"]["user"] = [ + "id" => $this->buildUserId($userId), + "name" => $user->getDisplayName() + ]; + } + + $folderLink = null; + + if (!empty($shareToken)) { + $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); + } + } + } elseif (!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); + } + + switch ($params["documentType"]) { + case "word": + $createName = $this->trans->t("Document") . ".docx"; + break; + case "cell": + $createName = $this->trans->t("Spreadsheet") . ".xlsx"; + break; + case "slide": + $createName = $this->trans->t("Presentation") . ".pptx"; + break; + } + + $createParam = [ + "dir" => "/", + "name" => $createName + ]; + + if (!empty($folderPath)) { + $folder = $userFolder->get($folderPath); + if (!empty($folder) && $folder->isCreatable()) { + $createParam["dir"] = $folderPath; + } + } + + $createUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.create_new", $createParam); + + $params["editorConfig"]["createUrl"] = urldecode($createUrl); + + $templatesList = TemplateManager::GetGlobalTemplates($file->getMimeType()); + if (!empty($templatesList)) { + $templates = []; + foreach ($templatesList as $templateItem) { + $createParam["templateId"] = $templateItem->getId(); + $createParam["name"] = $templateItem->getName(); + + array_push($templates, [ + "image" => "", + "title" => $templateItem->getName(), + "url" => urldecode($this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.create_new", $createParam)) + ]); + } + + $params["editorConfig"]["templates"] = $templates; + } + + if (!$template) { + $params["document"]["info"]["favorite"] = $this->isFavorite($fileId); + } + $params["_file_path"] = $userFolder->getRelativePath($file->getPath()); + } + + if ($folderLink !== null + && $this->config->GetSystemValue($this->config->_customization_goback) !== false) { + $params["editorConfig"]["customization"]["goback"] = [ + "url" => $folderLink + ]; + + if (!$desktop) { + if ($this->config->GetSameTab()) { + $params["editorConfig"]["customization"]["goback"]["blank"] = false; + if ($inframe === true) { + $params["editorConfig"]["customization"]["goback"]["requestClose"] = true; + } + } + } + } + + if ($inframe === true) { + $params["_files_sharing"] = \OC::$server->getAppManager()->isEnabledForUser("files_sharing"); + } + + $params = $this->setCustomization($params); + + if ($this->config->UseDemo()) { + $params["editorConfig"]["tenant"] = $this->config->GetSystemValue("instanceid", true); + } + + if ($anchor !== null) { + try { + $actionLink = json_decode($anchor, true); + + $params["editorConfig"]["actionLink"] = $actionLink; + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Config: $fileId decode $anchor", "app" => $this->appName]); + } + } + + if (!empty($this->config->GetDocumentServerSecret())) { + $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); + $params["token"] = $token; + } + + $this->logger->debug("Config is generated for: $fileId ($version) with key $key", ["app" => $this->appName]); + + return new JSONResponse($params); + } + + /** + * Getting file by identifier + * + * @param string $userId - user identifier + * @param integer $fileId - file identifier + * @param string $filePath - file path + * @param bool $template - file is template + * + * @return array + */ + private function getFile($userId, $fileId, $filePath = null, $template = false) { + if (empty($fileId)) { + return [null, $this->trans->t("FileId is empty"), null]; + } + + try { + $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); + $files = $folder->getById($fileId); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); + return [null, $this->trans->t("Invalid request"), null]; + } + + if (empty($files)) { + $this->logger->info("Files not found: $fileId", ["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; + break; + } + } + } + + 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 File $file - file + * @param IUser $user - user with access + * @param string $shareToken - access token + * @param integer $version - file version + * @param bool $changes - is required url to file changes + * @param bool $template - file is template + * + * @return string + */ + private function getUrl($file, $user = null, $shareToken = null, $version = 0, $changes = false, $template = false) { + $data = [ + "action" => "download", + "fileId" => $file->getId() + ]; + + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + $data["userId"] = $userId; + } + if (!empty($shareToken)) { + $data["shareToken"] = $shareToken; + } + if ($version > 0) { + $data["version"] = $version; + } + if ($changes) { + $data["changes"] = true; + } + if ($template) { + $data["template"] = true; + } + + $hashUrl = $this->crypt->GetHash($data); + + $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); + + if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { + $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); + } + + return $fileUrl; + } + + /** + * Generate unique user identifier + * + * @param string $userId - current user identifier + * + * @return string + */ + private function buildUserId($userId) { + $instanceId = $this->config->GetSystemValue("instanceid", true); + $userId = $instanceId . "_" . $userId; + return $userId; + } + + /** + * 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 false + if ($this->config->GetCustomizationForcesave() === true) { + $params["editorConfig"]["customization"]["forcesave"] = true; + } + + //default is true + if ($this->config->GetCustomizationHelp() === false) { + $params["editorConfig"]["customization"]["help"] = false; + } + + //default is original + $reviewDisplay = $this->config->GetCustomizationReviewDisplay(); + if ($reviewDisplay !== "original") { + $params["editorConfig"]["customization"]["reviewDisplay"] = $reviewDisplay; + } + + $theme = $this->config->GetCustomizationTheme(); + if (isset($theme)) { + $params["editorConfig"]["customization"]["uiTheme"] = $theme; + } + + //default is false + if ($this->config->GetCustomizationToolbarNoTabs() === true) { + $params["editorConfig"]["customization"]["toolbarNoTabs"] = true; + } + + //default is true + if ($this->config->GetCustomizationMacros() === false) { + $params["editorConfig"]["customization"]["macros"] = false; + } + + //default is true + if ($this->config->GetCustomizationPlugins() === false) { + $params["editorConfig"]["customization"]["plugins"] = false; + } + + /* from system config */ + + $autosave = $this->config->GetSystemValue($this->config->_customization_autosave); + if (isset($autosave)) { + $params["editorConfig"]["customization"]["autosave"] = $autosave; + } + + $customer = $this->config->GetSystemValue($this->config->_customization_customer); + if (isset($customer)) { + $params["editorConfig"]["customization"]["customer"] = $customer; + } + + $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; + } + + $zoom = $this->config->GetSystemValue($this->config->_customization_zoom); + if (isset($zoom)) { + $params["editorConfig"]["customization"]["zoom"] = $zoom; + } + + return $params; + } + + /** + * Check file favorite + * + * @param integer $fileId - file identifier + * + * @return bool + */ + private function isFavorite($fileId) { + $currentTags = $this->tagManager->load("files")->getTagsForObjects([$fileId]); + if ($currentTags) { + return \in_array(Tags::TAG_FAVORITE, $currentTags[$fileId]); + } + + return false; + } } diff --git a/controller/editorcontroller.php b/controller/editorcontroller.php index 7e32f805..20cfcac4 100644 --- a/controller/editorcontroller.php +++ b/controller/editorcontroller.php @@ -55,1406 +55,1406 @@ * Controller with the main functions */ class EditorController extends Controller { - /** - * Current user session - * - * @var IUserSession - */ - private $userSession; - - /** - * Current user manager - * - * @var IUserManager - */ - private $userManager; - - /** - * Root folder - * - * @var IRootFolder - */ - private $root; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * File utility - * - * @var FileUtility - */ - private $fileUtility; - - /** - * File version manager - * - * @var VersionManager - */ - private $versionManager; - - /** - * Share manager - * - * @var IManager - */ - private $shareManager; - - /** - * Group manager - * - * @var IGroupManager - */ - private $groupManager; - - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IRootFolder $root - root folder - * @param IUserSession $userSession - current user session - * @param IUserManager $userManager - current user manager - * @param IURLGenerator $urlGenerator - url generator service - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param AppConfig $config - application configuration - * @param Crypt $crypt - hash generator - * @param IManager $shareManager - Share manager - * @param ISession $session - Session - * @param IGroupManager $groupManager - Group manager - */ - public function __construct( - $AppName, - IRequest $request, - IRootFolder $root, - IUserSession $userSession, - IUserManager $userManager, - IURLGenerator $urlGenerator, - IL10N $trans, - ILogger $logger, - AppConfig $config, - Crypt $crypt, - IManager $shareManager, - ISession $session, - IGroupManager $groupManager - ) { - parent::__construct($AppName, $request); - - $this->userSession = $userSession; - $this->userManager = $userManager; - $this->root = $root; - $this->urlGenerator = $urlGenerator; - $this->trans = $trans; - $this->logger = $logger; - $this->config = $config; - $this->crypt = $crypt; - $this->shareManager = $shareManager; - $this->groupManager = $groupManager; - - $this->versionManager = new VersionManager($AppName, $root); - - $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 $templateId - file identifier - * @param string $targetPath - file path for using as template for create - * @param string $shareToken - access token - * - * @return array - * - * @NoAdminRequired - * @PublicPage - */ - public function create($name, $dir, $templateId = null, $targetPath = null, $shareToken = null) { - $this->logger->debug("Create: $name", ["app" => $this->appName]); - - if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - if (empty($name)) { - $this->logger->error("File name for creation was not found: $name", ["app" => $this->appName]); - return ["error" => $this->trans->t("Template not found")]; - } - - $user = null; - if (empty($shareToken)) { - $user = $this->userSession->getUser(); - $userId = $user->getUID(); - $userFolder = $this->root->getUserFolder($userId); - } else { - list($userFolder, $error, $share) = $this->fileUtility->getNodeByToken($shareToken); - - if (isset($error)) { - $this->logger->error("Create: $error", ["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", ["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", ["app" => $this->appName]); - return ["error" => $this->trans->t("The required folder was not found")]; - } - if (!($folder->isCreatable() && $folder->isUpdateable())) { - $this->logger->error("Folder for file creation without permission: $dir", ["app" => $this->appName]); - return ["error" => $this->trans->t("You don't have enough permission to create")]; - } - - if (!empty($templateId)) { - $templateFile = TemplateManager::GetTemplate($templateId); - if ($templateFile) { - $template = $templateFile->getContent(); - } - } elseif (!empty($targetPath)) { - $targetFile = $userFolder->get($targetPath); - - $canDownload = $this->fileUtility->hasPermissionAttribute($targetFile); - if (!$canDownload) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $targetId = $targetFile->getId(); - $targetName = $targetFile->getName(); - $targetExt = strtolower(pathinfo($targetName, PATHINFO_EXTENSION)); - $targetKey = $this->fileUtility->getKey($targetFile); - - $fileUrl = $this->getUrl($targetFile, $user, $shareToken); - - $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); - $documentService = new DocumentService($this->trans, $this->config); - try { - $newFileUri = $documentService->GetConvertedUri($fileUrl, $targetExt, $ext, $targetKey); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "GetConvertedUri: " . $targetFile->getId(), "app" => $this->appName]); - return ["error" => $e->getMessage()]; - } - $template = $documentService->Request($newFileUri); - } else { - $template = TemplateManager::GetEmptyTemplate($name); - } - - if (!$template) { - $this->logger->error("Template for file creation not found: $name ($templateId)", ["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->logException($e, ["message" => "Can't create file: $name", "app" => $this->appName]); - return ["error" => $this->trans->t("Can't create file")]; - } catch (ForbiddenException $e) { - $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); - return ["error" => $this->trans->t("Can't create file")]; - } - - $fileInfo = $file->getFileInfo(); - - $result = Helper::formatFileInfo($fileInfo); - return $result; - } - - /** - * Create new file in folder from editor - * - * @param string $name - file name - * @param string $dir - folder path - * @param string $templateId - file identifier - * - * @return TemplateResponse|RedirectResponse - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function createNew($name, $dir, $templateId = null) { - $this->logger->debug("Create from editor: $name in $dir", ["app" => $this->appName]); - - $result = $this->create($name, $dir, $templateId); - if (isset($result["error"])) { - return $this->renderError($result["error"]); - } - - $openEditor = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", ["fileId" => $result["id"]]); - return new RedirectResponse($openEditor); - } - - /** - * Get users - * - * @param $fileId - file identifier - * - * @return array - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function users($fileId) { - $this->logger->debug("Search users", ["app" => $this->appName]); - $result = []; - - if (!$this->config->isUserAllowedToUse()) { - return $result; - } - - if (!$this->allowEnumeration()) { - return $result; - } - - $autocompleteMemberGroup = false; - if ($this->limitEnumerationToGroups()) { - $autocompleteMemberGroup = true; - } - - $currentUser = $this->userSession->getUser(); - $currentUserId = $currentUser->getUID(); - - list($file, $error, $share) = $this->getFile($currentUserId, $fileId); - if (isset($error)) { - $this->logger->error("Users: $fileId $error", ["app" => $this->appName]); - return $result; - } - - $canShare = (($file->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE); - - $shareMemberGroups = $this->shareManager->shareWithGroupMembersOnly(); - - $all = false; - $users = []; - if ($canShare) { - if ($shareMemberGroups || $autocompleteMemberGroup) { - $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser); - foreach ($currentUserGroups as $currentUserGroup) { - $group = $this->groupManager->get($currentUserGroup); - foreach ($group->getUsers() as $user) { - if (!\in_array($user, $users)) { - array_push($users, $user); - } - } - } - } else { - $users = $this->userManager->search(""); - $all = true; - } - } - - if (!$all) { - $accessList = $this->getAccessList($file); - foreach ($accessList as $accessUser) { - if (!\in_array($accessUser, $users)) { - array_push($users, $accessUser); - } - } - } - - foreach ($users as $user) { - $email = $user->getEMailAddress(); - if ($user->getUID() != $currentUserId && !empty($email)) { - array_push($result, [ - "email" => $email, - "name" => $user->getDisplayName() - ]); - } - } - - return $result; - } - - /** - * Send notify about mention - * - * @param int $fileId - file identifier - * @param string $anchor - the anchor on target content - * @param string $comment - comment - * @param array $emails - emails array to whom to send notify - * - * @return array - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function mention($fileId, $anchor, $comment, $emails) { - $this->logger->debug("mention: from $fileId to " . json_encode($emails), ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - if (empty($emails)) { - return ["error" => $this->trans->t("Failed to send notification")]; - } - - $recipientIds = []; - foreach ($emails as $email) { - $recipients = $this->userManager->getByEmail($email); - foreach ($recipients as $recipient) { - $recipientId = $recipient->getUID(); - if (!\in_array($recipientId, $recipientIds)) { - array_push($recipientIds, $recipientId); - } - } - } - - $user = $this->userSession->getUser(); - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - } - - list($file, $error, $share) = $this->getFile($userId, $fileId); - if (isset($error)) { - $this->logger->error("Mention: $fileId $error", ["app" => $this->appName]); - return ["error" => $this->trans->t("Failed to send notification")]; - } - - foreach ($emails as $email) { - $substrToDelete = "+" . $email . " "; - $comment = str_replace($substrToDelete, "", $comment); - } - - //Length from ownCloud: - //https://github.com/owncloud/core/blob/master/lib/private/Notification/Notification.php#L181 - $maxLen = 64; - if (\strlen($comment) > $maxLen) { - $ending = "..."; - $comment = substr($comment, 0, ($maxLen - \strlen($ending))) . $ending; - } - - $notificationManager = \OC::$server->getNotificationManager(); - $notification = $notificationManager->createNotification(); - $notification->setApp($this->appName) - ->setDateTime(new \DateTime()) - ->setObject("mention", $comment) - ->setSubject("mention_info", [ - "notifierId" => $userId, - "fileId" => $file->getId(), - "fileName" => $file->getName(), - "anchor" => $anchor - ]); - - $shareMemberGroups = $this->shareManager->shareWithGroupMembersOnly(); - $canShare = ($file->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE; - - $currentUserGroups = []; - if ($shareMemberGroups) { - $currentUserGroups = $this->groupManager->getUserGroupIds($user); - } - - $accessList = $this->getAccessList($file); - - foreach ($recipientIds as $recipientId) { - $recipient = $this->userManager->get($recipientId); - $isAvailable = \in_array($recipient, $accessList); - - if (!$isAvailable - && $file->getFileInfo()->getMountPoint() instanceof \OCA\Files_External\Config\ExternalMountPoint) { - $recipientFolder = $this->root->getUserFolder($recipientId); - $recipientFile = $recipientFolder->getById($file->getId()); - - $isAvailable = !empty($recipientFile); - } - - if (!$isAvailable) { - if (!$canShare) { - continue; - } - if ($shareMemberGroups) { - $recipientGroups = $this->groupManager->getUserGroupIds($recipient); - if (empty(array_intersect($currentUserGroups, $recipientGroups))) { - continue; - } - } - - $share = $this->shareManager->newShare(); - $share->setNode($file) - ->setShareType(Share::SHARE_TYPE_USER) - ->setSharedBy($userId) - ->setSharedWith($recipientId) - ->setShareOwner($userId) - ->setPermissions(Constants::PERMISSION_READ); - - $this->shareManager->createShare($share); - - $this->logger->debug("mention: share $fileId to $recipientId", ["app" => $this->appName]); - } - - $notification->setUser($recipientId); - - $notificationManager->notify($notification); - } - - return ["message" => $this->trans->t("Notification sent successfully")]; - } - - /** - * Reference data - * - * @param array $referenceData - reference data - * @param string $path - file path - * - * @return array - * - * @NoAdminRequired - * @PublicPage - */ - public function reference($referenceData, $path = null) { - $this->logger->debug("reference: " . json_encode($referenceData) . " $path", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $user = $this->userSession->getUser(); - if (empty($user)) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $userId = $user->getUID(); - - $file = null; - $fileId = (integer)($referenceData["fileKey"] ?? 0); - if (!empty($fileId) - && $referenceData["instanceId"] === $this->config->GetSystemValue("instanceid", true)) { - list($file, $error, $share) = $this->getFile($userId, $fileId); - } - - $userFolder = $this->root->getUserFolder($userId); - if ($file === null - && $path !== null - && $userFolder->nodeExists($path)) { - $node = $userFolder->get($path); - if ($node instanceof File - && $node->isReadable()) { - $file = $node; - } - } - - if ($file === null) { - $this->logger->error("Reference not found: $fileId $path", ["app" => $this->appName]); - return ["error" => $this->trans->t("File not found")]; - } - - $fileName = $file->getName(); - $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); - - $response = [ - "fileType" => $ext, - "path" => $userFolder->getRelativePath($file->getPath()), - "referenceData" => [ - "fileKey" => $file->getId(), - "instanceId" => $this->config->GetSystemValue("instanceid", true), - ], - "url" => $this->getUrl($file, $user), - ]; - - if (!empty($this->config->GetDocumentServerSecret())) { - $token = \Firebase\JWT\JWT::encode($response, $this->config->GetDocumentServerSecret(), "HS256"); - $response["token"] = $token; - } - - return $response; - } - - /** - * 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", ["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", ["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", ["app" => $this->appName]); - return ["error" => $this->trans->t("You do not have enough permissions to view the file")]; - } - - $canDownload = $this->fileUtility->hasPermissionAttribute($file); - if (!$canDownload) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $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", ["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", ["app" => $this->appName]); - return ["error" => $this->trans->t("Conversion is not required")]; - } - - $internalExtension = "docx"; - switch ($format["type"]) { - case "cell": - $internalExtension = "xlsx"; - break; - case "slide": - $internalExtension = "pptx"; - break; - } - - $newFileUri = null; - $documentService = new DocumentService($this->trans, $this->config); - $key = $this->fileUtility->getKey($file); - $fileUrl = $this->getUrl($file, $user, $shareToken); - try { - $newFileUri = $documentService->GetConvertedUri($fileUrl, $ext, $internalExtension, $key); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "GetConvertedUri: " . $file->getId(), "app" => $this->appName]); - return ["error" => $e->getMessage()]; - } - - $folder = $file->getParent(); - if (!($folder->isCreatable() && $folder->isUpdateable())) { - $folder = $this->root->getUserFolder($userId); - } - - try { - $newData = $documentService->Request($newFileUri); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Failed to download converted file", "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->logException($e, ["message" => "Can't create file: $newFileName", "app" => $this->appName]); - return ["error" => $this->trans->t("Can't create file")]; - } catch (ForbiddenException $e) { - $this->logger->logException($e, ["message" => "Can't put file: $newFileName", "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 - * @PublicPage - */ - public function save($name, $dir, $url) { - $this->logger->debug("Save: $name", ["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", ["app" => $this->appName]); - return ["error" => $this->trans->t("The required folder was not found")]; - } - if (!($folder->isCreatable() && $folder->isUpdateable())) { - $this->logger->error("Folder for saving file without permission: $dir", ["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->logException($e, ["message" => "Failed to download file for saving", "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->logException($e, ["message" => "Can't save file: $name", "app" => $this->appName]); - return ["error" => $this->trans->t("Can't create file")]; - } catch (ForbiddenException $e) { - $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); - return ["error" => $this->trans->t("Can't create file")]; - } - - $fileInfo = $file->getFileInfo(); - - $result = Helper::formatFileInfo($fileInfo); - return $result; - } - - /** - * Get versions history for file - * - * @param integer $fileId - file identifier - * - * @return array - * - * @NoAdminRequired - */ - public function history($fileId) { - $this->logger->debug("Request history for: $fileId", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $history = []; - - $user = $this->userSession->getUser(); - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - } - - list($file, $error, $share) = $this->getFile($userId, $fileId); - - if (isset($error)) { - $this->logger->error("History: $fileId $error", ["app" => $this->appName]); - return ["error" => $error]; - } - - if ($fileId === 0) { - $fileId = $file->getId(); - } - - $ownerId = null; - $owner = $file->getFileInfo()->getOwner(); - if ($owner !== null) { - $ownerId = $owner->getUID(); - } - - $versions = []; - if ($this->versionManager->available - && $owner !== null) { - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - } - - $prevVersion = ""; - $versionNum = 0; - foreach ($versions as $version) { - $versionNum = $versionNum + 1; - - $key = $this->fileUtility->getVersionKey($version); - $key = DocumentService::GenerateRevisionId($key); - - $historyItem = [ - "created" => $version->getTimestamp(), - "key" => $key, - "version" => $versionNum - ]; - - $versionId = $version->getRevisionId(); - - $author = FileVersions::getAuthor($ownerId, $fileId, $versionId); - $authorId = $author !== null ? $author["id"] : $ownerId; - $authorName = $author !== null ? $author["name"] : $owner->getDisplayName(); - - $historyItem["user"] = [ - "id" => $this->buildUserId($authorId), - "name" => $authorName - ]; - - $historyData = FileVersions::getHistoryData($ownerId, $fileId, $versionId, $prevVersion); - if ($historyData !== null) { - $historyItem["changes"] = $historyData["changes"]; - $historyItem["serverVersion"] = $historyData["serverVersion"]; - } - - $prevVersion = $versionId; - - array_push($history, $historyItem); - } - - $key = $this->fileUtility->getKey($file, true); - $key = DocumentService::GenerateRevisionId($key); - - $historyItem = [ - "created" => $file->getMTime(), - "key" => $key, - "version" => $versionNum + 1 - ]; - - $versionId = $file->getFileInfo()->getMtime(); - - $author = FileVersions::getAuthor($ownerId, $fileId, $versionId); - if ($author !== null) { - $historyItem["user"] = [ - "id" => $this->buildUserId($author["id"]), - "name" => $author["name"] - ]; - } elseif ($owner !== null) { - $historyItem["user"] = [ - "id" => $this->buildUserId($ownerId), - "name" => $owner->getDisplayName() - ]; - } - - $historyData = FileVersions::getHistoryData($ownerId, $fileId, $versionId, $prevVersion); - if ($historyData !== null) { - $historyItem["changes"] = $historyData["changes"]; - $historyItem["serverVersion"] = $historyData["serverVersion"]; - } - - array_push($history, $historyItem); - - return $history; - } - - /** - * Get file attributes of specific version - * - * @param integer $fileId - file identifier - * @param integer $version - file version - * - * @return array - * - * @NoAdminRequired - */ - public function version($fileId, $version) { - $this->logger->debug("Request version for: $fileId ($version)", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $version = empty($version) ? null : $version; - - $user = $this->userSession->getUser(); - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - } - - list($file, $error, $share) = $this->getFile($userId, $fileId); - - if (isset($error)) { - $this->logger->error("History: $fileId $error", ["app" => $this->appName]); - return ["error" => $error]; - } - - if ($fileId === 0) { - $fileId = $file->getId(); - } - - $owner = null; - $ownerId = null; - $versions = []; - if ($this->versionManager->available) { - $owner = $file->getFileInfo()->getOwner(); - if ($owner !== null) { - $ownerId = $owner->getUID(); - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - } - } - - $key = null; - $fileUrl = null; - $versionId = null; - if ($version > \count($versions)) { - $key = $this->fileUtility->getKey($file, true); - $versionId = $file->getFileInfo()->getMtime(); - - $fileUrl = $this->getUrl($file, $user); - } else { - $fileVersion = array_values($versions)[$version - 1]; - - $key = $this->fileUtility->getVersionKey($fileVersion); - $versionId = $fileVersion->getRevisionId(); - - $fileUrl = $this->getUrl($file, $user, null, $version); - } - $key = DocumentService::GenerateRevisionId($key); - $fileName = $file->getName(); - $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); - - $result = [ - "fileType" => $ext, - "url" => $fileUrl, - "version" => $version, - "key" => $key - ]; - - if ($version > 1 - && \count($versions) >= $version - 1 - && FileVersions::hasChanges($ownerId, $fileId, $versionId)) { - $changesUrl = $this->getUrl($file, $user, null, $version, true); - $result["changesUrl"] = $changesUrl; - - $prevVersion = array_values($versions)[$version - 2]; - $prevVersionKey = $this->fileUtility->getVersionKey($prevVersion); - $prevVersionKey = DocumentService::GenerateRevisionId($prevVersionKey); - - $prevVersionUrl = $this->getUrl($file, $user, null, $version - 1); - - $result["previous"] = [ - "fileType" => $ext, - "key" => $prevVersionKey, - "url" => $prevVersionUrl - ]; - } - - if (!empty($this->config->GetDocumentServerSecret())) { - $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256"); - $result["token"] = $token; - } - - return $result; - } - - /** - * Restore file version - * - * @param integer $fileId - file identifier - * @param integer $version - file version - * - * @return array - * - * @NoAdminRequired - * @PublicPage - */ - public function restore($fileId, $version) { - $this->logger->debug("Request restore version for: $fileId ($version)", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $version = empty($version) ? null : $version; - - $user = $this->userSession->getUser(); - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - } - - list($file, $error, $share) = $this->getFile($userId, $fileId); - - if (isset($error)) { - $this->logger->error("Restore: $fileId $error", ["app" => $this->appName]); - return ["error" => $error]; - } - - if ($fileId === 0) { - $fileId = $file->getId(); - } - - $owner = null; - $versions = []; - if ($this->versionManager->available) { - $owner = $file->getFileInfo()->getOwner(); - if ($owner !== null) { - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - } - - if (\count($versions) >= $version) { - $fileVersion = array_values($versions)[$version - 1]; - $this->versionManager->rollback($fileVersion); - } - } - - return $this->history($fileId); - } - - /** - * Get presigned url to file - * - * @param string $filePath - file path - * - * @return array - * - * @NoAdminRequired - */ - public function url($filePath) { - $this->logger->debug("Request url for: $filePath", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return ["error" => $this->trans->t("Not permitted")]; - } - - $user = $this->userSession->getUser(); - $userId = $user->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", ["app" => $this->appName]); - return ["error" => $this->trans->t("File not found")]; - } - if (!$file->isReadable()) { - $this->logger->error("File without permission: $dir", ["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, $user); - - $result = [ - "fileType" => $ext, - "url" => $fileUrl - ]; - - if (!empty($this->config->GetDocumentServerSecret())) { - $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256"); - $result["token"] = $token; - } - - return $result; - } - - /** - * Download method - * - * @param int $fileId - file identifier - * @param string $toExtension - file extension to download - * @param bool $template - file is template - * - * @return DataDownloadResponse|TemplateResponse - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function download($fileId, $toExtension = null, $template = false) { - $this->logger->debug("Download: $fileId $toExtension", ["app" => $this->appName]); - - if (!$this->config->isUserAllowedToUse()) { - return $this->renderError($this->trans->t("Not permitted")); - } - - if ($template) { - $templateFile = TemplateManager::GetTemplate($fileId); - - if (empty($templateFile)) { - $this->logger->info("Download: template not found: $fileId", ["app" => $this->appName]); - return $this->renderError($this->trans->t("File not found")); - } - - $file = $templateFile; - } else { - $user = $this->userSession->getUser(); - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - } - - list($file, $error, $share) = $this->getFile($userId, $fileId); - - if (isset($error)) { - $this->logger->error("Download: $fileId $error", ["app" => $this->appName]); - return $this->renderError($error); - } - } - - $canDownload = $this->fileUtility->hasPermissionAttribute($file); - if (!$canDownload) { - return $this->renderError($this->trans->t("Not permitted")); - } - - $fileName = $file->getName(); - $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); - $toExtension = strtolower($toExtension); - - if ($toExtension === null - || $ext === $toExtension - || $template) { - return new DataDownloadResponse($file->getContent(), $fileName, $file->getMimeType()); - } - - $newFileUri = null; - $documentService = new DocumentService($this->trans, $this->config); - $key = $this->fileUtility->getKey($file); - $fileUrl = $this->getUrl($file, $user); - try { - $newFileUri = $documentService->GetConvertedUri($fileUrl, $ext, $toExtension, $key); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "GetConvertedUri: " . $file->getId(), "app" => $this->appName]); - return $this->renderError($e->getMessage()); - } - - try { - $newData = $documentService->Request($newFileUri); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Failed to download converted file", "app" => $this->appName]); - return $this->renderError($this->trans->t("Failed to download converted file")); - } - - $fileNameWithoutExt = substr($fileName, 0, \strlen($fileName) - \strlen($ext) - 1); - $newFileName = $fileNameWithoutExt . "." . $toExtension; - - $formats = $this->config->FormatsSetting(); - - return new DataDownloadResponse($newData, $newFileName, $formats[$toExtension]["mime"]); - } - - /** - * Print editor section - * - * @param integer $fileId - file identifier - * @param string $filePath - file path - * @param string $shareToken - access token - * @param integer $version - file version - * @param bool $inframe - open in frame - * @param bool $template - file is template - * @param string $anchor - anchor for file content - * - * @return TemplateResponse|RedirectResponse - * - * @NoAdminRequired - * @NoCSRFRequired - */ - public function index($fileId, $filePath = null, $shareToken = null, $version = 0, $inframe = false, $template = false, $anchor = null) { - $this->logger->debug("Open: $fileId ($version) $filePath", ["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); - } - - $shareBy = null; - if (!empty($shareToken) && !$this->userSession->isLoggedIn()) { - list($share, $error) = $this->fileUtility->getShare($shareToken); - if (!empty($share)) { - $shareBy = $share->getSharedBy(); - } - } - - if (!$this->config->isUserAllowedToUse($shareBy)) { - return $this->renderError($this->trans->t("Not permitted")); - } - - $documentServerUrl = $this->config->GetDocumentServerUrl(); - - if (empty($documentServerUrl)) { - $this->logger->error("documentServerUrl is empty", ["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, - "version" => $version, - "template" => $template, - "inframe" => false, - "anchor" => $anchor - ]; - - if ($inframe === true) { - $params["inframe"] = true; - $response = new TemplateResponse($this->appName, "editor", $params, "plain"); - } else { - $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("'self'"); - } - $response->setContentSecurityPolicy($csp); - - return $response; - } - - /** - * Print public editor section - * - * @param integer $fileId - file identifier - * @param string $shareToken - access token - * @param integer $version - file version - * @param bool $inframe - open in frame - * - * @return TemplateResponse - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ - public function PublicPage($fileId, $shareToken, $version = 0, $inframe = false) { - return $this->index($fileId, null, $shareToken, $version, $inframe); - } - - /** - * Getting file by identifier - * - * @param string $userId - user identifier - * @param integer $fileId - file identifier - * @param string $filePath - file path - * @param bool $template - file is template - * - * @return array - */ - private function getFile($userId, $fileId, $filePath = null, $template = false) { - if (empty($fileId)) { - return [null, $this->trans->t("FileId is empty"), null]; - } - - try { - $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); - $files = $folder->getById($fileId); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); - return [null, $this->trans->t("Invalid request"), null]; - } - - if (empty($files)) { - $this->logger->info("Files not found: $fileId", ["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; - break; - } - } - } - - 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 File $file - file - * @param IUser $user - user with access - * @param string $shareToken - access token - * @param integer $version - file version - * @param bool $changes - is required url to file changes - * @param bool $template - file is template - * - * @return string - */ - private function getUrl($file, $user = null, $shareToken = null, $version = 0, $changes = false, $template = false) { - $data = [ - "action" => "download", - "fileId" => $file->getId() - ]; - - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - $data["userId"] = $userId; - } - if (!empty($shareToken)) { - $data["shareToken"] = $shareToken; - } - if ($version > 0) { - $data["version"] = $version; - } - if ($changes) { - $data["changes"] = true; - } - if ($template) { - $data["template"] = true; - } - - $hashUrl = $this->crypt->GetHash($data); - - $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); - - if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { - $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); - } - - return $fileUrl; - } - - /** - * Generate unique user identifier - * - * @param string $userId - current user identifier - * - * @return string - */ - private function buildUserId($userId) { - $instanceId = $this->config->GetSystemValue("instanceid", true); - $userId = $instanceId . "_" . $userId; - return $userId; - } - - /** - * Return list users who has access to file - * - * @param File $file - file - * - * @return array - */ - private function getAccessList($file) { - $result = []; - - foreach ($this->shareManager->getSharesByPath($file) as $share) { - $accessList = []; - $shareWith = $share->getSharedWith(); - if ($share->getShareType() === Share::SHARE_TYPE_GROUP) { - $group = $this->groupManager->get($shareWith); - $accessList = $group->getUsers(); - } elseif ($share->getShareType() === Share::SHARE_TYPE_USER) { - array_push($accessList, $this->userManager->get($shareWith)); - } - - foreach ($accessList as $accessUser) { - if (!\in_array($accessUser, $result)) { - array_push($result, $accessUser); - } - } - } - - if (!\in_array($file->getOwner(), $result)) { - array_push($result, $file->getOwner()); - } - - return $result; - } - - /** - * Return allow autocomplete usernames - * - * @return bool - */ - private function allowEnumeration() { - return \OC::$server->getConfig()->getAppValue("core", "shareapi_allow_share_dialog_user_enumeration", "yes") === "yes"; - } - - /** - * Return allow autocomplete usernames group member only - * - * @return bool - */ - private function limitEnumerationToGroups() { - if ($this->allowEnumeration()) { - return \OC::$server->getConfig()->getAppValue("core", "shareapi_share_dialog_user_enumeration_group_members", "no") === "yes"; - } - - 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", [ - "errors" => [ - [ - "error" => $error, - "hint" => $hint - ] - ] - ], "error"); - } + /** + * Current user session + * + * @var IUserSession + */ + private $userSession; + + /** + * Current user manager + * + * @var IUserManager + */ + private $userManager; + + /** + * Root folder + * + * @var IRootFolder + */ + private $root; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * File utility + * + * @var FileUtility + */ + private $fileUtility; + + /** + * File version manager + * + * @var VersionManager + */ + private $versionManager; + + /** + * Share manager + * + * @var IManager + */ + private $shareManager; + + /** + * Group manager + * + * @var IGroupManager + */ + private $groupManager; + + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IRootFolder $root - root folder + * @param IUserSession $userSession - current user session + * @param IUserManager $userManager - current user manager + * @param IURLGenerator $urlGenerator - url generator service + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param AppConfig $config - application configuration + * @param Crypt $crypt - hash generator + * @param IManager $shareManager - Share manager + * @param ISession $session - Session + * @param IGroupManager $groupManager - Group manager + */ + public function __construct( + $AppName, + IRequest $request, + IRootFolder $root, + IUserSession $userSession, + IUserManager $userManager, + IURLGenerator $urlGenerator, + IL10N $trans, + ILogger $logger, + AppConfig $config, + Crypt $crypt, + IManager $shareManager, + ISession $session, + IGroupManager $groupManager + ) { + parent::__construct($AppName, $request); + + $this->userSession = $userSession; + $this->userManager = $userManager; + $this->root = $root; + $this->urlGenerator = $urlGenerator; + $this->trans = $trans; + $this->logger = $logger; + $this->config = $config; + $this->crypt = $crypt; + $this->shareManager = $shareManager; + $this->groupManager = $groupManager; + + $this->versionManager = new VersionManager($AppName, $root); + + $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 $templateId - file identifier + * @param string $targetPath - file path for using as template for create + * @param string $shareToken - access token + * + * @return array + * + * @NoAdminRequired + * @PublicPage + */ + public function create($name, $dir, $templateId = null, $targetPath = null, $shareToken = null) { + $this->logger->debug("Create: $name", ["app" => $this->appName]); + + if (empty($shareToken) && !$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + if (empty($name)) { + $this->logger->error("File name for creation was not found: $name", ["app" => $this->appName]); + return ["error" => $this->trans->t("Template not found")]; + } + + $user = null; + if (empty($shareToken)) { + $user = $this->userSession->getUser(); + $userId = $user->getUID(); + $userFolder = $this->root->getUserFolder($userId); + } else { + list($userFolder, $error, $share) = $this->fileUtility->getNodeByToken($shareToken); + + if (isset($error)) { + $this->logger->error("Create: $error", ["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", ["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", ["app" => $this->appName]); + return ["error" => $this->trans->t("The required folder was not found")]; + } + if (!($folder->isCreatable() && $folder->isUpdateable())) { + $this->logger->error("Folder for file creation without permission: $dir", ["app" => $this->appName]); + return ["error" => $this->trans->t("You don't have enough permission to create")]; + } + + if (!empty($templateId)) { + $templateFile = TemplateManager::GetTemplate($templateId); + if ($templateFile) { + $template = $templateFile->getContent(); + } + } elseif (!empty($targetPath)) { + $targetFile = $userFolder->get($targetPath); + + $canDownload = $this->fileUtility->hasPermissionAttribute($targetFile); + if (!$canDownload) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $targetId = $targetFile->getId(); + $targetName = $targetFile->getName(); + $targetExt = strtolower(pathinfo($targetName, PATHINFO_EXTENSION)); + $targetKey = $this->fileUtility->getKey($targetFile); + + $fileUrl = $this->getUrl($targetFile, $user, $shareToken); + + $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + $documentService = new DocumentService($this->trans, $this->config); + try { + $newFileUri = $documentService->GetConvertedUri($fileUrl, $targetExt, $ext, $targetKey); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "GetConvertedUri: " . $targetFile->getId(), "app" => $this->appName]); + return ["error" => $e->getMessage()]; + } + $template = $documentService->Request($newFileUri); + } else { + $template = TemplateManager::GetEmptyTemplate($name); + } + + if (!$template) { + $this->logger->error("Template for file creation not found: $name ($templateId)", ["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->logException($e, ["message" => "Can't create file: $name", "app" => $this->appName]); + return ["error" => $this->trans->t("Can't create file")]; + } catch (ForbiddenException $e) { + $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); + return ["error" => $this->trans->t("Can't create file")]; + } + + $fileInfo = $file->getFileInfo(); + + $result = Helper::formatFileInfo($fileInfo); + return $result; + } + + /** + * Create new file in folder from editor + * + * @param string $name - file name + * @param string $dir - folder path + * @param string $templateId - file identifier + * + * @return TemplateResponse|RedirectResponse + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function createNew($name, $dir, $templateId = null) { + $this->logger->debug("Create from editor: $name in $dir", ["app" => $this->appName]); + + $result = $this->create($name, $dir, $templateId); + if (isset($result["error"])) { + return $this->renderError($result["error"]); + } + + $openEditor = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", ["fileId" => $result["id"]]); + return new RedirectResponse($openEditor); + } + + /** + * Get users + * + * @param $fileId - file identifier + * + * @return array + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function users($fileId) { + $this->logger->debug("Search users", ["app" => $this->appName]); + $result = []; + + if (!$this->config->isUserAllowedToUse()) { + return $result; + } + + if (!$this->allowEnumeration()) { + return $result; + } + + $autocompleteMemberGroup = false; + if ($this->limitEnumerationToGroups()) { + $autocompleteMemberGroup = true; + } + + $currentUser = $this->userSession->getUser(); + $currentUserId = $currentUser->getUID(); + + list($file, $error, $share) = $this->getFile($currentUserId, $fileId); + if (isset($error)) { + $this->logger->error("Users: $fileId $error", ["app" => $this->appName]); + return $result; + } + + $canShare = (($file->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE); + + $shareMemberGroups = $this->shareManager->shareWithGroupMembersOnly(); + + $all = false; + $users = []; + if ($canShare) { + if ($shareMemberGroups || $autocompleteMemberGroup) { + $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser); + foreach ($currentUserGroups as $currentUserGroup) { + $group = $this->groupManager->get($currentUserGroup); + foreach ($group->getUsers() as $user) { + if (!\in_array($user, $users)) { + array_push($users, $user); + } + } + } + } else { + $users = $this->userManager->search(""); + $all = true; + } + } + + if (!$all) { + $accessList = $this->getAccessList($file); + foreach ($accessList as $accessUser) { + if (!\in_array($accessUser, $users)) { + array_push($users, $accessUser); + } + } + } + + foreach ($users as $user) { + $email = $user->getEMailAddress(); + if ($user->getUID() != $currentUserId && !empty($email)) { + array_push($result, [ + "email" => $email, + "name" => $user->getDisplayName() + ]); + } + } + + return $result; + } + + /** + * Send notify about mention + * + * @param int $fileId - file identifier + * @param string $anchor - the anchor on target content + * @param string $comment - comment + * @param array $emails - emails array to whom to send notify + * + * @return array + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function mention($fileId, $anchor, $comment, $emails) { + $this->logger->debug("mention: from $fileId to " . json_encode($emails), ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + if (empty($emails)) { + return ["error" => $this->trans->t("Failed to send notification")]; + } + + $recipientIds = []; + foreach ($emails as $email) { + $recipients = $this->userManager->getByEmail($email); + foreach ($recipients as $recipient) { + $recipientId = $recipient->getUID(); + if (!\in_array($recipientId, $recipientIds)) { + array_push($recipientIds, $recipientId); + } + } + } + + $user = $this->userSession->getUser(); + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + } + + list($file, $error, $share) = $this->getFile($userId, $fileId); + if (isset($error)) { + $this->logger->error("Mention: $fileId $error", ["app" => $this->appName]); + return ["error" => $this->trans->t("Failed to send notification")]; + } + + foreach ($emails as $email) { + $substrToDelete = "+" . $email . " "; + $comment = str_replace($substrToDelete, "", $comment); + } + + //Length from ownCloud: + //https://github.com/owncloud/core/blob/master/lib/private/Notification/Notification.php#L181 + $maxLen = 64; + if (\strlen($comment) > $maxLen) { + $ending = "..."; + $comment = substr($comment, 0, ($maxLen - \strlen($ending))) . $ending; + } + + $notificationManager = \OC::$server->getNotificationManager(); + $notification = $notificationManager->createNotification(); + $notification->setApp($this->appName) + ->setDateTime(new \DateTime()) + ->setObject("mention", $comment) + ->setSubject("mention_info", [ + "notifierId" => $userId, + "fileId" => $file->getId(), + "fileName" => $file->getName(), + "anchor" => $anchor + ]); + + $shareMemberGroups = $this->shareManager->shareWithGroupMembersOnly(); + $canShare = ($file->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE; + + $currentUserGroups = []; + if ($shareMemberGroups) { + $currentUserGroups = $this->groupManager->getUserGroupIds($user); + } + + $accessList = $this->getAccessList($file); + + foreach ($recipientIds as $recipientId) { + $recipient = $this->userManager->get($recipientId); + $isAvailable = \in_array($recipient, $accessList); + + if (!$isAvailable + && $file->getFileInfo()->getMountPoint() instanceof \OCA\Files_External\Config\ExternalMountPoint) { + $recipientFolder = $this->root->getUserFolder($recipientId); + $recipientFile = $recipientFolder->getById($file->getId()); + + $isAvailable = !empty($recipientFile); + } + + if (!$isAvailable) { + if (!$canShare) { + continue; + } + if ($shareMemberGroups) { + $recipientGroups = $this->groupManager->getUserGroupIds($recipient); + if (empty(array_intersect($currentUserGroups, $recipientGroups))) { + continue; + } + } + + $share = $this->shareManager->newShare(); + $share->setNode($file) + ->setShareType(Share::SHARE_TYPE_USER) + ->setSharedBy($userId) + ->setSharedWith($recipientId) + ->setShareOwner($userId) + ->setPermissions(Constants::PERMISSION_READ); + + $this->shareManager->createShare($share); + + $this->logger->debug("mention: share $fileId to $recipientId", ["app" => $this->appName]); + } + + $notification->setUser($recipientId); + + $notificationManager->notify($notification); + } + + return ["message" => $this->trans->t("Notification sent successfully")]; + } + + /** + * Reference data + * + * @param array $referenceData - reference data + * @param string $path - file path + * + * @return array + * + * @NoAdminRequired + * @PublicPage + */ + public function reference($referenceData, $path = null) { + $this->logger->debug("reference: " . json_encode($referenceData) . " $path", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $user = $this->userSession->getUser(); + if (empty($user)) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $userId = $user->getUID(); + + $file = null; + $fileId = (integer)($referenceData["fileKey"] ?? 0); + if (!empty($fileId) + && $referenceData["instanceId"] === $this->config->GetSystemValue("instanceid", true)) { + list($file, $error, $share) = $this->getFile($userId, $fileId); + } + + $userFolder = $this->root->getUserFolder($userId); + if ($file === null + && $path !== null + && $userFolder->nodeExists($path)) { + $node = $userFolder->get($path); + if ($node instanceof File + && $node->isReadable()) { + $file = $node; + } + } + + if ($file === null) { + $this->logger->error("Reference not found: $fileId $path", ["app" => $this->appName]); + return ["error" => $this->trans->t("File not found")]; + } + + $fileName = $file->getName(); + $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + + $response = [ + "fileType" => $ext, + "path" => $userFolder->getRelativePath($file->getPath()), + "referenceData" => [ + "fileKey" => $file->getId(), + "instanceId" => $this->config->GetSystemValue("instanceid", true), + ], + "url" => $this->getUrl($file, $user), + ]; + + if (!empty($this->config->GetDocumentServerSecret())) { + $token = \Firebase\JWT\JWT::encode($response, $this->config->GetDocumentServerSecret(), "HS256"); + $response["token"] = $token; + } + + return $response; + } + + /** + * 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", ["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", ["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", ["app" => $this->appName]); + return ["error" => $this->trans->t("You do not have enough permissions to view the file")]; + } + + $canDownload = $this->fileUtility->hasPermissionAttribute($file); + if (!$canDownload) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $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", ["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", ["app" => $this->appName]); + return ["error" => $this->trans->t("Conversion is not required")]; + } + + $internalExtension = "docx"; + switch ($format["type"]) { + case "cell": + $internalExtension = "xlsx"; + break; + case "slide": + $internalExtension = "pptx"; + break; + } + + $newFileUri = null; + $documentService = new DocumentService($this->trans, $this->config); + $key = $this->fileUtility->getKey($file); + $fileUrl = $this->getUrl($file, $user, $shareToken); + try { + $newFileUri = $documentService->GetConvertedUri($fileUrl, $ext, $internalExtension, $key); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "GetConvertedUri: " . $file->getId(), "app" => $this->appName]); + return ["error" => $e->getMessage()]; + } + + $folder = $file->getParent(); + if (!($folder->isCreatable() && $folder->isUpdateable())) { + $folder = $this->root->getUserFolder($userId); + } + + try { + $newData = $documentService->Request($newFileUri); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Failed to download converted file", "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->logException($e, ["message" => "Can't create file: $newFileName", "app" => $this->appName]); + return ["error" => $this->trans->t("Can't create file")]; + } catch (ForbiddenException $e) { + $this->logger->logException($e, ["message" => "Can't put file: $newFileName", "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 + * @PublicPage + */ + public function save($name, $dir, $url) { + $this->logger->debug("Save: $name", ["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", ["app" => $this->appName]); + return ["error" => $this->trans->t("The required folder was not found")]; + } + if (!($folder->isCreatable() && $folder->isUpdateable())) { + $this->logger->error("Folder for saving file without permission: $dir", ["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->logException($e, ["message" => "Failed to download file for saving", "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->logException($e, ["message" => "Can't save file: $name", "app" => $this->appName]); + return ["error" => $this->trans->t("Can't create file")]; + } catch (ForbiddenException $e) { + $this->logger->logException($e, ["message" => "Can't put file: $name", "app" => $this->appName]); + return ["error" => $this->trans->t("Can't create file")]; + } + + $fileInfo = $file->getFileInfo(); + + $result = Helper::formatFileInfo($fileInfo); + return $result; + } + + /** + * Get versions history for file + * + * @param integer $fileId - file identifier + * + * @return array + * + * @NoAdminRequired + */ + public function history($fileId) { + $this->logger->debug("Request history for: $fileId", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $history = []; + + $user = $this->userSession->getUser(); + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + } + + list($file, $error, $share) = $this->getFile($userId, $fileId); + + if (isset($error)) { + $this->logger->error("History: $fileId $error", ["app" => $this->appName]); + return ["error" => $error]; + } + + if ($fileId === 0) { + $fileId = $file->getId(); + } + + $ownerId = null; + $owner = $file->getFileInfo()->getOwner(); + if ($owner !== null) { + $ownerId = $owner->getUID(); + } + + $versions = []; + if ($this->versionManager->available + && $owner !== null) { + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + } + + $prevVersion = ""; + $versionNum = 0; + foreach ($versions as $version) { + $versionNum = $versionNum + 1; + + $key = $this->fileUtility->getVersionKey($version); + $key = DocumentService::GenerateRevisionId($key); + + $historyItem = [ + "created" => $version->getTimestamp(), + "key" => $key, + "version" => $versionNum + ]; + + $versionId = $version->getRevisionId(); + + $author = FileVersions::getAuthor($ownerId, $fileId, $versionId); + $authorId = $author !== null ? $author["id"] : $ownerId; + $authorName = $author !== null ? $author["name"] : $owner->getDisplayName(); + + $historyItem["user"] = [ + "id" => $this->buildUserId($authorId), + "name" => $authorName + ]; + + $historyData = FileVersions::getHistoryData($ownerId, $fileId, $versionId, $prevVersion); + if ($historyData !== null) { + $historyItem["changes"] = $historyData["changes"]; + $historyItem["serverVersion"] = $historyData["serverVersion"]; + } + + $prevVersion = $versionId; + + array_push($history, $historyItem); + } + + $key = $this->fileUtility->getKey($file, true); + $key = DocumentService::GenerateRevisionId($key); + + $historyItem = [ + "created" => $file->getMTime(), + "key" => $key, + "version" => $versionNum + 1 + ]; + + $versionId = $file->getFileInfo()->getMtime(); + + $author = FileVersions::getAuthor($ownerId, $fileId, $versionId); + if ($author !== null) { + $historyItem["user"] = [ + "id" => $this->buildUserId($author["id"]), + "name" => $author["name"] + ]; + } elseif ($owner !== null) { + $historyItem["user"] = [ + "id" => $this->buildUserId($ownerId), + "name" => $owner->getDisplayName() + ]; + } + + $historyData = FileVersions::getHistoryData($ownerId, $fileId, $versionId, $prevVersion); + if ($historyData !== null) { + $historyItem["changes"] = $historyData["changes"]; + $historyItem["serverVersion"] = $historyData["serverVersion"]; + } + + array_push($history, $historyItem); + + return $history; + } + + /** + * Get file attributes of specific version + * + * @param integer $fileId - file identifier + * @param integer $version - file version + * + * @return array + * + * @NoAdminRequired + */ + public function version($fileId, $version) { + $this->logger->debug("Request version for: $fileId ($version)", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $version = empty($version) ? null : $version; + + $user = $this->userSession->getUser(); + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + } + + list($file, $error, $share) = $this->getFile($userId, $fileId); + + if (isset($error)) { + $this->logger->error("History: $fileId $error", ["app" => $this->appName]); + return ["error" => $error]; + } + + if ($fileId === 0) { + $fileId = $file->getId(); + } + + $owner = null; + $ownerId = null; + $versions = []; + if ($this->versionManager->available) { + $owner = $file->getFileInfo()->getOwner(); + if ($owner !== null) { + $ownerId = $owner->getUID(); + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + } + } + + $key = null; + $fileUrl = null; + $versionId = null; + if ($version > \count($versions)) { + $key = $this->fileUtility->getKey($file, true); + $versionId = $file->getFileInfo()->getMtime(); + + $fileUrl = $this->getUrl($file, $user); + } else { + $fileVersion = array_values($versions)[$version - 1]; + + $key = $this->fileUtility->getVersionKey($fileVersion); + $versionId = $fileVersion->getRevisionId(); + + $fileUrl = $this->getUrl($file, $user, null, $version); + } + $key = DocumentService::GenerateRevisionId($key); + $fileName = $file->getName(); + $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + + $result = [ + "fileType" => $ext, + "url" => $fileUrl, + "version" => $version, + "key" => $key + ]; + + if ($version > 1 + && \count($versions) >= $version - 1 + && FileVersions::hasChanges($ownerId, $fileId, $versionId)) { + $changesUrl = $this->getUrl($file, $user, null, $version, true); + $result["changesUrl"] = $changesUrl; + + $prevVersion = array_values($versions)[$version - 2]; + $prevVersionKey = $this->fileUtility->getVersionKey($prevVersion); + $prevVersionKey = DocumentService::GenerateRevisionId($prevVersionKey); + + $prevVersionUrl = $this->getUrl($file, $user, null, $version - 1); + + $result["previous"] = [ + "fileType" => $ext, + "key" => $prevVersionKey, + "url" => $prevVersionUrl + ]; + } + + if (!empty($this->config->GetDocumentServerSecret())) { + $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256"); + $result["token"] = $token; + } + + return $result; + } + + /** + * Restore file version + * + * @param integer $fileId - file identifier + * @param integer $version - file version + * + * @return array + * + * @NoAdminRequired + * @PublicPage + */ + public function restore($fileId, $version) { + $this->logger->debug("Request restore version for: $fileId ($version)", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $version = empty($version) ? null : $version; + + $user = $this->userSession->getUser(); + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + } + + list($file, $error, $share) = $this->getFile($userId, $fileId); + + if (isset($error)) { + $this->logger->error("Restore: $fileId $error", ["app" => $this->appName]); + return ["error" => $error]; + } + + if ($fileId === 0) { + $fileId = $file->getId(); + } + + $owner = null; + $versions = []; + if ($this->versionManager->available) { + $owner = $file->getFileInfo()->getOwner(); + if ($owner !== null) { + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + } + + if (\count($versions) >= $version) { + $fileVersion = array_values($versions)[$version - 1]; + $this->versionManager->rollback($fileVersion); + } + } + + return $this->history($fileId); + } + + /** + * Get presigned url to file + * + * @param string $filePath - file path + * + * @return array + * + * @NoAdminRequired + */ + public function url($filePath) { + $this->logger->debug("Request url for: $filePath", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return ["error" => $this->trans->t("Not permitted")]; + } + + $user = $this->userSession->getUser(); + $userId = $user->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", ["app" => $this->appName]); + return ["error" => $this->trans->t("File not found")]; + } + if (!$file->isReadable()) { + $this->logger->error("File without permission: $dir", ["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, $user); + + $result = [ + "fileType" => $ext, + "url" => $fileUrl + ]; + + if (!empty($this->config->GetDocumentServerSecret())) { + $token = \Firebase\JWT\JWT::encode($result, $this->config->GetDocumentServerSecret(), "HS256"); + $result["token"] = $token; + } + + return $result; + } + + /** + * Download method + * + * @param int $fileId - file identifier + * @param string $toExtension - file extension to download + * @param bool $template - file is template + * + * @return DataDownloadResponse|TemplateResponse + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function download($fileId, $toExtension = null, $template = false) { + $this->logger->debug("Download: $fileId $toExtension", ["app" => $this->appName]); + + if (!$this->config->isUserAllowedToUse()) { + return $this->renderError($this->trans->t("Not permitted")); + } + + if ($template) { + $templateFile = TemplateManager::GetTemplate($fileId); + + if (empty($templateFile)) { + $this->logger->info("Download: template not found: $fileId", ["app" => $this->appName]); + return $this->renderError($this->trans->t("File not found")); + } + + $file = $templateFile; + } else { + $user = $this->userSession->getUser(); + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + } + + list($file, $error, $share) = $this->getFile($userId, $fileId); + + if (isset($error)) { + $this->logger->error("Download: $fileId $error", ["app" => $this->appName]); + return $this->renderError($error); + } + } + + $canDownload = $this->fileUtility->hasPermissionAttribute($file); + if (!$canDownload) { + return $this->renderError($this->trans->t("Not permitted")); + } + + $fileName = $file->getName(); + $ext = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + $toExtension = strtolower($toExtension); + + if ($toExtension === null + || $ext === $toExtension + || $template) { + return new DataDownloadResponse($file->getContent(), $fileName, $file->getMimeType()); + } + + $newFileUri = null; + $documentService = new DocumentService($this->trans, $this->config); + $key = $this->fileUtility->getKey($file); + $fileUrl = $this->getUrl($file, $user); + try { + $newFileUri = $documentService->GetConvertedUri($fileUrl, $ext, $toExtension, $key); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "GetConvertedUri: " . $file->getId(), "app" => $this->appName]); + return $this->renderError($e->getMessage()); + } + + try { + $newData = $documentService->Request($newFileUri); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Failed to download converted file", "app" => $this->appName]); + return $this->renderError($this->trans->t("Failed to download converted file")); + } + + $fileNameWithoutExt = substr($fileName, 0, \strlen($fileName) - \strlen($ext) - 1); + $newFileName = $fileNameWithoutExt . "." . $toExtension; + + $formats = $this->config->FormatsSetting(); + + return new DataDownloadResponse($newData, $newFileName, $formats[$toExtension]["mime"]); + } + + /** + * Print editor section + * + * @param integer $fileId - file identifier + * @param string $filePath - file path + * @param string $shareToken - access token + * @param integer $version - file version + * @param bool $inframe - open in frame + * @param bool $template - file is template + * @param string $anchor - anchor for file content + * + * @return TemplateResponse|RedirectResponse + * + * @NoAdminRequired + * @NoCSRFRequired + */ + public function index($fileId, $filePath = null, $shareToken = null, $version = 0, $inframe = false, $template = false, $anchor = null) { + $this->logger->debug("Open: $fileId ($version) $filePath", ["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); + } + + $shareBy = null; + if (!empty($shareToken) && !$this->userSession->isLoggedIn()) { + list($share, $error) = $this->fileUtility->getShare($shareToken); + if (!empty($share)) { + $shareBy = $share->getSharedBy(); + } + } + + if (!$this->config->isUserAllowedToUse($shareBy)) { + return $this->renderError($this->trans->t("Not permitted")); + } + + $documentServerUrl = $this->config->GetDocumentServerUrl(); + + if (empty($documentServerUrl)) { + $this->logger->error("documentServerUrl is empty", ["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, + "version" => $version, + "template" => $template, + "inframe" => false, + "anchor" => $anchor + ]; + + if ($inframe === true) { + $params["inframe"] = true; + $response = new TemplateResponse($this->appName, "editor", $params, "plain"); + } else { + $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("'self'"); + } + $response->setContentSecurityPolicy($csp); + + return $response; + } + + /** + * Print public editor section + * + * @param integer $fileId - file identifier + * @param string $shareToken - access token + * @param integer $version - file version + * @param bool $inframe - open in frame + * + * @return TemplateResponse + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + */ + public function PublicPage($fileId, $shareToken, $version = 0, $inframe = false) { + return $this->index($fileId, null, $shareToken, $version, $inframe); + } + + /** + * Getting file by identifier + * + * @param string $userId - user identifier + * @param integer $fileId - file identifier + * @param string $filePath - file path + * @param bool $template - file is template + * + * @return array + */ + private function getFile($userId, $fileId, $filePath = null, $template = false) { + if (empty($fileId)) { + return [null, $this->trans->t("FileId is empty"), null]; + } + + try { + $folder = !$template ? $this->root->getUserFolder($userId) : TemplateManager::GetGlobalTemplateDir(); + $files = $folder->getById($fileId); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFile: $fileId", "app" => $this->appName]); + return [null, $this->trans->t("Invalid request"), null]; + } + + if (empty($files)) { + $this->logger->info("Files not found: $fileId", ["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; + break; + } + } + } + + 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 File $file - file + * @param IUser $user - user with access + * @param string $shareToken - access token + * @param integer $version - file version + * @param bool $changes - is required url to file changes + * @param bool $template - file is template + * + * @return string + */ + private function getUrl($file, $user = null, $shareToken = null, $version = 0, $changes = false, $template = false) { + $data = [ + "action" => "download", + "fileId" => $file->getId() + ]; + + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + $data["userId"] = $userId; + } + if (!empty($shareToken)) { + $data["shareToken"] = $shareToken; + } + if ($version > 0) { + $data["version"] = $version; + } + if ($changes) { + $data["changes"] = true; + } + if ($template) { + $data["template"] = true; + } + + $hashUrl = $this->crypt->GetHash($data); + + $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); + + if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { + $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); + } + + return $fileUrl; + } + + /** + * Generate unique user identifier + * + * @param string $userId - current user identifier + * + * @return string + */ + private function buildUserId($userId) { + $instanceId = $this->config->GetSystemValue("instanceid", true); + $userId = $instanceId . "_" . $userId; + return $userId; + } + + /** + * Return list users who has access to file + * + * @param File $file - file + * + * @return array + */ + private function getAccessList($file) { + $result = []; + + foreach ($this->shareManager->getSharesByPath($file) as $share) { + $accessList = []; + $shareWith = $share->getSharedWith(); + if ($share->getShareType() === Share::SHARE_TYPE_GROUP) { + $group = $this->groupManager->get($shareWith); + $accessList = $group->getUsers(); + } elseif ($share->getShareType() === Share::SHARE_TYPE_USER) { + array_push($accessList, $this->userManager->get($shareWith)); + } + + foreach ($accessList as $accessUser) { + if (!\in_array($accessUser, $result)) { + array_push($result, $accessUser); + } + } + } + + if (!\in_array($file->getOwner(), $result)) { + array_push($result, $file->getOwner()); + } + + return $result; + } + + /** + * Return allow autocomplete usernames + * + * @return bool + */ + private function allowEnumeration() { + return \OC::$server->getConfig()->getAppValue("core", "shareapi_allow_share_dialog_user_enumeration", "yes") === "yes"; + } + + /** + * Return allow autocomplete usernames group member only + * + * @return bool + */ + private function limitEnumerationToGroups() { + if ($this->allowEnumeration()) { + return \OC::$server->getConfig()->getAppValue("core", "shareapi_share_dialog_user_enumeration_group_members", "no") === "yes"; + } + + 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", [ + "errors" => [ + [ + "error" => $error, + "hint" => $hint + ] + ] + ], "error"); + } } diff --git a/controller/federationcontroller.php b/controller/federationcontroller.php index c4858ac0..5070fd5c 100644 --- a/controller/federationcontroller.php +++ b/controller/federationcontroller.php @@ -38,132 +38,132 @@ * OCS handler */ class FederationController extends OCSController { - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * Application configuration - * - * @var AppConfig - */ - public $config; - - /** - * File utility - * - * @var FileUtility - */ - private $fileUtility; - - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param IManager $shareManager - Share manager - * @param IManager $ISession - Session - */ - public function __construct( - $AppName, - IRequest $request, - IL10N $trans, - ILogger $logger, - IManager $shareManager, - ISession $session - ) { - parent::__construct($AppName, $request); - - $this->logger = $logger; - - $this->config = new AppConfig($this->appName); - $this->fileUtility = new FileUtility($AppName, $trans, $logger, $this->config, $shareManager, $session); - } - - /** - * Returns the origin document key for editor - * - * @param string $shareToken - access token - * @param string $path - file path - * - * @return Result - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ - public function key($shareToken, $path) { - list($file, $error, $share) = $this->fileUtility->getFileByToken(null, $shareToken, $path); - - if (isset($error)) { - $this->logger->error("Federated getFileByToken: $error", ["app" => $this->appName]); - return new Result(["error" => $error]); - } - - $key = $this->fileUtility->getKey($file, true); - - $key = DocumentService::GenerateRevisionId($key); - - $this->logger->debug("Federated request get for " . $file->getId() . " key $key", ["app" => $this->appName]); - - return new Result(["key" => $key]); - } - - /** - * Lock the origin document key for editor - * - * @param string $shareToken - access token - * @param string $path - file path - * @param bool $lock - status - * @param bool $fs - status - * - * @return Result - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ - public function keylock($shareToken, $path, $lock, $fs) { - list($file, $error, $share) = $this->fileUtility->getFileByToken(null, $shareToken, $path); - - if (isset($error)) { - $this->logger->error("Federated getFileByToken: $error", ["app" => $this->appName]); - return new Result(["error" => $error]); - } - - $fileId = $file->getId(); - - if (RemoteInstance::isRemoteFile($file)) { - $isLock = RemoteInstance::lockRemoteKey($file, $lock, $fs); - if (!$isLock) { - return new Result(["error" => "Failed request"]); - } - } else { - KeyManager::lock($fileId, $lock); - if (!empty($fs)) { - KeyManager::setForcesave($fileId, $fs); - } - } - - $this->logger->debug("Federated request lock for " . $fileId, ["app" => $this->appName]); - return new Result(); - } - - /** - * Health check instance - * - * @return Result - * - * @NoAdminRequired - * @NoCSRFRequired - * @PublicPage - */ - public function healthcheck() { - $this->logger->debug("Federated healthcheck", ["app" => $this->appName]); - - return new Result(["alive" => true]); - } + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * Application configuration + * + * @var AppConfig + */ + public $config; + + /** + * File utility + * + * @var FileUtility + */ + private $fileUtility; + + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param IManager $shareManager - Share manager + * @param IManager $ISession - Session + */ + public function __construct( + $AppName, + IRequest $request, + IL10N $trans, + ILogger $logger, + IManager $shareManager, + ISession $session + ) { + parent::__construct($AppName, $request); + + $this->logger = $logger; + + $this->config = new AppConfig($this->appName); + $this->fileUtility = new FileUtility($AppName, $trans, $logger, $this->config, $shareManager, $session); + } + + /** + * Returns the origin document key for editor + * + * @param string $shareToken - access token + * @param string $path - file path + * + * @return Result + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + */ + public function key($shareToken, $path) { + list($file, $error, $share) = $this->fileUtility->getFileByToken(null, $shareToken, $path); + + if (isset($error)) { + $this->logger->error("Federated getFileByToken: $error", ["app" => $this->appName]); + return new Result(["error" => $error]); + } + + $key = $this->fileUtility->getKey($file, true); + + $key = DocumentService::GenerateRevisionId($key); + + $this->logger->debug("Federated request get for " . $file->getId() . " key $key", ["app" => $this->appName]); + + return new Result(["key" => $key]); + } + + /** + * Lock the origin document key for editor + * + * @param string $shareToken - access token + * @param string $path - file path + * @param bool $lock - status + * @param bool $fs - status + * + * @return Result + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + */ + public function keylock($shareToken, $path, $lock, $fs) { + list($file, $error, $share) = $this->fileUtility->getFileByToken(null, $shareToken, $path); + + if (isset($error)) { + $this->logger->error("Federated getFileByToken: $error", ["app" => $this->appName]); + return new Result(["error" => $error]); + } + + $fileId = $file->getId(); + + if (RemoteInstance::isRemoteFile($file)) { + $isLock = RemoteInstance::lockRemoteKey($file, $lock, $fs); + if (!$isLock) { + return new Result(["error" => "Failed request"]); + } + } else { + KeyManager::lock($fileId, $lock); + if (!empty($fs)) { + KeyManager::setForcesave($fileId, $fs); + } + } + + $this->logger->debug("Federated request lock for " . $fileId, ["app" => $this->appName]); + return new Result(); + } + + /** + * Health check instance + * + * @return Result + * + * @NoAdminRequired + * @NoCSRFRequired + * @PublicPage + */ + public function healthcheck() { + $this->logger->debug("Federated healthcheck", ["app" => $this->appName]); + + return new Result(["alive" => true]); + } } diff --git a/controller/joblistcontroller.php b/controller/joblistcontroller.php index a1138043..562eecdf 100644 --- a/controller/joblistcontroller.php +++ b/controller/joblistcontroller.php @@ -39,84 +39,84 @@ * @package OCA\Onlyoffice\Controller */ class JobListController extends Controller { - /** - * Logger - * - * @var ILogger - */ - private $logger; + /** + * Logger + * + * @var ILogger + */ + private $logger; - /** - * Job list - * - * @var IJobList - */ - private $jobList; + /** + * Job list + * + * @var IJobList + */ + private $jobList; - /** - * Application configuration - * - * @var AppConfig - */ - private $config; + /** + * Application configuration + * + * @var AppConfig + */ + private $config; - /** - * JobListController constructor. - * - * @param string $AppName - application name - * @param IRequest $request - request object - * @param ILogger $logger - * @param AppConfig $config - application configuration - * @param IJobList $jobList - job list - */ - public function __construct($AppName, IRequest $request, ILogger $logger, AppConfig $config, IJobList $jobList) { - parent::__construct($AppName, $request); - $this->logger = $logger; - $this->config = $config; - $this->jobList = $jobList; - } + /** + * JobListController constructor. + * + * @param string $AppName - application name + * @param IRequest $request - request object + * @param ILogger $logger + * @param AppConfig $config - application configuration + * @param IJobList $jobList - job list + */ + public function __construct($AppName, IRequest $request, ILogger $logger, AppConfig $config, IJobList $jobList) { + parent::__construct($AppName, $request); + $this->logger = $logger; + $this->config = $config; + $this->jobList = $jobList; + } - /** - * Add a job to list - * - * @param IJob|string $job - */ - private function addJob($job) { - if (!$this->jobList->has($job, null)) { - $this->jobList->add($job); - $this->logger->debug("Job '".$job."' added to JobList.", ["app" => $this->appName]); - } - } + /** + * Add a job to list + * + * @param IJob|string $job + */ + private function addJob($job) { + if (!$this->jobList->has($job, null)) { + $this->jobList->add($job); + $this->logger->debug("Job '".$job."' added to JobList.", ["app" => $this->appName]); + } + } - /** - * Remove a job from list - * - * @param IJob|string $job - */ - private function removeJob($job) { - if ($this->jobList->has($job, null)) { - $this->jobList->remove($job); - $this->logger->debug("Job '".$job."' removed from JobList.", ["app" => $this->appName]); - } - } + /** + * Remove a job from list + * + * @param IJob|string $job + */ + private function removeJob($job) { + if ($this->jobList->has($job, null)) { + $this->jobList->remove($job); + $this->logger->debug("Job '".$job."' removed from JobList.", ["app" => $this->appName]); + } + } - /** - * Add or remove EditorsCheck job depending on the value of _editors_check_interval - * - */ - private function checkEditorsCheckJob() { - if ($this->config->GetEditorsCheckInterval() > 0) { - $this->addJob(EditorsCheck::class); - } else { - $this->removeJob(EditorsCheck::class); - } - } + /** + * Add or remove EditorsCheck job depending on the value of _editors_check_interval + * + */ + private function checkEditorsCheckJob() { + if ($this->config->GetEditorsCheckInterval() > 0) { + $this->addJob(EditorsCheck::class); + } else { + $this->removeJob(EditorsCheck::class); + } + } - /** - * Method for sequentially calling checks of all jobs - * - */ - public function checkAllJobs() { - $this->checkEditorsCheckJob(); - } + /** + * Method for sequentially calling checks of all jobs + * + */ + public function checkAllJobs() { + $this->checkEditorsCheckJob(); + } } diff --git a/controller/settingsapicontroller.php b/controller/settingsapicontroller.php index 83e2f6e8..45369859 100644 --- a/controller/settingsapicontroller.php +++ b/controller/settingsapicontroller.php @@ -30,54 +30,54 @@ * Settings controller for the administration page */ class SettingsApiController extends OCSController { - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; - /** - * Application configuration - * - * @var AppConfig - */ - private $config; + /** + * Application configuration + * + * @var AppConfig + */ + private $config; - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IURLGenerator $urlGenerator - url generator service - * @param AppConfig $config - application configuration - */ - public function __construct( - $AppName, - IRequest $request, - IURLGenerator $urlGenerator, - AppConfig $config - ) { - parent::__construct($AppName, $request); + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IURLGenerator $urlGenerator - url generator service + * @param AppConfig $config - application configuration + */ + public function __construct( + $AppName, + IRequest $request, + IURLGenerator $urlGenerator, + AppConfig $config + ) { + parent::__construct($AppName, $request); - $this->urlGenerator = $urlGenerator; - $this->config = $config; - } + $this->urlGenerator = $urlGenerator; + $this->config = $config; + } - /** - * Get document server url - * - * @return JSONResponse - * - * @NoAdminRequired - * @CORS - */ - public function GetDocServerUrl() { - $url = $this->config->GetDocumentServerUrl(); - if (!$this->config->SettingsAreSuccessful()) { - $url = ""; - } elseif (!preg_match("/^https?:\/\//i", $url)) { - $url = $this->urlGenerator->getAbsoluteURL($url); - } + /** + * Get document server url + * + * @return JSONResponse + * + * @NoAdminRequired + * @CORS + */ + public function GetDocServerUrl() { + $url = $this->config->GetDocumentServerUrl(); + if (!$this->config->SettingsAreSuccessful()) { + $url = ""; + } elseif (!preg_match("/^https?:\/\//i", $url)) { + $url = $this->urlGenerator->getAbsoluteURL($url); + } - return new JSONResponse(["documentServerUrl" => $url]); - } + return new JSONResponse(["documentServerUrl" => $url]); + } } diff --git a/controller/settingscontroller.php b/controller/settingscontroller.php index 4d3ba471..870b8675 100644 --- a/controller/settingscontroller.php +++ b/controller/settingscontroller.php @@ -36,292 +36,292 @@ * Settings controller for the administration page */ class SettingsController extends Controller { - /** - * l10n service - * - * @var IL10N - */ - private $trans; + /** + * l10n service + * + * @var IL10N + */ + private $trans; - /** - * Logger - * - * @var ILogger - */ - private $logger; + /** + * Logger + * + * @var ILogger + */ + private $logger; - /** - * Application configuration - * - * @var AppConfig - */ - private $config; + /** + * Application configuration + * + * @var AppConfig + */ + private $config; - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IURLGenerator $urlGenerator - url generator service - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param AppConfig $config - application configuration - * @param Crypt $crypt - hash generator - */ - public function __construct( - $AppName, - IRequest $request, - IURLGenerator $urlGenerator, - IL10N $trans, - ILogger $logger, - AppConfig $config, - Crypt $crypt - ) { - parent::__construct($AppName, $request); + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IURLGenerator $urlGenerator - url generator service + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param AppConfig $config - application configuration + * @param Crypt $crypt - hash generator + */ + public function __construct( + $AppName, + IRequest $request, + IURLGenerator $urlGenerator, + IL10N $trans, + ILogger $logger, + AppConfig $config, + Crypt $crypt + ) { + parent::__construct($AppName, $request); - $this->urlGenerator = $urlGenerator; - $this->trans = $trans; - $this->logger = $logger; - $this->config = $config; - $this->crypt = $crypt; - } + $this->urlGenerator = $urlGenerator; + $this->trans = $trans; + $this->logger = $logger; + $this->config = $config; + $this->crypt = $crypt; + } - /** - * Print config section - * - * @return TemplateResponse - */ - public function index() { - $data = [ - "documentserver" => $this->config->GetDocumentServerUrl(true), - "documentserverInternal" => $this->config->GetDocumentServerInternalUrl(true), - "storageUrl" => $this->config->GetStorageUrl(), - "verifyPeerOff" => $this->config->GetVerifyPeerOff(), - "secret" => $this->config->GetDocumentServerSecret(true), - "jwtHeader" => $this->config->JwtHeader(true), - "demo" => $this->config->GetDemoData(), - "currentServer" => $this->urlGenerator->getAbsoluteURL("/"), - "formats" => $this->config->FormatsSetting(), - "sameTab" => $this->config->GetSameTab(), - "preview" => $this->config->GetPreview(), - "versionHistory" => $this->config->GetVersionHistory(), - "protection" => $this->config->GetProtection(), - "encryption" => $this->config->checkEncryptionModule(), - "limitGroups" => $this->config->GetLimitGroups(), - "chat" => $this->config->GetCustomizationChat(), - "compactHeader" => $this->config->GetCustomizationCompactHeader(), - "feedback" => $this->config->GetCustomizationFeedback(), - "forcesave" => $this->config->GetCustomizationForcesave(), - "help" => $this->config->GetCustomizationHelp(), - "toolbarNoTabs" => $this->config->GetCustomizationToolbarNoTabs(), - "successful" => $this->config->SettingsAreSuccessful(), - "plugins" => $this->config->GetCustomizationPlugins(), - "macros" => $this->config->GetCustomizationMacros(), - "reviewDisplay" => $this->config->GetCustomizationReviewDisplay(), - "theme" => $this->config->GetCustomizationTheme(), - "templates" => $this->GetGlobalTemplates(), - "linkToDocs" => $this->config->GetLinkToDocs() - ]; - return new TemplateResponse($this->appName, "settings", $data, "blank"); - } + /** + * Print config section + * + * @return TemplateResponse + */ + public function index() { + $data = [ + "documentserver" => $this->config->GetDocumentServerUrl(true), + "documentserverInternal" => $this->config->GetDocumentServerInternalUrl(true), + "storageUrl" => $this->config->GetStorageUrl(), + "verifyPeerOff" => $this->config->GetVerifyPeerOff(), + "secret" => $this->config->GetDocumentServerSecret(true), + "jwtHeader" => $this->config->JwtHeader(true), + "demo" => $this->config->GetDemoData(), + "currentServer" => $this->urlGenerator->getAbsoluteURL("/"), + "formats" => $this->config->FormatsSetting(), + "sameTab" => $this->config->GetSameTab(), + "preview" => $this->config->GetPreview(), + "versionHistory" => $this->config->GetVersionHistory(), + "protection" => $this->config->GetProtection(), + "encryption" => $this->config->checkEncryptionModule(), + "limitGroups" => $this->config->GetLimitGroups(), + "chat" => $this->config->GetCustomizationChat(), + "compactHeader" => $this->config->GetCustomizationCompactHeader(), + "feedback" => $this->config->GetCustomizationFeedback(), + "forcesave" => $this->config->GetCustomizationForcesave(), + "help" => $this->config->GetCustomizationHelp(), + "toolbarNoTabs" => $this->config->GetCustomizationToolbarNoTabs(), + "successful" => $this->config->SettingsAreSuccessful(), + "plugins" => $this->config->GetCustomizationPlugins(), + "macros" => $this->config->GetCustomizationMacros(), + "reviewDisplay" => $this->config->GetCustomizationReviewDisplay(), + "theme" => $this->config->GetCustomizationTheme(), + "templates" => $this->GetGlobalTemplates(), + "linkToDocs" => $this->config->GetLinkToDocs() + ]; + return new TemplateResponse($this->appName, "settings", $data, "blank"); + } - /** - * Save address settings - * - * @param string $jwtHeader - jwt header - * @param string $documentserver - document service address - * @param string $documentserverInternal - document service address available from ownCloud - * @param string $storageUrl - ownCloud address available from document server - * @param bool $verifyPeerOff - parameter verification setting - * @param string $secret - secret key for signature - * @param bool $demo - use demo server - * - * @return array - */ - public function SaveAddress( - $documentserver, - $documentserverInternal, - $storageUrl, - $verifyPeerOff, - $secret, - $jwtHeader, - $demo - ) { - $error = null; - if (!$this->config->SelectDemo($demo === true)) { - $error = $this->trans->t("The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server."); - } - if ($demo !== true) { - $this->config->SetDocumentServerUrl($documentserver); - $this->config->SetVerifyPeerOff($verifyPeerOff); - $this->config->SetDocumentServerInternalUrl($documentserverInternal); - $this->config->SetDocumentServerSecret($secret); - $this->config->SetJwtHeader($jwtHeader); - } - $this->config->SetStorageUrl($storageUrl); + /** + * Save address settings + * + * @param string $jwtHeader - jwt header + * @param string $documentserver - document service address + * @param string $documentserverInternal - document service address available from ownCloud + * @param string $storageUrl - ownCloud address available from document server + * @param bool $verifyPeerOff - parameter verification setting + * @param string $secret - secret key for signature + * @param bool $demo - use demo server + * + * @return array + */ + public function SaveAddress( + $documentserver, + $documentserverInternal, + $storageUrl, + $verifyPeerOff, + $secret, + $jwtHeader, + $demo + ) { + $error = null; + if (!$this->config->SelectDemo($demo === true)) { + $error = $this->trans->t("The 30-day test period is over, you can no longer connect to demo ONLYOFFICE Docs server."); + } + if ($demo !== true) { + $this->config->SetDocumentServerUrl($documentserver); + $this->config->SetVerifyPeerOff($verifyPeerOff); + $this->config->SetDocumentServerInternalUrl($documentserverInternal); + $this->config->SetDocumentServerSecret($secret); + $this->config->SetJwtHeader($jwtHeader); + } + $this->config->SetStorageUrl($storageUrl); - $version = null; - if (empty($error)) { - $documentserver = $this->config->GetDocumentServerUrl(); - if (!empty($documentserver)) { - $documentService = new DocumentService($this->trans, $this->config); - list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); - $this->config->SetSettingsError($error); - } + $version = null; + if (empty($error)) { + $documentserver = $this->config->GetDocumentServerUrl(); + if (!empty($documentserver)) { + $documentService = new DocumentService($this->trans, $this->config); + list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); + $this->config->SetSettingsError($error); + } - if ($this->config->checkEncryptionModule() === true) { - $this->logger->info("SaveSettings when encryption is enabled", ["app" => $this->appName]); - } - } + if ($this->config->checkEncryptionModule() === true) { + $this->logger->info("SaveSettings when encryption is enabled", ["app" => $this->appName]); + } + } - return [ - "documentserver" => $this->config->GetDocumentServerUrl(true), - "verifyPeerOff" => $this->config->GetVerifyPeerOff(), - "documentserverInternal" => $this->config->GetDocumentServerInternalUrl(true), - "storageUrl" => $this->config->GetStorageUrl(), - "secret" => $this->config->GetDocumentServerSecret(true), - "jwtHeader" => $this->config->JwtHeader(true), - "error" => $error, - "version" => $version, - ]; - } + return [ + "documentserver" => $this->config->GetDocumentServerUrl(true), + "verifyPeerOff" => $this->config->GetVerifyPeerOff(), + "documentserverInternal" => $this->config->GetDocumentServerInternalUrl(true), + "storageUrl" => $this->config->GetStorageUrl(), + "secret" => $this->config->GetDocumentServerSecret(true), + "jwtHeader" => $this->config->JwtHeader(true), + "error" => $error, + "version" => $version, + ]; + } - /** - * Save common settings - * - * @param array $defFormats - formats array with default action - * @param array $editFormats - editable formats array - * @param bool $sameTab - open in the same tab - * @param bool $preview - generate preview files - * @param bool $versionHistory - keep version history - * @param array $limitGroups - list of groups - * @param bool $chat - display chat - * @param bool $compactHeader - display compact header - * @param bool $feedback - display feedback - * @param bool $forcesave - forcesave - * @param bool $help - display help - * @param bool $toolbarNoTabs - display toolbar tab - * @param string $reviewDisplay - review viewing mode - * @param string $theme - default theme mode - * - * @return array - */ - public function SaveCommon( - $defFormats, - $editFormats, - $sameTab, - $preview, - $versionHistory, - $limitGroups, - $chat, - $compactHeader, - $feedback, - $forcesave, - $help, - $toolbarNoTabs, - $reviewDisplay, - $theme - ) { - $this->config->SetDefaultFormats($defFormats); - $this->config->SetEditableFormats($editFormats); - $this->config->SetSameTab($sameTab); - $this->config->SetPreview($preview); - $this->config->SetVersionHistory($versionHistory); - $this->config->SetLimitGroups($limitGroups); - $this->config->SetCustomizationChat($chat); - $this->config->SetCustomizationCompactHeader($compactHeader); - $this->config->SetCustomizationFeedback($feedback); - $this->config->SetCustomizationForcesave($forcesave); - $this->config->SetCustomizationHelp($help); - $this->config->SetCustomizationToolbarNoTabs($toolbarNoTabs); - $this->config->SetCustomizationReviewDisplay($reviewDisplay); - $this->config->SetCustomizationTheme($theme); + /** + * Save common settings + * + * @param array $defFormats - formats array with default action + * @param array $editFormats - editable formats array + * @param bool $sameTab - open in the same tab + * @param bool $preview - generate preview files + * @param bool $versionHistory - keep version history + * @param array $limitGroups - list of groups + * @param bool $chat - display chat + * @param bool $compactHeader - display compact header + * @param bool $feedback - display feedback + * @param bool $forcesave - forcesave + * @param bool $help - display help + * @param bool $toolbarNoTabs - display toolbar tab + * @param string $reviewDisplay - review viewing mode + * @param string $theme - default theme mode + * + * @return array + */ + public function SaveCommon( + $defFormats, + $editFormats, + $sameTab, + $preview, + $versionHistory, + $limitGroups, + $chat, + $compactHeader, + $feedback, + $forcesave, + $help, + $toolbarNoTabs, + $reviewDisplay, + $theme + ) { + $this->config->SetDefaultFormats($defFormats); + $this->config->SetEditableFormats($editFormats); + $this->config->SetSameTab($sameTab); + $this->config->SetPreview($preview); + $this->config->SetVersionHistory($versionHistory); + $this->config->SetLimitGroups($limitGroups); + $this->config->SetCustomizationChat($chat); + $this->config->SetCustomizationCompactHeader($compactHeader); + $this->config->SetCustomizationFeedback($feedback); + $this->config->SetCustomizationForcesave($forcesave); + $this->config->SetCustomizationHelp($help); + $this->config->SetCustomizationToolbarNoTabs($toolbarNoTabs); + $this->config->SetCustomizationReviewDisplay($reviewDisplay); + $this->config->SetCustomizationTheme($theme); - return [ - ]; - } + return [ + ]; + } - /** - * Save security settings - * - * @param bool $plugins - enable plugins - * @param bool $macros - run document macros - * @param string $protection - protection - * - * @return array - */ - public function SaveSecurity( - $plugins, - $macros, - $protection - ) { - $this->config->SetCustomizationPlugins($plugins); - $this->config->SetCustomizationMacros($macros); - $this->config->SetProtection($protection); + /** + * Save security settings + * + * @param bool $plugins - enable plugins + * @param bool $macros - run document macros + * @param string $protection - protection + * + * @return array + */ + public function SaveSecurity( + $plugins, + $macros, + $protection + ) { + $this->config->SetCustomizationPlugins($plugins); + $this->config->SetCustomizationMacros($macros); + $this->config->SetProtection($protection); - return [ - ]; - } + return [ + ]; + } - /** - * Clear all version history - * - * @return array - */ - public function ClearHistory() { - FileVersions::clearHistory(); + /** + * Clear all version history + * + * @return array + */ + public function ClearHistory() { + FileVersions::clearHistory(); - return [ - ]; - } + return [ + ]; + } - /** - * Get app settings - * - * @return array - * - * @NoAdminRequired - * @PublicPage - */ - public function GetSettings() { - $result = [ - "formats" => $this->config->FormatsSetting(), - "sameTab" => $this->config->GetSameTab(), - "shareAttributesVersion" => $this->config->ShareAttributesVersion() - ]; - return $result; - } + /** + * Get app settings + * + * @return array + * + * @NoAdminRequired + * @PublicPage + */ + public function GetSettings() { + $result = [ + "formats" => $this->config->FormatsSetting(), + "sameTab" => $this->config->GetSameTab(), + "shareAttributesVersion" => $this->config->ShareAttributesVersion() + ]; + return $result; + } - /** - * Get global templates - * - * @return array - */ - private function GetGlobalTemplates() { - $templates = []; - $templatesList = TemplateManager::GetGlobalTemplates(); + /** + * Get global templates + * + * @return array + */ + private function GetGlobalTemplates() { + $templates = []; + $templatesList = TemplateManager::GetGlobalTemplates(); - foreach ($templatesList as $templateItem) { - $template = [ - "id" => $templateItem->getId(), - "name" => $templateItem->getName(), - "type" => TemplateManager::GetTypeTemplate($templateItem->getMimeType()) - ]; - array_push($templates, $template); - } + foreach ($templatesList as $templateItem) { + $template = [ + "id" => $templateItem->getId(), + "name" => $templateItem->getName(), + "type" => TemplateManager::GetTypeTemplate($templateItem->getMimeType()) + ]; + array_push($templates, $template); + } - return $templates; - } + return $templates; + } } diff --git a/controller/templatecontroller.php b/controller/templatecontroller.php index 98aa3da3..4dfc1317 100644 --- a/controller/templatecontroller.php +++ b/controller/templatecontroller.php @@ -30,132 +30,132 @@ * Template controller for template manage */ class TemplateController extends Controller { - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * @param string $AppName - application name - * @param IRequest $request - request object - * @param IL10N $trans - l10n service - */ - public function __construct( - $AppName, - IRequest $request, - IL10N $trans, - ILogger $logger - ) { - parent::__construct($AppName, $request); - - $this->trans = $trans; - $this->logger = $logger; - } - - /** - * Get templates - * - * @return array - * - * @NoAdminRequired - */ - public function GetTemplates() { - $templatesList = TemplateManager::GetGlobalTemplates(); - - $templates = []; - foreach ($templatesList as $templatesItem) { - $template = [ - "id" => $templatesItem->getId(), - "name" => $templatesItem->getName(), - "type" => TemplateManager::GetTypeTemplate($templatesItem->getMimeType()) - ]; - array_push($templates, $template); - } - - return $templates; - } - - /** - * Add global template - * - * @return array - */ - public function AddTemplate() { - $file = $this->request->getUploadedFile("file"); - - if ($file !== null) { - if (is_uploaded_file($file["tmp_name"]) && $file["error"] === 0) { - if (!TemplateManager::IsTemplateType($file["name"])) { - return [ - "error" => $this->trans->t("Template must be in OOXML format") - ]; - } - - $templateDir = TemplateManager::GetGlobalTemplateDir(); - if ($templateDir->nodeExists($file["name"])) { - return [ - "error" => $this->trans->t("Template already exists") - ]; - } - - $templateContent = file_get_contents($file["tmp_name"]); - $template = $templateDir->newFile($file["name"]); - $template->putContent($templateContent); - - $fileInfo = $template->getFileInfo(); - $result = [ - "id" => $fileInfo->getId(), - "name" => $fileInfo->getName(), - "type" => TemplateManager::GetTypeTemplate($fileInfo->getMimeType()) - ]; - - $this->logger->debug("Template: added " . $fileInfo->getName(), ["app" => $this->appName]); - - return $result; - } - } - - return [ - "error" => $this->trans->t("Invalid file provided") - ]; - } - - /** - * Delete template - * - * @param string $templateId - file identifier - */ - public function DeleteTemplate($templateId) { - $templateDir = TemplateManager::GetGlobalTemplateDir(); - - try { - $templates = $templateDir->getById($templateId); - } catch(\Exception $e) { - $this->logger->logException($e, ["message" => "DeleteTemplate: $templateId", "app" => $this->AppName]); - return [ - "error" => $this->trans->t("Failed to delete template") - ]; - } - - if (empty($templates)) { - $this->logger->info("Template not found: $templateId", ["app" => $this->AppName]); - return [ - "error" => $this->trans->t("Failed to delete template") - ]; - } - - $templates[0]->delete(); - - $this->logger->debug("Template: deleted " . $templates[0]->getName(), ["app" => $this->appName]); - return []; - } + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * @param string $AppName - application name + * @param IRequest $request - request object + * @param IL10N $trans - l10n service + */ + public function __construct( + $AppName, + IRequest $request, + IL10N $trans, + ILogger $logger + ) { + parent::__construct($AppName, $request); + + $this->trans = $trans; + $this->logger = $logger; + } + + /** + * Get templates + * + * @return array + * + * @NoAdminRequired + */ + public function GetTemplates() { + $templatesList = TemplateManager::GetGlobalTemplates(); + + $templates = []; + foreach ($templatesList as $templatesItem) { + $template = [ + "id" => $templatesItem->getId(), + "name" => $templatesItem->getName(), + "type" => TemplateManager::GetTypeTemplate($templatesItem->getMimeType()) + ]; + array_push($templates, $template); + } + + return $templates; + } + + /** + * Add global template + * + * @return array + */ + public function AddTemplate() { + $file = $this->request->getUploadedFile("file"); + + if ($file !== null) { + if (is_uploaded_file($file["tmp_name"]) && $file["error"] === 0) { + if (!TemplateManager::IsTemplateType($file["name"])) { + return [ + "error" => $this->trans->t("Template must be in OOXML format") + ]; + } + + $templateDir = TemplateManager::GetGlobalTemplateDir(); + if ($templateDir->nodeExists($file["name"])) { + return [ + "error" => $this->trans->t("Template already exists") + ]; + } + + $templateContent = file_get_contents($file["tmp_name"]); + $template = $templateDir->newFile($file["name"]); + $template->putContent($templateContent); + + $fileInfo = $template->getFileInfo(); + $result = [ + "id" => $fileInfo->getId(), + "name" => $fileInfo->getName(), + "type" => TemplateManager::GetTypeTemplate($fileInfo->getMimeType()) + ]; + + $this->logger->debug("Template: added " . $fileInfo->getName(), ["app" => $this->appName]); + + return $result; + } + } + + return [ + "error" => $this->trans->t("Invalid file provided") + ]; + } + + /** + * Delete template + * + * @param string $templateId - file identifier + */ + public function DeleteTemplate($templateId) { + $templateDir = TemplateManager::GetGlobalTemplateDir(); + + try { + $templates = $templateDir->getById($templateId); + } catch(\Exception $e) { + $this->logger->logException($e, ["message" => "DeleteTemplate: $templateId", "app" => $this->AppName]); + return [ + "error" => $this->trans->t("Failed to delete template") + ]; + } + + if (empty($templates)) { + $this->logger->info("Template not found: $templateId", ["app" => $this->AppName]); + return [ + "error" => $this->trans->t("Failed to delete template") + ]; + } + + $templates[0]->delete(); + + $this->logger->debug("Template: deleted " . $templates[0]->getName(), ["app" => $this->appName]); + return []; + } } diff --git a/controller/webassetcontroller.php b/controller/webassetcontroller.php index 3e03ab7f..c904fbb2 100644 --- a/controller/webassetcontroller.php +++ b/controller/webassetcontroller.php @@ -33,46 +33,46 @@ * @package OCA\Onlyoffice\Controller */ class WebAssetController extends Controller { - /** - * @var ILogger - */ - private $logger; + /** + * @var ILogger + */ + private $logger; - /** - * WebAssetController constructor. - * - * @param string $AppName - application name - * @param IRequest $request - request object - * @param ILogger $logger - */ - public function __construct($AppName, IRequest $request, ILogger $logger) { - parent::__construct($AppName, $request); - $this->logger = $logger; - } + /** + * WebAssetController constructor. + * + * @param string $AppName - application name + * @param IRequest $request - request object + * @param ILogger $logger + */ + public function __construct($AppName, IRequest $request, ILogger $logger) { + parent::__construct($AppName, $request); + $this->logger = $logger; + } - /** - * Loads the onlyoffice.js file for integration into ownCloud Web - * - * @PublicPage - * @NoCSRFRequired - * - * @return Response - */ - public function get(): Response { - $basePath = \dirname(__DIR__, 1); - $filePath = \realpath($basePath . '/js/web/onlyoffice.js'); - try { - return new DataDisplayResponse(\file_get_contents($filePath), Http::STATUS_OK, [ - 'Content-Type' => "text/javascript", - 'Content-Length' => \filesize($filePath), - 'Cache-Control' => 'max-age=0, no-cache, no-store, must-revalidate', - 'Pragma' => 'no-cache', - 'Expires' => 'Tue, 24 Sep 1985 22:15:00 GMT', - 'X-Frame-Options' => 'DENY' - ]); - } catch(\Exception $e) { - $this->logger->logException($e, ['app' => $this->appName]); - return new DataResponse(["message" => $e->getMessage()], Http::STATUS_NOT_FOUND); - } - } + /** + * Loads the onlyoffice.js file for integration into ownCloud Web + * + * @PublicPage + * @NoCSRFRequired + * + * @return Response + */ + public function get(): Response { + $basePath = \dirname(__DIR__, 1); + $filePath = \realpath($basePath . '/js/web/onlyoffice.js'); + try { + return new DataDisplayResponse(\file_get_contents($filePath), Http::STATUS_OK, [ + 'Content-Type' => "text/javascript", + 'Content-Length' => \filesize($filePath), + 'Cache-Control' => 'max-age=0, no-cache, no-store, must-revalidate', + 'Pragma' => 'no-cache', + 'Expires' => 'Tue, 24 Sep 1985 22:15:00 GMT', + 'X-Frame-Options' => 'DENY' + ]); + } catch(\Exception $e) { + $this->logger->logException($e, ['app' => $this->appName]); + return new DataResponse(["message" => $e->getMessage()], Http::STATUS_NOT_FOUND); + } + } } diff --git a/lib/adminsettings.php b/lib/adminsettings.php index 79b68135..14171bc4 100644 --- a/lib/adminsettings.php +++ b/lib/adminsettings.php @@ -25,33 +25,33 @@ * Settings controller for the administration page */ class AdminSettings implements ISettings { - public function __construct() { - } + public function __construct() { + } - /** - * Print config section - * - * @return TemplateResponse - */ - public function getPanel() { - return $this->getForm(); - } + /** + * Print config section + * + * @return TemplateResponse + */ + public function getPanel() { + return $this->getForm(); + } - /** - * Get section ID - * - * @return string - */ - public function getSectionID() { - return "general"; - } + /** + * Get section ID + * + * @return string + */ + public function getSectionID() { + return "general"; + } - /** - * Get priority order - * - * @return int - */ - public function getPriority() { - return 50; - } + /** + * Get priority order + * + * @return int + */ + public function getPriority() { + return 50; + } } diff --git a/lib/appconfig.php b/lib/appconfig.php index ddbe49f1..8dac94f2 100644 --- a/lib/appconfig.php +++ b/lib/appconfig.php @@ -31,1231 +31,1231 @@ * @package OCA\Onlyoffice */ class AppConfig { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * Config service - * - * @var IConfig - */ - private $config; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * The config key for the demo server - * - * @var string - */ - private $_demo = "demo"; - - /** - * The config key for the document server address - * - * @var string - */ - private $_documentserver = "DocumentServerUrl"; - - /** - * The config key for the document server address available from ownCloud - * - * @var string - */ - private $_documentserverInternal = "DocumentServerInternalUrl"; - - /** - * The config key for the ownCloud address available from document server - * - * @var string - */ - private $_storageUrl = "StorageUrl"; - - /** - * The config key for the secret key - * - * @var string - */ - private $_cryptSecret = "secret"; - - /** - * The config key for the default formats - * - * @var string - */ - private $_defFormats = "defFormats"; - - /** - * The config key for the editable formats - * - * @var string - */ - private $_editFormats = "editFormats"; - - /** - * The config key for the setting same tab - * - * @var string - */ - private $_sameTab = "sameTab"; - - /** - * The config key for the generate preview - * - * @var string - */ - private $_preview = "preview"; - - /** - * The config key for the keep versions history - * - * @var string - */ - private $_versionHistory = "versionHistory"; - - /** - * The config key for the protection - * - * @var string - */ - private $_protection = "protection"; - - /** - * The config key for the chat display setting - * - * @var string - */ - private $_customizationChat = "customizationChat"; - - /** - * The config key for display the header more compact setting - * - * @var string - */ - private $_customizationCompactHeader = "customizationCompactHeader"; - - /** - * The config key for the feedback display setting - * - * @var string - */ - private $_customizationFeedback = "customizationFeedback"; - - /** - * The config key for the forcesave setting - * - * @var string - */ - private $_customizationForcesave = "customizationForcesave"; - - /** - * The config key for the help display setting - * - * @var string - */ - private $_customizationHelp = "customizationHelp"; - - /** - * The config key for the no tabs setting - * - * @var string - */ - private $_customizationToolbarNoTabs = "customizationToolbarNoTabs"; - - /** - * The config key for the review mode setting - * - * @var string - */ - private $_customizationReviewDisplay = "customizationReviewDisplay"; - - /** - * The config key for the theme setting - * - * @var string - */ - private $_customizationTheme = "customizationTheme"; - - /** - * The config key for the setting limit groups - * - * @var string - */ - private $_groups = "groups"; - - /** - * The config key for the verification - * - * @var string - */ - private $_verification = "verify_peer_off"; - - /** - * The config key for the secret key in jwt - * - * @var string - */ - private $_jwtSecret = "jwt_secret"; - - /** - * The config key for the jwt header - * - * @var string - */ - private $_jwtHeader = "jwt_header"; - - /** - * The config key for the allowable leeway in Jwt checks - * - * @var string - */ - private $_jwtLeeway = "jwt_leeway"; - - /** - * The config key for the settings error - * - * @var string - */ - private $_settingsError = "settings_error"; - - /** - * The config key for limit thumbnail size - * - * @var string - */ - public $_limitThumbSize = "limit_thumb_size"; - - /** - * The config key for the customer - * - * @var string - */ - public $_customization_customer = "customization_customer"; - - /** - * The config key for the loaderLogo - * - * @var string - */ - public $_customization_loaderLogo = "customization_loaderLogo"; - - /** - * The config key for the loaderName - * - * @var string - */ - public $_customization_loaderName = "customization_loaderName"; - - /** - * The config key for the logo - * - * @var string - */ - public $_customization_logo = "customization_logo"; - - /** - * The config key for the zoom - * - * @var string - */ - public $_customization_zoom = "customization_zoom"; - - /** - * The config key for the autosave - * - * @var string - */ - public $_customization_autosave = "customization_autosave"; - - /** - * The config key for the goback - * - * @var string - */ - public $_customization_goback = "customization_goback"; - - /** - * The config key for the macros - * - * @var string - */ - public $_customization_macros = "customization_macros"; - - /** - * The config key for the plugins - * - * @var string - */ - public $_customizationPlugins = "customization_plugins"; - - /** - * The config key for the interval of editors availability check by cron - * - * @var string - */ - private $_editors_check_interval = "editors_check_interval"; - - /** - * @param string $AppName - application name - */ - public function __construct($AppName) { - $this->appName = $AppName; - - $this->config = \OC::$server->getConfig(); - $this->logger = \OC::$server->getLogger(); - } - - /** - * Get value from the system configuration - * - * @param string $key - key configuration - * @param bool $system - get from root or from app section - * - * @return string - */ - public function GetSystemValue($key, $system = false) { - if ($system) { - return $this->config->getSystemValue($key); - } - if (!empty($this->config->getSystemValue($this->appName)) - && \array_key_exists($key, $this->config->getSystemValue($this->appName))) { - return $this->config->getSystemValue($this->appName)[$key]; - } - return null; - } - - /** - * Switch on demo server - * - * @param bool $value - select demo - * - * @return bool - */ - public function SelectDemo($value) { - $this->logger->info("Select demo: " . json_encode($value), ["app" => $this->appName]); - - $data = $this->GetDemoData(); - - if ($value === true && !$data["available"]) { - $this->logger->info("Trial demo is overdue: " . json_encode($data), ["app" => $this->appName]); - return false; - } - - $data["enabled"] = $value === true; - if (!isset($data["start"])) { - $data["start"] = new DateTime(); - } - - $this->config->setAppValue($this->appName, $this->_demo, json_encode($data)); - return true; - } - - /** - * Get demo data - * - * @return array - */ - public function GetDemoData() { - $data = $this->config->getAppValue($this->appName, $this->_demo, ""); - - if (empty($data)) { - return [ - "available" => true, - "enabled" => false - ]; - } - $data = json_decode($data, true); - - $overdue = new DateTime(isset($data["start"]) ? $data["start"]["date"] : null); - $overdue->add(new DateInterval("P" . $this->DEMO_PARAM["TRIAL"] . "D")); - if ($overdue > new DateTime()) { - $data["available"] = true; - $data["enabled"] = $data["enabled"] === true; - } else { - $data["available"] = false; - $data["enabled"] = false; - } - - return $data; - } - - /** - * Get status of demo server - * - * @return bool - */ - public function UseDemo() { - return $this->GetDemoData()["enabled"] === true; - } - - /** - * Save the document service address to the application configuration - * - * @param string $documentServer - document service address - */ - public function SetDocumentServerUrl($documentServer) { - $documentServer = trim($documentServer); - if (\strlen($documentServer) > 0) { - $documentServer = rtrim($documentServer, "/") . "/"; - if (!preg_match("/(^https?:\/\/)|^\//i", $documentServer)) { - $documentServer = "http://" . $documentServer; - } - } - - $this->logger->info("SetDocumentServerUrl: $documentServer", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_documentserver, $documentServer); - } - - /** - * Get the document service address from the application configuration - * - * @param bool $origin - take origin - * - * @return string - */ - public function GetDocumentServerUrl($origin = false) { - if (!$origin && $this->UseDemo()) { - return $this->DEMO_PARAM["ADDR"]; - } - - $url = $this->config->getAppValue($this->appName, $this->_documentserver, ""); - if (empty($url)) { - $url = $this->GetSystemValue($this->_documentserver); - } - if ($url !== "/") { - $url = rtrim($url, "/"); - if (\strlen($url) > 0) { - $url = $url . "/"; - } - } - return $url; - } - - /** - * Save the document service address available from ownCloud to the application configuration - * - * @param string $documentServerInternal - document service address - */ - public function SetDocumentServerInternalUrl($documentServerInternal) { - $documentServerInternal = rtrim(trim($documentServerInternal), "/"); - if (\strlen($documentServerInternal) > 0) { - $documentServerInternal = $documentServerInternal . "/"; - if (!preg_match("/^https?:\/\//i", $documentServerInternal)) { - $documentServerInternal = "http://" . $documentServerInternal; - } - } - - $this->logger->info("SetDocumentServerInternalUrl: $documentServerInternal", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_documentserverInternal, $documentServerInternal); - } - - /** - * Get the document service address available from ownCloud from the application configuration - * - * @param bool $origin - take origin - * - * @return string - */ - public function GetDocumentServerInternalUrl($origin = false) { - if (!$origin && $this->UseDemo()) { - return $this->GetDocumentServerUrl(); - } - - $url = $this->config->getAppValue($this->appName, $this->_documentserverInternal, ""); - if (empty($url)) { - $url = $this->GetSystemValue($this->_documentserverInternal); - } - if (!$origin && empty($url)) { - $url = $this->GetDocumentServerUrl(); - } - return $url; - } - - /** - * Replace domain in document server url with internal address from configuration - * - * @param string $url - document server url - * - * @return string - */ - public function ReplaceDocumentServerUrlToInternal($url) { - $documentServerUrl = $this->GetDocumentServerInternalUrl(); - if (!empty($documentServerUrl)) { - $from = $this->GetDocumentServerUrl(); - - if (!preg_match("/^https?:\/\//i", $from)) { - $parsedUrl = parse_url($url); - $from = $parsedUrl["scheme"] . "://" . $parsedUrl["host"] . (\array_key_exists("port", $parsedUrl) ? (":" . $parsedUrl["port"]) : "") . $from; - } - - if ($from !== $documentServerUrl) { - $this->logger->debug("Replace url from $from to $documentServerUrl", ["app" => $this->appName]); - $url = str_replace($from, $documentServerUrl, $url); - } - } - - return $url; - } - - /** - * Save the ownCloud address available from document server to the application configuration - * - * @param string $documentServer - document service address - */ - public function SetStorageUrl($storageUrl) { - $storageUrl = rtrim(trim($storageUrl), "/"); - if (\strlen($storageUrl) > 0) { - $storageUrl = $storageUrl . "/"; - if (!preg_match("/^https?:\/\//i", $storageUrl)) { - $storageUrl = "http://" . $storageUrl; - } - } - - $this->logger->info("SetStorageUrl: $storageUrl", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_storageUrl, $storageUrl); - } - - /** - * Get the ownCloud address available from document server from the application configuration - * - * @return string - */ - public function GetStorageUrl() { - $url = $this->config->getAppValue($this->appName, $this->_storageUrl, ""); - if (empty($url)) { - $url = $this->GetSystemValue($this->_storageUrl); - } - return $url; - } - - /** - * Save the document service secret key to the application configuration - * - * @param string $secret - secret key - */ - public function SetDocumentServerSecret($secret) { - $secret = trim($secret); - if (empty($secret)) { - $this->logger->info("Clear secret key", ["app" => $this->appName]); - } else { - $this->logger->info("Set secret key", ["app" => $this->appName]); - } - - $this->config->setAppValue($this->appName, $this->_jwtSecret, $secret); - } - - /** - * Get the document service secret key from the application configuration - * - * @param bool $origin - take origin - * - * @return string - */ - public function GetDocumentServerSecret($origin = false) { - if (!$origin && $this->UseDemo()) { - return $this->DEMO_PARAM["SECRET"]; - } - - $secret = $this->config->getAppValue($this->appName, $this->_jwtSecret, ""); - if (empty($secret)) { - $secret = $this->GetSystemValue($this->_jwtSecret); - } - return $secret; - } - - /** - * Get the secret key from the application configuration - * - * @return string - */ - public function GetSKey() { - $secret = $this->GetDocumentServerSecret(); - if (empty($secret)) { - $secret = $this->GetSystemValue($this->_cryptSecret, true); - } - return $secret; - } - - /** - * Save an array of formats with default action - * - * @param array $formats - formats with status - */ - public function SetDefaultFormats($formats) { - $value = json_encode($formats); - $this->logger->info("Set default formats: $value", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_defFormats, $value); - } - - /** - * Get an array of formats with default action - * - * @return array - */ - private function GetDefaultFormats() { - $value = $this->config->getAppValue($this->appName, $this->_defFormats, ""); - if (empty($value)) { - return []; - } - return json_decode($value, true); - } - - /** - * Save an array of formats that is opened for editing - * - * @param array $formats - formats with status - */ - public function SetEditableFormats($formats) { - $value = json_encode($formats); - $this->logger->info("Set editing formats: $value", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_editFormats, $value); - } - - /** - * Get an array of formats opening for editing - * - * @return array - */ - private function GetEditableFormats() { - $value = $this->config->getAppValue($this->appName, $this->_editFormats, ""); - if (empty($value)) { - return []; - } - return json_decode($value, true); - } - - /** - * Save the opening setting in a same tab - * - * @param bool $value - same tab - */ - public function SetSameTab($value) { - $this->logger->info("Set opening in a same tab: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_sameTab, json_encode($value)); - } - - /** - * Get the opening setting in a same tab - * - * @return bool - */ - public function GetSameTab() { - return $this->config->getAppValue($this->appName, $this->_sameTab, "false") === "true"; - } - - /** - * Save generate preview setting - * - * @param bool $value - preview - */ - public function SetPreview($value) { - $this->logger->info("Set generate preview: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_preview, json_encode($value)); - } - - /** - * Get generate preview setting - * - * @return bool - */ - public function GetPreview() { - return $this->config->getAppValue($this->appName, $this->_preview, "true") === "true"; - } - - /** - * Save keep versions history - * - * @param bool $value - version history - */ - public function SetVersionHistory($value) { - $this->logger->info("Set keep versions history: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_versionHistory, json_encode($value)); - } - - /** - * Get keep versions history - * - * @return bool - */ - public function GetVersionHistory() { - return $this->config->getAppValue($this->appName, $this->_versionHistory, "true") === "true"; - } - - /** - * Save protection - * - * @param bool $value - version history - */ - public function SetProtection($value) { - $this->logger->info("Set protection: " . $value, ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_protection, $value); - } - - /** - * Get protection - * - * @return bool - */ - public function GetProtection() { - $value = $this->config->getAppValue($this->appName, $this->_protection, "owner"); - if ($value === "all") { - return "all"; - } - return "owner"; - } - - /** - * Save chat display setting - * - * @param bool $value - display chat - */ - public function SetCustomizationChat($value) { - $this->logger->info("Set chat display: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationChat, json_encode($value)); - } - - /** - * Get chat display setting - * - * @return bool - */ - public function GetCustomizationChat() { - return $this->config->getAppValue($this->appName, $this->_customizationChat, "true") === "true"; - } - - /** - * Save compact header setting - * - * @param bool $value - display compact header - */ - public function SetCustomizationCompactHeader($value) { - $this->logger->info("Set compact header display: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationCompactHeader, json_encode($value)); - } - - /** - * Get compact header setting - * - * @return bool - */ - public function GetCustomizationCompactHeader() { - return $this->config->getAppValue($this->appName, $this->_customizationCompactHeader, "true") === "true"; - } - - /** - * Save feedback display setting - * - * @param bool $value - display feedback - */ - public function SetCustomizationFeedback($value) { - $this->logger->info("Set feedback display: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationFeedback, json_encode($value)); - } - - /** - * Get feedback display setting - * - * @return bool - */ - public function GetCustomizationFeedback() { - return $this->config->getAppValue($this->appName, $this->_customizationFeedback, "true") === "true"; - } - - /** - * Save forcesave setting - * - * @param bool $value - forcesave - */ - public function SetCustomizationForcesave($value) { - $this->logger->info("Set forcesave: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationForcesave, json_encode($value)); - } - - /** - * Get forcesave setting - * - * @return bool - */ - public function GetCustomizationForcesave() { - $value = $this->config->getAppValue($this->appName, $this->_customizationForcesave, "false") === "true"; - - return $value && ($this->checkEncryptionModule() === false); - } - - /** - * Save help display setting - * - * @param bool $value - display help - */ - public function SetCustomizationHelp($value) { - $this->logger->info("Set help display: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationHelp, json_encode($value)); - } - - /** - * Get help display setting - * - * @return bool - */ - public function GetCustomizationHelp() { - return $this->config->getAppValue($this->appName, $this->_customizationHelp, "true") === "true"; - } - - /** - * Save without tabs setting - * - * @param bool $value - without tabs - */ - public function SetCustomizationToolbarNoTabs($value) { - $this->logger->info("Set without tabs: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationToolbarNoTabs, json_encode($value)); - } - - /** - * Get without tabs setting - * - * @return bool - */ - public function GetCustomizationToolbarNoTabs() { - return $this->config->getAppValue($this->appName, $this->_customizationToolbarNoTabs, "true") === "true"; - } - - /** - * Save review viewing mode setting - * - * @param string $value - review mode - */ - public function SetCustomizationReviewDisplay($value) { - $this->logger->info("Set review mode: " . $value, ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationReviewDisplay, $value); - } - - /** - * Get review viewing mode setting - * - * @return string - */ - public function GetCustomizationReviewDisplay() { - $value = $this->config->getAppValue($this->appName, $this->_customizationReviewDisplay, "original"); - if ($value === "markup") { - return "markup"; - } - if ($value === "final") { - return "final"; - } - return "original"; - } - - /** - * Save theme setting - * - * @param string $value - theme - */ - public function SetCustomizationTheme($value) { - $this->logger->info("Set theme: " . $value, ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationTheme, $value); - } - - /** - * Get theme setting - * - * @return string - */ - public function GetCustomizationTheme() { - $value = $this->config->getAppValue($this->appName, $this->_customizationTheme, "theme-classic-light"); - if ($value === "theme-light") { - return "theme-light"; - } - if ($value === "theme-dark") { - return "theme-dark"; - } - return "theme-classic-light"; - } - - /** - * Save macros setting - * - * @param bool $value - enable macros - */ - public function SetCustomizationMacros($value) { - $this->logger->info("Set macros enabled: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customization_macros, json_encode($value)); - } - - /** - * Get macros setting - * - * @return bool - */ - public function GetCustomizationMacros() { - return $this->config->getAppValue($this->appName, $this->_customization_macros, "true") === "true"; - } - - /** - * Save plugins setting - * - * @param bool $value - enable macros - */ - public function SetCustomizationPlugins($value) { - $this->logger->info("Set plugins enabled: " . json_encode($value), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_customizationPlugins, json_encode($value)); - } - - /** - * Get plugins setting - * - * @return bool - */ - public function GetCustomizationPlugins() { - return $this->config->getAppValue($this->appName, $this->_customizationPlugins, "true") === "true"; - } - - /** - * Save the list of groups - * - * @param array $groups - the list of groups - */ - public function SetLimitGroups($groups) { - if (!\is_array($groups)) { - $groups = []; - } - $value = json_encode($groups); - $this->logger->info("Set groups: $value", ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_groups, $value); - } - - /** - * Get the list of groups - * - * @return array - */ - public function GetLimitGroups() { - $value = $this->config->getAppValue($this->appName, $this->_groups, ""); - if (empty($value)) { - return []; - } - $groups = json_decode($value, true); - if (!\is_array($groups)) { - $groups = []; - } - return $groups; - } - - /** - * Check access for group - * - * @param string $userId - user identifier - * - * @return bool - */ - public function isUserAllowedToUse($userId = null) { - // no user -> no - $userSession = \OC::$server->getUserSession(); - if ($userId === null && ($userSession === null || !$userSession->isLoggedIn())) { - return false; - } - - $groups = $this->GetLimitGroups(); - // no group set -> all users are allowed - if (\count($groups) === 0) { - return true; - } - - if ($userId === null) { - $user = $userSession->getUser(); - } else { - $user = \OC::$server->getUserManager()->get($userId); - if (empty($user)) { - return false; - } - } - - foreach ($groups as $groupName) { - // group unknown -> error and allow nobody - $group = \OC::$server->getGroupManager()->get($groupName); - if ($group === null) { - \OC::$server->getLogger()->error("Group is unknown $groupName", ["app" => $this->appName]); - $this->SetLimitGroups(array_diff($groups, [$groupName])); - } else { - if ($group->inGroup($user)) { - return true; - } - } - } - - return false; - } - - /** - * Save the document service verification setting to the application configuration - * - * @param bool $verifyPeerOff - parameter verification setting - */ - public function SetVerifyPeerOff($verifyPeerOff) { - $this->logger->info("SetVerifyPeerOff " . json_encode($verifyPeerOff), ["app" => $this->appName]); - - $this->config->setAppValue($this->appName, $this->_verification, json_encode($verifyPeerOff)); - } - - /** - * Get the document service verification setting to the application configuration - * - * @return bool - */ - public function GetVerifyPeerOff() { - $turnOff = $this->config->getAppValue($this->appName, $this->_verification, ""); - - if (!empty($turnOff)) { - return $turnOff === "true"; - } - - return $this->GetSystemValue($this->_verification); - } - - /** - * Get the limit on size document when generating thumbnails - * - * @return int - */ - public function GetLimitThumbSize() { - $limitSize = (integer)$this->GetSystemValue($this->_limitThumbSize); - - if (!empty($limitSize)) { - return $limitSize; - } - - return 100*1024*1024; - } - - /** - * Get the jwt header setting - * - * @param bool $origin - take origin - * - * @return string - */ - public function JwtHeader($origin = false) { - if (!$origin && $this->UseDemo()) { - return $this->DEMO_PARAM["HEADER"]; - } - - $header = $this->config->getAppValue($this->appName, $this->_jwtHeader, ""); - if (empty($header)) { - $header = $this->GetSystemValue($this->_jwtHeader); - } - if (!$origin && empty($header)) { - $header = "Authorization"; - } - return $header; - } - - /** - * Save the jwtHeader setting - * - * @param string $value - jwtHeader - */ - public function SetJwtHeader($value) { - $value = trim($value); - if (empty($value)) { - $this->logger->info("Clear header key", ["app" => $this->appName]); - } else { - $this->logger->info("Set header key " . $value, ["app" => $this->appName]); - } - - $this->config->setAppValue($this->appName, $this->_jwtHeader, $value); - } - - /** - * Get the Jwt Leeway - * - * @return int - */ - public function GetJwtLeeway() { - $jwtLeeway = (integer)$this->GetSystemValue($this->_jwtLeeway); - - return $jwtLeeway; - } - - /** - * Save the status settings - * - * @param string $value - error - */ - public function SetSettingsError($value) { - $this->config->setAppValue($this->appName, $this->_settingsError, $value); - } - - /** - * Get the status settings - * - * @return bool - */ - public function SettingsAreSuccessful() { - return empty($this->config->getAppValue($this->appName, $this->_settingsError, "")); - } - - /** - * Checking encryption enabled - * - * @return string|bool - */ - public function checkEncryptionModule() { - if (!\OC::$server->getAppManager()->isInstalled("encryption")) { - return false; - } - if (!\OC::$server->getEncryptionManager()->isEnabled()) { - return false; - } - - $crypt = new \OCA\Encryption\Crypto\Crypt(\OC::$server->getLogger(), \OC::$server->getUserSession(), \OC::$server->getConfig(), \OC::$server->getL10N("encryption")); - $util = new \OCA\Encryption\Util(new \OC\Files\View(), $crypt, \OC::$server->getLogger(), \OC::$server->getUserSession(), \OC::$server->getConfig(), \OC::$server->getUserManager()); - if ($util->isMasterKeyEnabled()) { - return "master"; - } - - return true; - } - - /** - * Get supported formats - * - * @return array - * - * @NoAdminRequired - */ - public function FormatsSetting() { - $result = $this->formats; - - $defFormats = $this->GetDefaultFormats(); - foreach ($defFormats as $format => $setting) { - if (\array_key_exists($format, $result)) { - $result[$format]["def"] = ($setting === true || $setting === "true"); - } - } - - $editFormats = $this->GetEditableFormats(); - foreach ($editFormats as $format => $setting) { - if (\array_key_exists($format, $result)) { - $result[$format]["edit"] = ($setting === true || $setting === "true"); - } - } - - return $result; - } - - public function ShareAttributesVersion() { - if (\version_compare(\implode(".", \OCP\Util::getVersion()), "10.3.0", ">=")) { - return "v2"; - } elseif (\version_compare(\implode(".", \OCP\Util::getVersion()), "10.2.0", ">=")) { - return "v1"; - } - return ""; - } - - /** - * Get the editors check interval - * - * @return int - */ - public function GetEditorsCheckInterval() { - $interval = $this->GetSystemValue($this->_editors_check_interval); - - if (empty($interval) && $interval !== 0) { - $interval = 60*60*24; - } - return (integer)$interval; - } - - /** - * Additional data about formats - * - * @var array - */ - private $formats = [ - "csv" => [ "mime" => "text/csv", "type" => "cell", "edit" => true, "editable" => true, "saveas" => ["ods", "pdf", "xlsx"] ], - "doc" => [ "mime" => "application/msword", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "docm" => [ "mime" => "application/vnd.ms-word.document.macroEnabled.12", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "docx" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "type" => "word", "edit" => true, "def" => true, "review" => true, "comment" => true, "saveas" => ["odt", "pdf", "rtf", "txt"] ], - "docxf" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf", "type" => "word", "edit" => true, "def" => true, "review" => true, "comment" => true, "saveas" => ["odt", "pdf", "rtf", "txt"], "createForm" => true ], - "oform" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform", "type" => "word", "fillForms" => true, "def" => true ], - "dot" => [ "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "dotx" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "epub" => [ "mime" => "application/epub+zip", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "htm" => [ "type" => "word", "conv" => true ], - "html" => [ "mime" => "text/html", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "odp" => [ "mime" => "application/vnd.oasis.opendocument.presentation", "type" => "slide", "conv" => true, "editable" => true, "saveas" => ["pdf", "pptx"] ], - "ods" => [ "mime" => "application/vnd.oasis.opendocument.spreadsheet", "type" => "cell", "conv" => true, "editable" => true, "saveas" => ["csv", "pdf", "xlsx"] ], - "odt" => [ "mime" => "application/vnd.oasis.opendocument.text", "type" => "word", "conv" => true, "editable" => true, "saveas" => ["docx", "pdf", "rtf", "txt"] ], - "otp" => [ "mime" => "application/vnd.oasis.opendocument.presentation-template", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "ots" => [ "mime" => "application/vnd.oasis.opendocument.spreadsheet-template", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], - "ott" => [ "mime" => "application/vnd.oasis.opendocument.text-template", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], - "pdf" => [ "mime" => "application/pdf", "type" => "word" ], - "pot" => [ "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "potm" => [ "mime" => "application/vnd.ms-powerpoint.template.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "potx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.template", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "pps" => [ "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "ppsm" => [ "mime" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "ppsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "ppt" => [ "mime" => "application/vnd.ms-powerpoint", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "pptm" => [ "mime" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], - "pptx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", "type" => "slide", "edit" => true, "def" => true, "comment" => true, "saveas" => ["pdf", "odp"] ], - "rtf" => [ "mime" => "text/rtf", "type" => "word", "conv" => true, "editable" => true, "saveas" => ["docx", "odt", "pdf", "txt"] ], - "txt" => [ "mime" => "text/plain", "type" => "word", "edit" => true, "editable" => true, "saveas" => ["docx", "odt", "pdf", "rtf"] ], - "xls" => [ "mime" => "application/vnd.ms-excel", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], - "xlsm" => [ "mime" => "application/vnd.ms-excel.sheet.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], - "xlsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "type" => "cell", "edit" => true, "def" => true, "comment" => true, "modifyFilter" => true, "saveas" => ["csv", "ods", "pdf"] ], - "xlt" => [ "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], - "xltm" => [ "mime" => "application/vnd.ms-excel.template.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], - "xltx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ] - ]; - - /** - * DEMO DATA - */ - private $DEMO_PARAM = [ - "ADDR" => "https://onlinedocs.onlyoffice.com/", - "HEADER" => "AuthorizationJWT", - "SECRET" => "sn2puSUF7muF5Jas", - "TRIAL" => 30 - ]; - - private $linkToDocs = "https://www.onlyoffice.com/docs-registration.aspx?referer=owncloud"; - - /** - * Get link to Docs Cloud - * - * @return string - */ - public function GetLinkToDocs() { - return $this->linkToDocs; - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * Config service + * + * @var IConfig + */ + private $config; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * The config key for the demo server + * + * @var string + */ + private $_demo = "demo"; + + /** + * The config key for the document server address + * + * @var string + */ + private $_documentserver = "DocumentServerUrl"; + + /** + * The config key for the document server address available from ownCloud + * + * @var string + */ + private $_documentserverInternal = "DocumentServerInternalUrl"; + + /** + * The config key for the ownCloud address available from document server + * + * @var string + */ + private $_storageUrl = "StorageUrl"; + + /** + * The config key for the secret key + * + * @var string + */ + private $_cryptSecret = "secret"; + + /** + * The config key for the default formats + * + * @var string + */ + private $_defFormats = "defFormats"; + + /** + * The config key for the editable formats + * + * @var string + */ + private $_editFormats = "editFormats"; + + /** + * The config key for the setting same tab + * + * @var string + */ + private $_sameTab = "sameTab"; + + /** + * The config key for the generate preview + * + * @var string + */ + private $_preview = "preview"; + + /** + * The config key for the keep versions history + * + * @var string + */ + private $_versionHistory = "versionHistory"; + + /** + * The config key for the protection + * + * @var string + */ + private $_protection = "protection"; + + /** + * The config key for the chat display setting + * + * @var string + */ + private $_customizationChat = "customizationChat"; + + /** + * The config key for display the header more compact setting + * + * @var string + */ + private $_customizationCompactHeader = "customizationCompactHeader"; + + /** + * The config key for the feedback display setting + * + * @var string + */ + private $_customizationFeedback = "customizationFeedback"; + + /** + * The config key for the forcesave setting + * + * @var string + */ + private $_customizationForcesave = "customizationForcesave"; + + /** + * The config key for the help display setting + * + * @var string + */ + private $_customizationHelp = "customizationHelp"; + + /** + * The config key for the no tabs setting + * + * @var string + */ + private $_customizationToolbarNoTabs = "customizationToolbarNoTabs"; + + /** + * The config key for the review mode setting + * + * @var string + */ + private $_customizationReviewDisplay = "customizationReviewDisplay"; + + /** + * The config key for the theme setting + * + * @var string + */ + private $_customizationTheme = "customizationTheme"; + + /** + * The config key for the setting limit groups + * + * @var string + */ + private $_groups = "groups"; + + /** + * The config key for the verification + * + * @var string + */ + private $_verification = "verify_peer_off"; + + /** + * The config key for the secret key in jwt + * + * @var string + */ + private $_jwtSecret = "jwt_secret"; + + /** + * The config key for the jwt header + * + * @var string + */ + private $_jwtHeader = "jwt_header"; + + /** + * The config key for the allowable leeway in Jwt checks + * + * @var string + */ + private $_jwtLeeway = "jwt_leeway"; + + /** + * The config key for the settings error + * + * @var string + */ + private $_settingsError = "settings_error"; + + /** + * The config key for limit thumbnail size + * + * @var string + */ + public $_limitThumbSize = "limit_thumb_size"; + + /** + * The config key for the customer + * + * @var string + */ + public $_customization_customer = "customization_customer"; + + /** + * The config key for the loaderLogo + * + * @var string + */ + public $_customization_loaderLogo = "customization_loaderLogo"; + + /** + * The config key for the loaderName + * + * @var string + */ + public $_customization_loaderName = "customization_loaderName"; + + /** + * The config key for the logo + * + * @var string + */ + public $_customization_logo = "customization_logo"; + + /** + * The config key for the zoom + * + * @var string + */ + public $_customization_zoom = "customization_zoom"; + + /** + * The config key for the autosave + * + * @var string + */ + public $_customization_autosave = "customization_autosave"; + + /** + * The config key for the goback + * + * @var string + */ + public $_customization_goback = "customization_goback"; + + /** + * The config key for the macros + * + * @var string + */ + public $_customization_macros = "customization_macros"; + + /** + * The config key for the plugins + * + * @var string + */ + public $_customizationPlugins = "customization_plugins"; + + /** + * The config key for the interval of editors availability check by cron + * + * @var string + */ + private $_editors_check_interval = "editors_check_interval"; + + /** + * @param string $AppName - application name + */ + public function __construct($AppName) { + $this->appName = $AppName; + + $this->config = \OC::$server->getConfig(); + $this->logger = \OC::$server->getLogger(); + } + + /** + * Get value from the system configuration + * + * @param string $key - key configuration + * @param bool $system - get from root or from app section + * + * @return string + */ + public function GetSystemValue($key, $system = false) { + if ($system) { + return $this->config->getSystemValue($key); + } + if (!empty($this->config->getSystemValue($this->appName)) + && \array_key_exists($key, $this->config->getSystemValue($this->appName))) { + return $this->config->getSystemValue($this->appName)[$key]; + } + return null; + } + + /** + * Switch on demo server + * + * @param bool $value - select demo + * + * @return bool + */ + public function SelectDemo($value) { + $this->logger->info("Select demo: " . json_encode($value), ["app" => $this->appName]); + + $data = $this->GetDemoData(); + + if ($value === true && !$data["available"]) { + $this->logger->info("Trial demo is overdue: " . json_encode($data), ["app" => $this->appName]); + return false; + } + + $data["enabled"] = $value === true; + if (!isset($data["start"])) { + $data["start"] = new DateTime(); + } + + $this->config->setAppValue($this->appName, $this->_demo, json_encode($data)); + return true; + } + + /** + * Get demo data + * + * @return array + */ + public function GetDemoData() { + $data = $this->config->getAppValue($this->appName, $this->_demo, ""); + + if (empty($data)) { + return [ + "available" => true, + "enabled" => false + ]; + } + $data = json_decode($data, true); + + $overdue = new DateTime(isset($data["start"]) ? $data["start"]["date"] : null); + $overdue->add(new DateInterval("P" . $this->DEMO_PARAM["TRIAL"] . "D")); + if ($overdue > new DateTime()) { + $data["available"] = true; + $data["enabled"] = $data["enabled"] === true; + } else { + $data["available"] = false; + $data["enabled"] = false; + } + + return $data; + } + + /** + * Get status of demo server + * + * @return bool + */ + public function UseDemo() { + return $this->GetDemoData()["enabled"] === true; + } + + /** + * Save the document service address to the application configuration + * + * @param string $documentServer - document service address + */ + public function SetDocumentServerUrl($documentServer) { + $documentServer = trim($documentServer); + if (\strlen($documentServer) > 0) { + $documentServer = rtrim($documentServer, "/") . "/"; + if (!preg_match("/(^https?:\/\/)|^\//i", $documentServer)) { + $documentServer = "http://" . $documentServer; + } + } + + $this->logger->info("SetDocumentServerUrl: $documentServer", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_documentserver, $documentServer); + } + + /** + * Get the document service address from the application configuration + * + * @param bool $origin - take origin + * + * @return string + */ + public function GetDocumentServerUrl($origin = false) { + if (!$origin && $this->UseDemo()) { + return $this->DEMO_PARAM["ADDR"]; + } + + $url = $this->config->getAppValue($this->appName, $this->_documentserver, ""); + if (empty($url)) { + $url = $this->GetSystemValue($this->_documentserver); + } + if ($url !== "/") { + $url = rtrim($url, "/"); + if (\strlen($url) > 0) { + $url = $url . "/"; + } + } + return $url; + } + + /** + * Save the document service address available from ownCloud to the application configuration + * + * @param string $documentServerInternal - document service address + */ + public function SetDocumentServerInternalUrl($documentServerInternal) { + $documentServerInternal = rtrim(trim($documentServerInternal), "/"); + if (\strlen($documentServerInternal) > 0) { + $documentServerInternal = $documentServerInternal . "/"; + if (!preg_match("/^https?:\/\//i", $documentServerInternal)) { + $documentServerInternal = "http://" . $documentServerInternal; + } + } + + $this->logger->info("SetDocumentServerInternalUrl: $documentServerInternal", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_documentserverInternal, $documentServerInternal); + } + + /** + * Get the document service address available from ownCloud from the application configuration + * + * @param bool $origin - take origin + * + * @return string + */ + public function GetDocumentServerInternalUrl($origin = false) { + if (!$origin && $this->UseDemo()) { + return $this->GetDocumentServerUrl(); + } + + $url = $this->config->getAppValue($this->appName, $this->_documentserverInternal, ""); + if (empty($url)) { + $url = $this->GetSystemValue($this->_documentserverInternal); + } + if (!$origin && empty($url)) { + $url = $this->GetDocumentServerUrl(); + } + return $url; + } + + /** + * Replace domain in document server url with internal address from configuration + * + * @param string $url - document server url + * + * @return string + */ + public function ReplaceDocumentServerUrlToInternal($url) { + $documentServerUrl = $this->GetDocumentServerInternalUrl(); + if (!empty($documentServerUrl)) { + $from = $this->GetDocumentServerUrl(); + + if (!preg_match("/^https?:\/\//i", $from)) { + $parsedUrl = parse_url($url); + $from = $parsedUrl["scheme"] . "://" . $parsedUrl["host"] . (\array_key_exists("port", $parsedUrl) ? (":" . $parsedUrl["port"]) : "") . $from; + } + + if ($from !== $documentServerUrl) { + $this->logger->debug("Replace url from $from to $documentServerUrl", ["app" => $this->appName]); + $url = str_replace($from, $documentServerUrl, $url); + } + } + + return $url; + } + + /** + * Save the ownCloud address available from document server to the application configuration + * + * @param string $documentServer - document service address + */ + public function SetStorageUrl($storageUrl) { + $storageUrl = rtrim(trim($storageUrl), "/"); + if (\strlen($storageUrl) > 0) { + $storageUrl = $storageUrl . "/"; + if (!preg_match("/^https?:\/\//i", $storageUrl)) { + $storageUrl = "http://" . $storageUrl; + } + } + + $this->logger->info("SetStorageUrl: $storageUrl", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_storageUrl, $storageUrl); + } + + /** + * Get the ownCloud address available from document server from the application configuration + * + * @return string + */ + public function GetStorageUrl() { + $url = $this->config->getAppValue($this->appName, $this->_storageUrl, ""); + if (empty($url)) { + $url = $this->GetSystemValue($this->_storageUrl); + } + return $url; + } + + /** + * Save the document service secret key to the application configuration + * + * @param string $secret - secret key + */ + public function SetDocumentServerSecret($secret) { + $secret = trim($secret); + if (empty($secret)) { + $this->logger->info("Clear secret key", ["app" => $this->appName]); + } else { + $this->logger->info("Set secret key", ["app" => $this->appName]); + } + + $this->config->setAppValue($this->appName, $this->_jwtSecret, $secret); + } + + /** + * Get the document service secret key from the application configuration + * + * @param bool $origin - take origin + * + * @return string + */ + public function GetDocumentServerSecret($origin = false) { + if (!$origin && $this->UseDemo()) { + return $this->DEMO_PARAM["SECRET"]; + } + + $secret = $this->config->getAppValue($this->appName, $this->_jwtSecret, ""); + if (empty($secret)) { + $secret = $this->GetSystemValue($this->_jwtSecret); + } + return $secret; + } + + /** + * Get the secret key from the application configuration + * + * @return string + */ + public function GetSKey() { + $secret = $this->GetDocumentServerSecret(); + if (empty($secret)) { + $secret = $this->GetSystemValue($this->_cryptSecret, true); + } + return $secret; + } + + /** + * Save an array of formats with default action + * + * @param array $formats - formats with status + */ + public function SetDefaultFormats($formats) { + $value = json_encode($formats); + $this->logger->info("Set default formats: $value", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_defFormats, $value); + } + + /** + * Get an array of formats with default action + * + * @return array + */ + private function GetDefaultFormats() { + $value = $this->config->getAppValue($this->appName, $this->_defFormats, ""); + if (empty($value)) { + return []; + } + return json_decode($value, true); + } + + /** + * Save an array of formats that is opened for editing + * + * @param array $formats - formats with status + */ + public function SetEditableFormats($formats) { + $value = json_encode($formats); + $this->logger->info("Set editing formats: $value", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_editFormats, $value); + } + + /** + * Get an array of formats opening for editing + * + * @return array + */ + private function GetEditableFormats() { + $value = $this->config->getAppValue($this->appName, $this->_editFormats, ""); + if (empty($value)) { + return []; + } + return json_decode($value, true); + } + + /** + * Save the opening setting in a same tab + * + * @param bool $value - same tab + */ + public function SetSameTab($value) { + $this->logger->info("Set opening in a same tab: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_sameTab, json_encode($value)); + } + + /** + * Get the opening setting in a same tab + * + * @return bool + */ + public function GetSameTab() { + return $this->config->getAppValue($this->appName, $this->_sameTab, "false") === "true"; + } + + /** + * Save generate preview setting + * + * @param bool $value - preview + */ + public function SetPreview($value) { + $this->logger->info("Set generate preview: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_preview, json_encode($value)); + } + + /** + * Get generate preview setting + * + * @return bool + */ + public function GetPreview() { + return $this->config->getAppValue($this->appName, $this->_preview, "true") === "true"; + } + + /** + * Save keep versions history + * + * @param bool $value - version history + */ + public function SetVersionHistory($value) { + $this->logger->info("Set keep versions history: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_versionHistory, json_encode($value)); + } + + /** + * Get keep versions history + * + * @return bool + */ + public function GetVersionHistory() { + return $this->config->getAppValue($this->appName, $this->_versionHistory, "true") === "true"; + } + + /** + * Save protection + * + * @param bool $value - version history + */ + public function SetProtection($value) { + $this->logger->info("Set protection: " . $value, ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_protection, $value); + } + + /** + * Get protection + * + * @return bool + */ + public function GetProtection() { + $value = $this->config->getAppValue($this->appName, $this->_protection, "owner"); + if ($value === "all") { + return "all"; + } + return "owner"; + } + + /** + * Save chat display setting + * + * @param bool $value - display chat + */ + public function SetCustomizationChat($value) { + $this->logger->info("Set chat display: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationChat, json_encode($value)); + } + + /** + * Get chat display setting + * + * @return bool + */ + public function GetCustomizationChat() { + return $this->config->getAppValue($this->appName, $this->_customizationChat, "true") === "true"; + } + + /** + * Save compact header setting + * + * @param bool $value - display compact header + */ + public function SetCustomizationCompactHeader($value) { + $this->logger->info("Set compact header display: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationCompactHeader, json_encode($value)); + } + + /** + * Get compact header setting + * + * @return bool + */ + public function GetCustomizationCompactHeader() { + return $this->config->getAppValue($this->appName, $this->_customizationCompactHeader, "true") === "true"; + } + + /** + * Save feedback display setting + * + * @param bool $value - display feedback + */ + public function SetCustomizationFeedback($value) { + $this->logger->info("Set feedback display: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationFeedback, json_encode($value)); + } + + /** + * Get feedback display setting + * + * @return bool + */ + public function GetCustomizationFeedback() { + return $this->config->getAppValue($this->appName, $this->_customizationFeedback, "true") === "true"; + } + + /** + * Save forcesave setting + * + * @param bool $value - forcesave + */ + public function SetCustomizationForcesave($value) { + $this->logger->info("Set forcesave: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationForcesave, json_encode($value)); + } + + /** + * Get forcesave setting + * + * @return bool + */ + public function GetCustomizationForcesave() { + $value = $this->config->getAppValue($this->appName, $this->_customizationForcesave, "false") === "true"; + + return $value && ($this->checkEncryptionModule() === false); + } + + /** + * Save help display setting + * + * @param bool $value - display help + */ + public function SetCustomizationHelp($value) { + $this->logger->info("Set help display: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationHelp, json_encode($value)); + } + + /** + * Get help display setting + * + * @return bool + */ + public function GetCustomizationHelp() { + return $this->config->getAppValue($this->appName, $this->_customizationHelp, "true") === "true"; + } + + /** + * Save without tabs setting + * + * @param bool $value - without tabs + */ + public function SetCustomizationToolbarNoTabs($value) { + $this->logger->info("Set without tabs: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationToolbarNoTabs, json_encode($value)); + } + + /** + * Get without tabs setting + * + * @return bool + */ + public function GetCustomizationToolbarNoTabs() { + return $this->config->getAppValue($this->appName, $this->_customizationToolbarNoTabs, "true") === "true"; + } + + /** + * Save review viewing mode setting + * + * @param string $value - review mode + */ + public function SetCustomizationReviewDisplay($value) { + $this->logger->info("Set review mode: " . $value, ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationReviewDisplay, $value); + } + + /** + * Get review viewing mode setting + * + * @return string + */ + public function GetCustomizationReviewDisplay() { + $value = $this->config->getAppValue($this->appName, $this->_customizationReviewDisplay, "original"); + if ($value === "markup") { + return "markup"; + } + if ($value === "final") { + return "final"; + } + return "original"; + } + + /** + * Save theme setting + * + * @param string $value - theme + */ + public function SetCustomizationTheme($value) { + $this->logger->info("Set theme: " . $value, ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationTheme, $value); + } + + /** + * Get theme setting + * + * @return string + */ + public function GetCustomizationTheme() { + $value = $this->config->getAppValue($this->appName, $this->_customizationTheme, "theme-classic-light"); + if ($value === "theme-light") { + return "theme-light"; + } + if ($value === "theme-dark") { + return "theme-dark"; + } + return "theme-classic-light"; + } + + /** + * Save macros setting + * + * @param bool $value - enable macros + */ + public function SetCustomizationMacros($value) { + $this->logger->info("Set macros enabled: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customization_macros, json_encode($value)); + } + + /** + * Get macros setting + * + * @return bool + */ + public function GetCustomizationMacros() { + return $this->config->getAppValue($this->appName, $this->_customization_macros, "true") === "true"; + } + + /** + * Save plugins setting + * + * @param bool $value - enable macros + */ + public function SetCustomizationPlugins($value) { + $this->logger->info("Set plugins enabled: " . json_encode($value), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_customizationPlugins, json_encode($value)); + } + + /** + * Get plugins setting + * + * @return bool + */ + public function GetCustomizationPlugins() { + return $this->config->getAppValue($this->appName, $this->_customizationPlugins, "true") === "true"; + } + + /** + * Save the list of groups + * + * @param array $groups - the list of groups + */ + public function SetLimitGroups($groups) { + if (!\is_array($groups)) { + $groups = []; + } + $value = json_encode($groups); + $this->logger->info("Set groups: $value", ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_groups, $value); + } + + /** + * Get the list of groups + * + * @return array + */ + public function GetLimitGroups() { + $value = $this->config->getAppValue($this->appName, $this->_groups, ""); + if (empty($value)) { + return []; + } + $groups = json_decode($value, true); + if (!\is_array($groups)) { + $groups = []; + } + return $groups; + } + + /** + * Check access for group + * + * @param string $userId - user identifier + * + * @return bool + */ + public function isUserAllowedToUse($userId = null) { + // no user -> no + $userSession = \OC::$server->getUserSession(); + if ($userId === null && ($userSession === null || !$userSession->isLoggedIn())) { + return false; + } + + $groups = $this->GetLimitGroups(); + // no group set -> all users are allowed + if (\count($groups) === 0) { + return true; + } + + if ($userId === null) { + $user = $userSession->getUser(); + } else { + $user = \OC::$server->getUserManager()->get($userId); + if (empty($user)) { + return false; + } + } + + foreach ($groups as $groupName) { + // group unknown -> error and allow nobody + $group = \OC::$server->getGroupManager()->get($groupName); + if ($group === null) { + \OC::$server->getLogger()->error("Group is unknown $groupName", ["app" => $this->appName]); + $this->SetLimitGroups(array_diff($groups, [$groupName])); + } else { + if ($group->inGroup($user)) { + return true; + } + } + } + + return false; + } + + /** + * Save the document service verification setting to the application configuration + * + * @param bool $verifyPeerOff - parameter verification setting + */ + public function SetVerifyPeerOff($verifyPeerOff) { + $this->logger->info("SetVerifyPeerOff " . json_encode($verifyPeerOff), ["app" => $this->appName]); + + $this->config->setAppValue($this->appName, $this->_verification, json_encode($verifyPeerOff)); + } + + /** + * Get the document service verification setting to the application configuration + * + * @return bool + */ + public function GetVerifyPeerOff() { + $turnOff = $this->config->getAppValue($this->appName, $this->_verification, ""); + + if (!empty($turnOff)) { + return $turnOff === "true"; + } + + return $this->GetSystemValue($this->_verification); + } + + /** + * Get the limit on size document when generating thumbnails + * + * @return int + */ + public function GetLimitThumbSize() { + $limitSize = (integer)$this->GetSystemValue($this->_limitThumbSize); + + if (!empty($limitSize)) { + return $limitSize; + } + + return 100*1024*1024; + } + + /** + * Get the jwt header setting + * + * @param bool $origin - take origin + * + * @return string + */ + public function JwtHeader($origin = false) { + if (!$origin && $this->UseDemo()) { + return $this->DEMO_PARAM["HEADER"]; + } + + $header = $this->config->getAppValue($this->appName, $this->_jwtHeader, ""); + if (empty($header)) { + $header = $this->GetSystemValue($this->_jwtHeader); + } + if (!$origin && empty($header)) { + $header = "Authorization"; + } + return $header; + } + + /** + * Save the jwtHeader setting + * + * @param string $value - jwtHeader + */ + public function SetJwtHeader($value) { + $value = trim($value); + if (empty($value)) { + $this->logger->info("Clear header key", ["app" => $this->appName]); + } else { + $this->logger->info("Set header key " . $value, ["app" => $this->appName]); + } + + $this->config->setAppValue($this->appName, $this->_jwtHeader, $value); + } + + /** + * Get the Jwt Leeway + * + * @return int + */ + public function GetJwtLeeway() { + $jwtLeeway = (integer)$this->GetSystemValue($this->_jwtLeeway); + + return $jwtLeeway; + } + + /** + * Save the status settings + * + * @param string $value - error + */ + public function SetSettingsError($value) { + $this->config->setAppValue($this->appName, $this->_settingsError, $value); + } + + /** + * Get the status settings + * + * @return bool + */ + public function SettingsAreSuccessful() { + return empty($this->config->getAppValue($this->appName, $this->_settingsError, "")); + } + + /** + * Checking encryption enabled + * + * @return string|bool + */ + public function checkEncryptionModule() { + if (!\OC::$server->getAppManager()->isInstalled("encryption")) { + return false; + } + if (!\OC::$server->getEncryptionManager()->isEnabled()) { + return false; + } + + $crypt = new \OCA\Encryption\Crypto\Crypt(\OC::$server->getLogger(), \OC::$server->getUserSession(), \OC::$server->getConfig(), \OC::$server->getL10N("encryption")); + $util = new \OCA\Encryption\Util(new \OC\Files\View(), $crypt, \OC::$server->getLogger(), \OC::$server->getUserSession(), \OC::$server->getConfig(), \OC::$server->getUserManager()); + if ($util->isMasterKeyEnabled()) { + return "master"; + } + + return true; + } + + /** + * Get supported formats + * + * @return array + * + * @NoAdminRequired + */ + public function FormatsSetting() { + $result = $this->formats; + + $defFormats = $this->GetDefaultFormats(); + foreach ($defFormats as $format => $setting) { + if (\array_key_exists($format, $result)) { + $result[$format]["def"] = ($setting === true || $setting === "true"); + } + } + + $editFormats = $this->GetEditableFormats(); + foreach ($editFormats as $format => $setting) { + if (\array_key_exists($format, $result)) { + $result[$format]["edit"] = ($setting === true || $setting === "true"); + } + } + + return $result; + } + + public function ShareAttributesVersion() { + if (\version_compare(\implode(".", \OCP\Util::getVersion()), "10.3.0", ">=")) { + return "v2"; + } elseif (\version_compare(\implode(".", \OCP\Util::getVersion()), "10.2.0", ">=")) { + return "v1"; + } + return ""; + } + + /** + * Get the editors check interval + * + * @return int + */ + public function GetEditorsCheckInterval() { + $interval = $this->GetSystemValue($this->_editors_check_interval); + + if (empty($interval) && $interval !== 0) { + $interval = 60*60*24; + } + return (integer)$interval; + } + + /** + * Additional data about formats + * + * @var array + */ + private $formats = [ + "csv" => [ "mime" => "text/csv", "type" => "cell", "edit" => true, "editable" => true, "saveas" => ["ods", "pdf", "xlsx"] ], + "doc" => [ "mime" => "application/msword", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "docm" => [ "mime" => "application/vnd.ms-word.document.macroEnabled.12", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "docx" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "type" => "word", "edit" => true, "def" => true, "review" => true, "comment" => true, "saveas" => ["odt", "pdf", "rtf", "txt"] ], + "docxf" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf", "type" => "word", "edit" => true, "def" => true, "review" => true, "comment" => true, "saveas" => ["odt", "pdf", "rtf", "txt"], "createForm" => true ], + "oform" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform", "type" => "word", "fillForms" => true, "def" => true ], + "dot" => [ "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "dotx" => [ "mime" => "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "epub" => [ "mime" => "application/epub+zip", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "htm" => [ "type" => "word", "conv" => true ], + "html" => [ "mime" => "text/html", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "odp" => [ "mime" => "application/vnd.oasis.opendocument.presentation", "type" => "slide", "conv" => true, "editable" => true, "saveas" => ["pdf", "pptx"] ], + "ods" => [ "mime" => "application/vnd.oasis.opendocument.spreadsheet", "type" => "cell", "conv" => true, "editable" => true, "saveas" => ["csv", "pdf", "xlsx"] ], + "odt" => [ "mime" => "application/vnd.oasis.opendocument.text", "type" => "word", "conv" => true, "editable" => true, "saveas" => ["docx", "pdf", "rtf", "txt"] ], + "otp" => [ "mime" => "application/vnd.oasis.opendocument.presentation-template", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "ots" => [ "mime" => "application/vnd.oasis.opendocument.spreadsheet-template", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], + "ott" => [ "mime" => "application/vnd.oasis.opendocument.text-template", "type" => "word", "conv" => true, "saveas" => ["docx", "odt", "pdf", "rtf", "txt"] ], + "pdf" => [ "mime" => "application/pdf", "type" => "word" ], + "pot" => [ "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "potm" => [ "mime" => "application/vnd.ms-powerpoint.template.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "potx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.template", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "pps" => [ "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "ppsm" => [ "mime" => "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "ppsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.slideshow", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "ppt" => [ "mime" => "application/vnd.ms-powerpoint", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "pptm" => [ "mime" => "application/vnd.ms-powerpoint.presentation.macroEnabled.12", "type" => "slide", "conv" => true, "saveas" => ["pdf", "pptx", "odp"] ], + "pptx" => [ "mime" => "application/vnd.openxmlformats-officedocument.presentationml.presentation", "type" => "slide", "edit" => true, "def" => true, "comment" => true, "saveas" => ["pdf", "odp"] ], + "rtf" => [ "mime" => "text/rtf", "type" => "word", "conv" => true, "editable" => true, "saveas" => ["docx", "odt", "pdf", "txt"] ], + "txt" => [ "mime" => "text/plain", "type" => "word", "edit" => true, "editable" => true, "saveas" => ["docx", "odt", "pdf", "rtf"] ], + "xls" => [ "mime" => "application/vnd.ms-excel", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], + "xlsm" => [ "mime" => "application/vnd.ms-excel.sheet.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], + "xlsx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "type" => "cell", "edit" => true, "def" => true, "comment" => true, "modifyFilter" => true, "saveas" => ["csv", "ods", "pdf"] ], + "xlt" => [ "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], + "xltm" => [ "mime" => "application/vnd.ms-excel.template.macroEnabled.12", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ], + "xltx" => [ "mime" => "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "type" => "cell", "conv" => true, "saveas" => ["csv", "ods", "pdf", "xlsx"] ] + ]; + + /** + * DEMO DATA + */ + private $DEMO_PARAM = [ + "ADDR" => "https://onlinedocs.onlyoffice.com/", + "HEADER" => "AuthorizationJWT", + "SECRET" => "sn2puSUF7muF5Jas", + "TRIAL" => 30 + ]; + + private $linkToDocs = "https://www.onlyoffice.com/docs-registration.aspx?referer=owncloud"; + + /** + * Get link to Docs Cloud + * + * @return string + */ + public function GetLinkToDocs() { + return $this->linkToDocs; + } } diff --git a/lib/command/documentserver.php b/lib/command/documentserver.php index 33c589f2..adda3514 100644 --- a/lib/command/documentserver.php +++ b/lib/command/documentserver.php @@ -32,101 +32,101 @@ use OCA\Onlyoffice\Crypt; class DocumentServer extends Command { - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * @param AppConfig $config - application configuration - * @param IL10N $trans - l10n service - * @param IURLGenerator $urlGenerator - url generator service - * @param Crypt $crypt - hash generator - */ - public function __construct( - AppConfig $config, - IL10N $trans, - IURLGenerator $urlGenerator, - Crypt $crypt - ) { - parent::__construct(); - $this->config = $config; - $this->trans = $trans; - $this->urlGenerator = $urlGenerator; - $this->crypt = $crypt; - } - - /** - * Configures the current command. - */ - protected function configure() { - $this - ->setName("onlyoffice:documentserver") - ->setDescription("Manage document server") - ->addOption( - "check", - null, - InputOption::VALUE_NONE, - "Check connection document server" - ); - } - - /** - * Executes the current command. - * - * @param InputInterface $input - input data - * @param OutputInterface $output - output data - * - * @return int 0 if everything went fine, or an exit code - */ - protected function execute(InputInterface $input, OutputInterface $output) { - $check = $input->getOption("check"); - - $documentserver = $this->config->GetDocumentServerUrl(true); - if (empty($documentserver)) { - $output->writeln("Document server is not configured"); - return 1; - } - - if ($check) { - $documentService = new DocumentService($this->trans, $this->config); - - list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); - $this->config->SetSettingsError($error); - - if (!empty($error)) { - $output->writeln("Error connection: $error"); - return 1; - } else { - $output->writeln("Document server $documentserver version $version is successfully connected"); - return 0; - } - } - - $output->writeln("The current document server: $documentserver"); - return 0; - } + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * @param AppConfig $config - application configuration + * @param IL10N $trans - l10n service + * @param IURLGenerator $urlGenerator - url generator service + * @param Crypt $crypt - hash generator + */ + public function __construct( + AppConfig $config, + IL10N $trans, + IURLGenerator $urlGenerator, + Crypt $crypt + ) { + parent::__construct(); + $this->config = $config; + $this->trans = $trans; + $this->urlGenerator = $urlGenerator; + $this->crypt = $crypt; + } + + /** + * Configures the current command. + */ + protected function configure() { + $this + ->setName("onlyoffice:documentserver") + ->setDescription("Manage document server") + ->addOption( + "check", + null, + InputOption::VALUE_NONE, + "Check connection document server" + ); + } + + /** + * Executes the current command. + * + * @param InputInterface $input - input data + * @param OutputInterface $output - output data + * + * @return int 0 if everything went fine, or an exit code + */ + protected function execute(InputInterface $input, OutputInterface $output) { + $check = $input->getOption("check"); + + $documentserver = $this->config->GetDocumentServerUrl(true); + if (empty($documentserver)) { + $output->writeln("Document server is not configured"); + return 1; + } + + if ($check) { + $documentService = new DocumentService($this->trans, $this->config); + + list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); + $this->config->SetSettingsError($error); + + if (!empty($error)) { + $output->writeln("Error connection: $error"); + return 1; + } else { + $output->writeln("Document server $documentserver version $version is successfully connected"); + return 0; + } + } + + $output->writeln("The current document server: $documentserver"); + return 0; + } } diff --git a/lib/cron/editorscheck.php b/lib/cron/editorscheck.php index 69065c4f..e9aa5054 100644 --- a/lib/cron/editorscheck.php +++ b/lib/cron/editorscheck.php @@ -37,156 +37,156 @@ * */ class EditorsCheck extends TimedJob { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * Logger - * - * @var OCP\ILogger - */ - private $logger; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * Group manager - * - * @var IGroupManager - */ - private $groupManager; - - /** - * @param string $AppName - application name - * @param IURLGenerator $urlGenerator - url generator service - * @param ITimeFactory $time - time - * @param AppConfig $config - application configuration - * @param IL10N $trans - l10n service - * @param Crypt $crypt - crypt service - */ - public function __construct( - string $AppName, - IURLGenerator $urlGenerator, - ITimeFactory $time, - AppConfig $config, - IL10N $trans, - Crypt $crypt, - IGroupManager $groupManager - ) { - $this->appName = $AppName; - $this->urlGenerator = $urlGenerator; - - $this->logger = \OC::$server->getLogger(); - $this->config = $config; - $this->trans = $trans; - $this->crypt = $crypt; - $this->groupManager = $groupManager; - $this->setInterval($this->config->GetEditorsCheckInterval()); - } - - /** - * Makes the background check - * - * @param array $argument unused argument - */ - protected function run($argument) { - if (empty($this->config->GetDocumentServerUrl())) { - $this->logger->debug("Settings are empty", ["app" => $this->appName]); - return; - } - if (!$this->config->SettingsAreSuccessful()) { - $this->logger->debug("Settings are not correct", ["app" => $this->appName]); - return; - } - $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.emptyfile"); - if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { - $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); - } - $host = parse_url($fileUrl)["host"]; - if ($host === "localhost" || $host === "127.0.0.1") { - $this->logger->debug("Localhost is not alowed for cron editors availability check. Please provide server address for internal requests from ONLYOFFICE Docs", ["app" => $this->appName]); - return; - } - - $this->logger->debug("ONLYOFFICE check started by cron", ["app" => $this->appName]); - - $documentService = new DocumentService($this->trans, $this->config); - list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); - if (!empty($error)) { - $this->logger->info("ONLYOFFICE server is not available", ["app" => $this->appName]); - $this->config->SetSettingsError($error); - $this->notifyAdmins(); - } else { - $this->logger->debug("ONLYOFFICE server availability check is finished successfully", ["app" => $this->appName]); - } - } - - /** - * Get the list of users to notify - * - * @return string[] - */ - private function getUsersToNotify() { - $notifyGroups = ["admin"]; - $notifyUsers = []; - - foreach ($notifyGroups as $notifyGroup) { - $group = $this->groupManager->get($notifyGroup); - if ($group === null || !($group instanceof IGroup)) { - continue; - } - $users = $group->getUsers(); - foreach ($users as $user) { - $notifyUsers[] = $user->getUID(); - } - } - return $notifyUsers; - } - - /** - * Send notification to admins - * @return void - */ - private function notifyAdmins() { - $notificationManager = \OC::$server->getNotificationManager(); - $notification = $notificationManager->createNotification(); - $notification->setApp($this->appName) - ->setDateTime(new \DateTime()) - ->setObject("editorsCheck", $this->trans->t("ONLYOFFICE server is not available")) - ->setSubject("editorscheck_info"); - foreach ($this->getUsersToNotify() as $uid) { - $notification->setUser($uid); - $notificationManager->notify($notification); - } - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * Logger + * + * @var OCP\ILogger + */ + private $logger; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * Group manager + * + * @var IGroupManager + */ + private $groupManager; + + /** + * @param string $AppName - application name + * @param IURLGenerator $urlGenerator - url generator service + * @param ITimeFactory $time - time + * @param AppConfig $config - application configuration + * @param IL10N $trans - l10n service + * @param Crypt $crypt - crypt service + */ + public function __construct( + string $AppName, + IURLGenerator $urlGenerator, + ITimeFactory $time, + AppConfig $config, + IL10N $trans, + Crypt $crypt, + IGroupManager $groupManager + ) { + $this->appName = $AppName; + $this->urlGenerator = $urlGenerator; + + $this->logger = \OC::$server->getLogger(); + $this->config = $config; + $this->trans = $trans; + $this->crypt = $crypt; + $this->groupManager = $groupManager; + $this->setInterval($this->config->GetEditorsCheckInterval()); + } + + /** + * Makes the background check + * + * @param array $argument unused argument + */ + protected function run($argument) { + if (empty($this->config->GetDocumentServerUrl())) { + $this->logger->debug("Settings are empty", ["app" => $this->appName]); + return; + } + if (!$this->config->SettingsAreSuccessful()) { + $this->logger->debug("Settings are not correct", ["app" => $this->appName]); + return; + } + $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.emptyfile"); + if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { + $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); + } + $host = parse_url($fileUrl)["host"]; + if ($host === "localhost" || $host === "127.0.0.1") { + $this->logger->debug("Localhost is not alowed for cron editors availability check. Please provide server address for internal requests from ONLYOFFICE Docs", ["app" => $this->appName]); + return; + } + + $this->logger->debug("ONLYOFFICE check started by cron", ["app" => $this->appName]); + + $documentService = new DocumentService($this->trans, $this->config); + list($error, $version) = $documentService->checkDocServiceUrl($this->urlGenerator, $this->crypt); + if (!empty($error)) { + $this->logger->info("ONLYOFFICE server is not available", ["app" => $this->appName]); + $this->config->SetSettingsError($error); + $this->notifyAdmins(); + } else { + $this->logger->debug("ONLYOFFICE server availability check is finished successfully", ["app" => $this->appName]); + } + } + + /** + * Get the list of users to notify + * + * @return string[] + */ + private function getUsersToNotify() { + $notifyGroups = ["admin"]; + $notifyUsers = []; + + foreach ($notifyGroups as $notifyGroup) { + $group = $this->groupManager->get($notifyGroup); + if ($group === null || !($group instanceof IGroup)) { + continue; + } + $users = $group->getUsers(); + foreach ($users as $user) { + $notifyUsers[] = $user->getUID(); + } + } + return $notifyUsers; + } + + /** + * Send notification to admins + * @return void + */ + private function notifyAdmins() { + $notificationManager = \OC::$server->getNotificationManager(); + $notification = $notificationManager->createNotification(); + $notification->setApp($this->appName) + ->setDateTime(new \DateTime()) + ->setObject("editorsCheck", $this->trans->t("ONLYOFFICE server is not available")) + ->setSubject("editorscheck_info"); + foreach ($this->getUsersToNotify() as $uid) { + $notification->setUser($uid); + $notificationManager->notify($notification); + } + } } diff --git a/lib/crypt.php b/lib/crypt.php index 730b2932..692064c5 100644 --- a/lib/crypt.php +++ b/lib/crypt.php @@ -27,49 +27,49 @@ * @package OCA\Onlyoffice */ class Crypt { - /** - * Application configuration - * - * @var AppConfig - */ - private $config; + /** + * Application configuration + * + * @var AppConfig + */ + private $config; - /** - * @param AppConfig $config - application configutarion - */ - public function __construct(AppConfig $appConfig) { - $this->config = $appConfig; - } + /** + * @param AppConfig $config - application configutarion + */ + public function __construct(AppConfig $appConfig) { + $this->config = $appConfig; + } - /** - * Generate token for the object - * - * @param array $object - object to signature - * - * @return string - */ - public function GetHash($object) { - return \Firebase\JWT\JWT::encode($object, $this->config->GetSKey(), "HS256"); - } + /** + * Generate token for the object + * + * @param array $object - object to signature + * + * @return string + */ + public function GetHash($object) { + return \Firebase\JWT\JWT::encode($object, $this->config->GetSKey(), "HS256"); + } - /** - * Create an object from the token - * - * @param string $token - token - * - * @return array - */ - public function ReadHash($token) { - $result = null; - $error = null; - if ($token === null) { - return [$result, "token is empty"]; - } - try { - $result = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetSKey(), "HS256")); - } catch (\UnexpectedValueException $e) { - $error = $e->getMessage(); - } - return [$result, $error]; - } + /** + * Create an object from the token + * + * @param string $token - token + * + * @return array + */ + public function ReadHash($token) { + $result = null; + $error = null; + if ($token === null) { + return [$result, "token is empty"]; + } + try { + $result = \Firebase\JWT\JWT::decode($token, new \Firebase\JWT\Key($this->config->GetSKey(), "HS256")); + } catch (\UnexpectedValueException $e) { + $error = $e->getMessage(); + } + return [$result, $error]; + } } diff --git a/lib/documentservice.php b/lib/documentservice.php index 73828716..88e59f8c 100644 --- a/lib/documentservice.php +++ b/lib/documentservice.php @@ -29,414 +29,414 @@ * @package OCA\Onlyoffice */ class DocumentService { - /** - * Application name - * - * @var string - */ - private static $appName = "onlyoffice"; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * @param IL10N $trans - l10n service - * @param AppConfig $config - application configutarion - */ - public function __construct(IL10N $trans, AppConfig $appConfig) { - $this->trans = $trans; - $this->config = $appConfig; - } - - /** - * Translation key to a supported form. - * - * @param string $expected_key - Expected key - * - * @return string - */ - public static function GenerateRevisionId($expected_key) { - if (\strlen($expected_key) > 20) { - $expected_key = crc32($expected_key); - } - $key = preg_replace("[^0-9-.a-zA-Z_=]", "_", $expected_key); - $key = substr($key, 0, min([\strlen($key), 20])); - return $key; - } - - /** - * The method is to convert the file to the required format and return the result url - * - * @param string $document_uri - Uri for the document to convert - * @param string $from_extension - Document extension - * @param string $to_extension - Extension to which to convert - * @param string $document_revision_id - Key for caching on service - * - * @return string - */ - public function GetConvertedUri($document_uri, $from_extension, $to_extension, $document_revision_id) { - $responceFromConvertService = $this->SendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, false); - - $errorElement = $responceFromConvertService->Error; - if ($errorElement->count() > 0) { - $this->ProcessConvServResponceError($errorElement . ""); - } - - $isEndConvert = $responceFromConvertService->EndConvert; - - if ($isEndConvert !== null && strtolower($isEndConvert) === "true") { - return (string)$responceFromConvertService->FileUrl; - } - - return ""; - } - - /** - * Request for conversion to a service - * - * @param string $document_uri - Uri for the document to convert - * @param string $from_extension - Document extension - * @param string $to_extension - Extension to which to convert - * @param string $document_revision_id - Key for caching on service - * @param bool - $is_async - Perform conversions asynchronously - * - * @return array - */ - public function SendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, $is_async) { - $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); - - if (empty($documentServerUrl)) { - throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); - } - - $urlToConverter = $documentServerUrl . "ConvertService.ashx"; - - if (empty($document_revision_id)) { - $document_revision_id = $document_uri; - } - - $document_revision_id = self::GenerateRevisionId($document_revision_id); - - if (empty($from_extension)) { - $from_extension = pathinfo($document_uri)["extension"]; - } else { - $from_extension = trim($from_extension, "."); - } - - $data = [ - "async" => $is_async, - "url" => $document_uri, - "outputtype" => trim($to_extension, "."), - "filetype" => $from_extension, - "title" => $document_revision_id . "." . $from_extension, - "key" => $document_revision_id, - "region" => str_replace("_", "-", \OC::$server->getL10NFactory("")->get("")->getLanguageCode()) - ]; - - if ($this->config->UseDemo()) { - $data["tenant"] = $this->config->GetSystemValue("instanceid", true); - } - - $opts = [ - "timeout" => "120", - "headers" => [ - "Content-type" => "application/json" - ], - "body" => json_encode($data) - ]; - - if (!empty($this->config->GetDocumentServerSecret())) { - $params = [ - "payload" => $data - ]; - $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); - $opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token; - - $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256"); - $data["token"] = $token; - $opts["body"] = json_encode($data); - } - - $response_xml_data = $this->Request($urlToConverter, "post", $opts); - - libxml_use_internal_errors(true); - if (!\function_exists("simplexml_load_file")) { - throw new \Exception($this->trans->t("Server can't read xml")); - } - $response_data = simplexml_load_string($response_xml_data); - if (!$response_data) { - $exc = $this->trans->t("Bad Response. Errors: "); - foreach (libxml_get_errors() as $error) { - $exc = $exc . "\t" . $error->message; - } - throw new \Exception($exc); - } - - return $response_data; - } - - /** - * Generate an error code table of convertion - * - * @param string $errorCode - Error code - * - * @return null - */ - public function ProcessConvServResponceError($errorCode) { - $errorMessageTemplate = $this->trans->t("Error occurred in the document service"); - $errorMessage = ""; - - switch ($errorCode) { - case -20: - $errorMessage = $errorMessageTemplate . ": Error encrypt signature"; - break; - case -8: - $errorMessage = $errorMessageTemplate . ": Invalid token"; - break; - case -7: - $errorMessage = $errorMessageTemplate . ": Error document request"; - break; - case -6: - $errorMessage = $errorMessageTemplate . ": Error while accessing the conversion result database"; - break; - case -5: - $errorMessage = $errorMessageTemplate . ": Incorrect password"; - break; - case -4: - $errorMessage = $errorMessageTemplate . ": Error while downloading the document file to be converted."; - break; - case -3: - $errorMessage = $errorMessageTemplate . ": Conversion error"; - break; - case -2: - $errorMessage = $errorMessageTemplate . ": Timeout conversion error"; - break; - case -1: - $errorMessage = $errorMessageTemplate . ": Unknown error"; - break; - case 0: - break; - default: - $errorMessage = $errorMessageTemplate . ": ErrorCode = " . $errorCode; - break; - } - - throw new \Exception($errorMessage); - } - - /** - * Request health status - * - * @return bool - */ - public function HealthcheckRequest() { - $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); - - if (empty($documentServerUrl)) { - throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); - } - - $urlHealthcheck = $documentServerUrl . "healthcheck"; - - $response = $this->Request($urlHealthcheck); - - return $response === "true"; - } - - /** - * Send command - * - * @param string $method - type of command - * - * @return array - */ - public function CommandRequest($method) { - $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); - - if (empty($documentServerUrl)) { - throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); - } - - $urlCommand = $documentServerUrl . "coauthoring/CommandService.ashx"; - - $data = [ - "c" => $method - ]; - - $opts = [ - "headers" => [ - "Content-type" => "application/json" - ], - "body" => json_encode($data) - ]; - - if (!empty($this->config->GetDocumentServerSecret())) { - $params = [ - "payload" => $data - ]; - $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); - $opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token; - - $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256"); - $data["token"] = $token; - $opts["body"] = json_encode($data); - } - - $response = $this->Request($urlCommand, "post", $opts); - - $data = json_decode($response); - - $this->ProcessCommandServResponceError($data->error); - - return $data; - } - - /** - * Generate an error code table of command - * - * @param string $errorCode - Error code - * - * @return null - */ - public function ProcessCommandServResponceError($errorCode) { - $errorMessageTemplate = $this->trans->t("Error occurred in the document service"); - $errorMessage = ""; - - switch ($errorCode) { - case 6: - $errorMessage = $errorMessageTemplate . ": Invalid token"; - break; - case 5: - $errorMessage = $errorMessageTemplate . ": Command not correсt"; - break; - case 3: - $errorMessage = $errorMessageTemplate . ": Internal server error"; - break; - case 0: - return; - default: - $errorMessage = $errorMessageTemplate . ": ErrorCode = " . $errorCode; - break; - } - - throw new \Exception($errorMessage); - } - - /** - * Request to Document Server with turn off verification - * - * @param string $url - request address - * @param array $method - request method - * @param array $opts - request options - * - * @return string - */ - public function Request($url, $method = "get", $opts = null) { - $httpClientService = \OC::$server->getHTTPClientService(); - $client = $httpClientService->newClient(); - - if ($opts === null) { - $opts = []; - } - if (substr($url, 0, \strlen("https")) === "https" && $this->config->GetVerifyPeerOff()) { - $opts["verify"] = false; - } - if (!\array_key_exists("timeout", $opts)) { - $opts["timeout"] = 60; - } - - if ($method === "post") { - $response = $client->post($url, $opts); - } else { - $response = $client->get($url, $opts); - } - - return $response->getBody(); - } - - /** - * Checking document service location - * - * @param OCP\IURLGenerator $urlGenerator - url generator - * @param OCA\Onlyoffice\Crypt $crypt -crypt - * - * @return array - */ - public function checkDocServiceUrl($urlGenerator, $crypt) { - $logger = \OC::$server->getLogger(); - $version = null; - - try { - if (preg_match("/^https:\/\//i", $urlGenerator->getAbsoluteURL("/")) - && preg_match("/^http:\/\//i", $this->config->GetDocumentServerUrl())) { - throw new \Exception($this->trans->t("Mixed Active Content is not allowed. HTTPS address for ONLYOFFICE Docs is required.")); - } - } catch (\Exception $e) { - $logger->logException($e, ["message" => "Protocol on check error", "app" => self::$appName]); - return [$e->getMessage(), $version]; - } - - try { - $healthcheckResponse = $this->HealthcheckRequest(); - if (!$healthcheckResponse) { - throw new \Exception($this->trans->t("Bad healthcheck status")); - } - } catch (\Exception $e) { - $logger->logException($e, ["message" => "HealthcheckRequest on check error", "app" => self::$appName]); - return [$e->getMessage(), $version]; - } - - try { - $commandResponse = $this->CommandRequest("version"); - - $logger->debug("CommandRequest on check: " . json_encode($commandResponse), ["app" => self::$appName]); - - if (empty($commandResponse)) { - throw new \Exception($this->trans->t("Error occurred in the document service")); - } - - $version = $commandResponse->version; - $versionF = \floatval($version); - if ($versionF > 0.0 && $versionF <= 6.0) { - throw new \Exception($this->trans->t("Not supported version")); - } - } catch (\Exception $e) { - $logger->logException($e, ["message" => "CommandRequest on check error", "app" => self::$appName]); - return [$e->getMessage(), $version]; - } - - $convertedFileUri = null; - try { - $hashUrl = $crypt->GetHash(["action" => "empty"]); - $fileUrl = $urlGenerator->linkToRouteAbsolute(self::$appName . ".callback.emptyfile", ["doc" => $hashUrl]); - if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { - $fileUrl = str_replace($urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); - } - - $convertedFileUri = $this->GetConvertedUri($fileUrl, "docx", "docx", "check_" . rand()); - } catch (\Exception $e) { - $logger->logException($e, ["message" => "GetConvertedUri on check error", "app" => self::$appName]); - return [$e->getMessage(), $version]; - } - - try { - $this->Request($convertedFileUri); - } catch (\Exception $e) { - $logger->logException($e, ["message" => "Request converted file on check error", "app" => self::$appName]); - return [$e->getMessage(), $version]; - } - - return ["", $version]; - } + /** + * Application name + * + * @var string + */ + private static $appName = "onlyoffice"; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * @param IL10N $trans - l10n service + * @param AppConfig $config - application configutarion + */ + public function __construct(IL10N $trans, AppConfig $appConfig) { + $this->trans = $trans; + $this->config = $appConfig; + } + + /** + * Translation key to a supported form. + * + * @param string $expected_key - Expected key + * + * @return string + */ + public static function GenerateRevisionId($expected_key) { + if (\strlen($expected_key) > 20) { + $expected_key = crc32($expected_key); + } + $key = preg_replace("[^0-9-.a-zA-Z_=]", "_", $expected_key); + $key = substr($key, 0, min([\strlen($key), 20])); + return $key; + } + + /** + * The method is to convert the file to the required format and return the result url + * + * @param string $document_uri - Uri for the document to convert + * @param string $from_extension - Document extension + * @param string $to_extension - Extension to which to convert + * @param string $document_revision_id - Key for caching on service + * + * @return string + */ + public function GetConvertedUri($document_uri, $from_extension, $to_extension, $document_revision_id) { + $responceFromConvertService = $this->SendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, false); + + $errorElement = $responceFromConvertService->Error; + if ($errorElement->count() > 0) { + $this->ProcessConvServResponceError($errorElement . ""); + } + + $isEndConvert = $responceFromConvertService->EndConvert; + + if ($isEndConvert !== null && strtolower($isEndConvert) === "true") { + return (string)$responceFromConvertService->FileUrl; + } + + return ""; + } + + /** + * Request for conversion to a service + * + * @param string $document_uri - Uri for the document to convert + * @param string $from_extension - Document extension + * @param string $to_extension - Extension to which to convert + * @param string $document_revision_id - Key for caching on service + * @param bool - $is_async - Perform conversions asynchronously + * + * @return array + */ + public function SendRequestToConvertService($document_uri, $from_extension, $to_extension, $document_revision_id, $is_async) { + $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); + + if (empty($documentServerUrl)) { + throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); + } + + $urlToConverter = $documentServerUrl . "ConvertService.ashx"; + + if (empty($document_revision_id)) { + $document_revision_id = $document_uri; + } + + $document_revision_id = self::GenerateRevisionId($document_revision_id); + + if (empty($from_extension)) { + $from_extension = pathinfo($document_uri)["extension"]; + } else { + $from_extension = trim($from_extension, "."); + } + + $data = [ + "async" => $is_async, + "url" => $document_uri, + "outputtype" => trim($to_extension, "."), + "filetype" => $from_extension, + "title" => $document_revision_id . "." . $from_extension, + "key" => $document_revision_id, + "region" => str_replace("_", "-", \OC::$server->getL10NFactory("")->get("")->getLanguageCode()) + ]; + + if ($this->config->UseDemo()) { + $data["tenant"] = $this->config->GetSystemValue("instanceid", true); + } + + $opts = [ + "timeout" => "120", + "headers" => [ + "Content-type" => "application/json" + ], + "body" => json_encode($data) + ]; + + if (!empty($this->config->GetDocumentServerSecret())) { + $params = [ + "payload" => $data + ]; + $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); + $opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token; + + $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256"); + $data["token"] = $token; + $opts["body"] = json_encode($data); + } + + $response_xml_data = $this->Request($urlToConverter, "post", $opts); + + libxml_use_internal_errors(true); + if (!\function_exists("simplexml_load_file")) { + throw new \Exception($this->trans->t("Server can't read xml")); + } + $response_data = simplexml_load_string($response_xml_data); + if (!$response_data) { + $exc = $this->trans->t("Bad Response. Errors: "); + foreach (libxml_get_errors() as $error) { + $exc = $exc . "\t" . $error->message; + } + throw new \Exception($exc); + } + + return $response_data; + } + + /** + * Generate an error code table of convertion + * + * @param string $errorCode - Error code + * + * @return null + */ + public function ProcessConvServResponceError($errorCode) { + $errorMessageTemplate = $this->trans->t("Error occurred in the document service"); + $errorMessage = ""; + + switch ($errorCode) { + case -20: + $errorMessage = $errorMessageTemplate . ": Error encrypt signature"; + break; + case -8: + $errorMessage = $errorMessageTemplate . ": Invalid token"; + break; + case -7: + $errorMessage = $errorMessageTemplate . ": Error document request"; + break; + case -6: + $errorMessage = $errorMessageTemplate . ": Error while accessing the conversion result database"; + break; + case -5: + $errorMessage = $errorMessageTemplate . ": Incorrect password"; + break; + case -4: + $errorMessage = $errorMessageTemplate . ": Error while downloading the document file to be converted."; + break; + case -3: + $errorMessage = $errorMessageTemplate . ": Conversion error"; + break; + case -2: + $errorMessage = $errorMessageTemplate . ": Timeout conversion error"; + break; + case -1: + $errorMessage = $errorMessageTemplate . ": Unknown error"; + break; + case 0: + break; + default: + $errorMessage = $errorMessageTemplate . ": ErrorCode = " . $errorCode; + break; + } + + throw new \Exception($errorMessage); + } + + /** + * Request health status + * + * @return bool + */ + public function HealthcheckRequest() { + $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); + + if (empty($documentServerUrl)) { + throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); + } + + $urlHealthcheck = $documentServerUrl . "healthcheck"; + + $response = $this->Request($urlHealthcheck); + + return $response === "true"; + } + + /** + * Send command + * + * @param string $method - type of command + * + * @return array + */ + public function CommandRequest($method) { + $documentServerUrl = $this->config->GetDocumentServerInternalUrl(); + + if (empty($documentServerUrl)) { + throw new \Exception($this->trans->t("ONLYOFFICE app is not configured. Please contact admin")); + } + + $urlCommand = $documentServerUrl . "coauthoring/CommandService.ashx"; + + $data = [ + "c" => $method + ]; + + $opts = [ + "headers" => [ + "Content-type" => "application/json" + ], + "body" => json_encode($data) + ]; + + if (!empty($this->config->GetDocumentServerSecret())) { + $params = [ + "payload" => $data + ]; + $token = \Firebase\JWT\JWT::encode($params, $this->config->GetDocumentServerSecret(), "HS256"); + $opts["headers"][$this->config->JwtHeader()] = "Bearer " . $token; + + $token = \Firebase\JWT\JWT::encode($data, $this->config->GetDocumentServerSecret(), "HS256"); + $data["token"] = $token; + $opts["body"] = json_encode($data); + } + + $response = $this->Request($urlCommand, "post", $opts); + + $data = json_decode($response); + + $this->ProcessCommandServResponceError($data->error); + + return $data; + } + + /** + * Generate an error code table of command + * + * @param string $errorCode - Error code + * + * @return null + */ + public function ProcessCommandServResponceError($errorCode) { + $errorMessageTemplate = $this->trans->t("Error occurred in the document service"); + $errorMessage = ""; + + switch ($errorCode) { + case 6: + $errorMessage = $errorMessageTemplate . ": Invalid token"; + break; + case 5: + $errorMessage = $errorMessageTemplate . ": Command not correсt"; + break; + case 3: + $errorMessage = $errorMessageTemplate . ": Internal server error"; + break; + case 0: + return; + default: + $errorMessage = $errorMessageTemplate . ": ErrorCode = " . $errorCode; + break; + } + + throw new \Exception($errorMessage); + } + + /** + * Request to Document Server with turn off verification + * + * @param string $url - request address + * @param array $method - request method + * @param array $opts - request options + * + * @return string + */ + public function Request($url, $method = "get", $opts = null) { + $httpClientService = \OC::$server->getHTTPClientService(); + $client = $httpClientService->newClient(); + + if ($opts === null) { + $opts = []; + } + if (substr($url, 0, \strlen("https")) === "https" && $this->config->GetVerifyPeerOff()) { + $opts["verify"] = false; + } + if (!\array_key_exists("timeout", $opts)) { + $opts["timeout"] = 60; + } + + if ($method === "post") { + $response = $client->post($url, $opts); + } else { + $response = $client->get($url, $opts); + } + + return $response->getBody(); + } + + /** + * Checking document service location + * + * @param OCP\IURLGenerator $urlGenerator - url generator + * @param OCA\Onlyoffice\Crypt $crypt -crypt + * + * @return array + */ + public function checkDocServiceUrl($urlGenerator, $crypt) { + $logger = \OC::$server->getLogger(); + $version = null; + + try { + if (preg_match("/^https:\/\//i", $urlGenerator->getAbsoluteURL("/")) + && preg_match("/^http:\/\//i", $this->config->GetDocumentServerUrl())) { + throw new \Exception($this->trans->t("Mixed Active Content is not allowed. HTTPS address for ONLYOFFICE Docs is required.")); + } + } catch (\Exception $e) { + $logger->logException($e, ["message" => "Protocol on check error", "app" => self::$appName]); + return [$e->getMessage(), $version]; + } + + try { + $healthcheckResponse = $this->HealthcheckRequest(); + if (!$healthcheckResponse) { + throw new \Exception($this->trans->t("Bad healthcheck status")); + } + } catch (\Exception $e) { + $logger->logException($e, ["message" => "HealthcheckRequest on check error", "app" => self::$appName]); + return [$e->getMessage(), $version]; + } + + try { + $commandResponse = $this->CommandRequest("version"); + + $logger->debug("CommandRequest on check: " . json_encode($commandResponse), ["app" => self::$appName]); + + if (empty($commandResponse)) { + throw new \Exception($this->trans->t("Error occurred in the document service")); + } + + $version = $commandResponse->version; + $versionF = \floatval($version); + if ($versionF > 0.0 && $versionF <= 6.0) { + throw new \Exception($this->trans->t("Not supported version")); + } + } catch (\Exception $e) { + $logger->logException($e, ["message" => "CommandRequest on check error", "app" => self::$appName]); + return [$e->getMessage(), $version]; + } + + $convertedFileUri = null; + try { + $hashUrl = $crypt->GetHash(["action" => "empty"]); + $fileUrl = $urlGenerator->linkToRouteAbsolute(self::$appName . ".callback.emptyfile", ["doc" => $hashUrl]); + if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { + $fileUrl = str_replace($urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); + } + + $convertedFileUri = $this->GetConvertedUri($fileUrl, "docx", "docx", "check_" . rand()); + } catch (\Exception $e) { + $logger->logException($e, ["message" => "GetConvertedUri on check error", "app" => self::$appName]); + return [$e->getMessage(), $version]; + } + + try { + $this->Request($convertedFileUri); + } catch (\Exception $e) { + $logger->logException($e, ["message" => "Request converted file on check error", "app" => self::$appName]); + return [$e->getMessage(), $version]; + } + + return ["", $version]; + } } diff --git a/lib/fileutility.php b/lib/fileutility.php index 3233511e..d413484a 100644 --- a/lib/fileutility.php +++ b/lib/fileutility.php @@ -38,260 +38,260 @@ * @package OCA\Onlyoffice */ class FileUtility { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * Share manager - * - * @var IManager - */ - private $shareManager; - - /** - * Session - * - * @var ISession - */ - private $session; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * @param string $AppName - application name - * @param IL10N $trans - l10n service - * @param ILogger $logger - logger - * @param AppConfig $config - application configuration - * @param IManager $shareManager - Share manager - * @param IManager $ISession - Session - */ - public function __construct( - $AppName, - IL10N $trans, - ILogger $logger, - AppConfig $config, - IManager $shareManager, - ISession $session - ) { - $this->appName = $AppName; - $this->trans = $trans; - $this->logger = $logger; - $this->config = $config; - $this->shareManager = $shareManager; - $this->session = $session; - } - - /** - * Getting file by token - * - * @param integer $fileId - file identifier - * @param string $shareToken - access token - * @param string $path - file path - * - * @return array - */ - public function getFileByToken($fileId, $shareToken, $path = null) { - list($node, $error, $share) = $this->getNodeByToken($shareToken); - - if (isset($error)) { - return [null, $error, null]; - } - - if ($node instanceof Folder) { - if ($fileId !== null && $fileId !== 0) { - try { - $files = $node->getById($fileId); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFileByToken: $fileId", "app" => $this->appName]); - return [null, $this->trans->t("Invalid request"), null]; - } - - if (empty($files)) { - $this->logger->info("Files not found: $fileId", ["app" => $this->appName]); - return [null, $this->trans->t("File not found"), null]; - } - $file = $files[0]; - } else { - try { - $file = $node->get($path); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "getFileByToken for path: $path", "app" => $this->appName]); - return [null, $this->trans->t("Invalid request"), null]; - } - } - } else { - $file = $node; - } - - return [$file, null, $share]; - } - - /** - * Getting file by token - * - * @param string $shareToken - access token - * - * @return array - */ - public function getNodeByToken($shareToken) { - list($share, $error) = $this->getShare($shareToken); - - if (isset($error)) { - return [null, $error, null]; - } - - if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) { - return [null, $this->trans->t("You do not have enough permissions to view the file"), null]; - } - - try { - $node = $share->getNode(); - } catch (NotFoundException $e) { - $this->logger->logException($e, ["message" => "getNodeByToken error", "app" => $this->appName]); - return [null, $this->trans->t("File not found"), null]; - } - - return [$node, null, $share]; - } - - /** - * Getting share by token - * - * @param string $shareToken - access token - * - * @return array - */ - public function getShare($shareToken) { - if (empty($shareToken)) { - return [null, $this->trans->t("FileId is empty")]; - } - - $share = null; - try { - $share = $this->shareManager->getShareByToken($shareToken); - } catch (ShareNotFound $e) { - $this->logger->logException($e, ["message" => "getShare error", "app" => $this->appName]); - $share = null; - } - - if ($share === null || $share === false) { - return [null, $this->trans->t("You do not have enough permissions to view the file")]; - } - - if ($share->getPassword() - && (!$this->session->exists("public_link_authenticated") - || $this->session->get("public_link_authenticated") !== (string) $share->getId())) { - return [null, $this->trans->t("You do not have enough permissions to view the file")]; - } - - return [$share, null]; - } - - /** - * Generate unique document identifier - * - * @param File $file - file - * @param bool $origin - request from federated store - * - * @return string - */ - public function getKey($file, $origin = false) { - $fileId = $file->getId(); - - if ($origin - && RemoteInstance::isRemoteFile($file)) { - $key = RemoteInstance::getRemoteKey($file); - if (!empty($key)) { - return $key; - } - } - - $key = KeyManager::get($fileId); - - if (empty($key)) { - $instanceId = $this->config->GetSystemValue("instanceid", true); - - $key = $instanceId . "_" . $this->GUID(); - - KeyManager::set($fileId, $key); - } - - return $key; - } - - /** - * Detected attribute permission for shared file - * - * @param File $file - file - * @param string $attribute - request from federated store - * - * @return bool - */ - public function hasPermissionAttribute($file, $attribute = "download") { - $fileStorage = $file->getStorage(); - if ($fileStorage->instanceOfStorage("\OCA\Files_Sharing\SharedStorage")) { - $storageShare = $fileStorage->getShare(); - if (method_exists($storageShare, "getAttributes")) { - $attributes = $storageShare->getAttributes(); - - $permissionsDownload = $attributes->getAttribute("permissions", "download"); - if ($permissionsDownload !== null && $permissionsDownload !== true) { - return false; - } - } - } - - return true; - } - - /** - * Generate unique identifier - * - * @return string - */ - private function GUID() { - if (\function_exists("com_create_guid") === true) { - return trim(com_create_guid(), "{}"); - } - - return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)); - } - - /** - * Generate unique file version key - * - * @param Version $version - file version - * - * @return string - */ - public function getVersionKey($version) { - $instanceId = $this->config->GetSystemValue("instanceid", true); - - $key = $instanceId . "_" . $version->getSourceFile()->getEtag() . "_" . $version->getRevisionId(); - - return $key; - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * Share manager + * + * @var IManager + */ + private $shareManager; + + /** + * Session + * + * @var ISession + */ + private $session; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * @param string $AppName - application name + * @param IL10N $trans - l10n service + * @param ILogger $logger - logger + * @param AppConfig $config - application configuration + * @param IManager $shareManager - Share manager + * @param IManager $ISession - Session + */ + public function __construct( + $AppName, + IL10N $trans, + ILogger $logger, + AppConfig $config, + IManager $shareManager, + ISession $session + ) { + $this->appName = $AppName; + $this->trans = $trans; + $this->logger = $logger; + $this->config = $config; + $this->shareManager = $shareManager; + $this->session = $session; + } + + /** + * Getting file by token + * + * @param integer $fileId - file identifier + * @param string $shareToken - access token + * @param string $path - file path + * + * @return array + */ + public function getFileByToken($fileId, $shareToken, $path = null) { + list($node, $error, $share) = $this->getNodeByToken($shareToken); + + if (isset($error)) { + return [null, $error, null]; + } + + if ($node instanceof Folder) { + if ($fileId !== null && $fileId !== 0) { + try { + $files = $node->getById($fileId); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFileByToken: $fileId", "app" => $this->appName]); + return [null, $this->trans->t("Invalid request"), null]; + } + + if (empty($files)) { + $this->logger->info("Files not found: $fileId", ["app" => $this->appName]); + return [null, $this->trans->t("File not found"), null]; + } + $file = $files[0]; + } else { + try { + $file = $node->get($path); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "getFileByToken for path: $path", "app" => $this->appName]); + return [null, $this->trans->t("Invalid request"), null]; + } + } + } else { + $file = $node; + } + + return [$file, null, $share]; + } + + /** + * Getting file by token + * + * @param string $shareToken - access token + * + * @return array + */ + public function getNodeByToken($shareToken) { + list($share, $error) = $this->getShare($shareToken); + + if (isset($error)) { + return [null, $error, null]; + } + + if (($share->getPermissions() & Constants::PERMISSION_READ) === 0) { + return [null, $this->trans->t("You do not have enough permissions to view the file"), null]; + } + + try { + $node = $share->getNode(); + } catch (NotFoundException $e) { + $this->logger->logException($e, ["message" => "getNodeByToken error", "app" => $this->appName]); + return [null, $this->trans->t("File not found"), null]; + } + + return [$node, null, $share]; + } + + /** + * Getting share by token + * + * @param string $shareToken - access token + * + * @return array + */ + public function getShare($shareToken) { + if (empty($shareToken)) { + return [null, $this->trans->t("FileId is empty")]; + } + + $share = null; + try { + $share = $this->shareManager->getShareByToken($shareToken); + } catch (ShareNotFound $e) { + $this->logger->logException($e, ["message" => "getShare error", "app" => $this->appName]); + $share = null; + } + + if ($share === null || $share === false) { + return [null, $this->trans->t("You do not have enough permissions to view the file")]; + } + + if ($share->getPassword() + && (!$this->session->exists("public_link_authenticated") + || $this->session->get("public_link_authenticated") !== (string) $share->getId())) { + return [null, $this->trans->t("You do not have enough permissions to view the file")]; + } + + return [$share, null]; + } + + /** + * Generate unique document identifier + * + * @param File $file - file + * @param bool $origin - request from federated store + * + * @return string + */ + public function getKey($file, $origin = false) { + $fileId = $file->getId(); + + if ($origin + && RemoteInstance::isRemoteFile($file)) { + $key = RemoteInstance::getRemoteKey($file); + if (!empty($key)) { + return $key; + } + } + + $key = KeyManager::get($fileId); + + if (empty($key)) { + $instanceId = $this->config->GetSystemValue("instanceid", true); + + $key = $instanceId . "_" . $this->GUID(); + + KeyManager::set($fileId, $key); + } + + return $key; + } + + /** + * Detected attribute permission for shared file + * + * @param File $file - file + * @param string $attribute - request from federated store + * + * @return bool + */ + public function hasPermissionAttribute($file, $attribute = "download") { + $fileStorage = $file->getStorage(); + if ($fileStorage->instanceOfStorage("\OCA\Files_Sharing\SharedStorage")) { + $storageShare = $fileStorage->getShare(); + if (method_exists($storageShare, "getAttributes")) { + $attributes = $storageShare->getAttributes(); + + $permissionsDownload = $attributes->getAttribute("permissions", "download"); + if ($permissionsDownload !== null && $permissionsDownload !== true) { + return false; + } + } + } + + return true; + } + + /** + * Generate unique identifier + * + * @return string + */ + private function GUID() { + if (\function_exists("com_create_guid") === true) { + return trim(com_create_guid(), "{}"); + } + + return sprintf('%04X%04X-%04X-%04X-%04X-%04X%04X%04X', mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(16384, 20479), mt_rand(32768, 49151), mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)); + } + + /** + * Generate unique file version key + * + * @param Version $version - file version + * + * @return string + */ + public function getVersionKey($version) { + $instanceId = $this->config->GetSystemValue("instanceid", true); + + $key = $instanceId . "_" . $version->getSourceFile()->getEtag() . "_" . $version->getRevisionId(); + + return $key; + } } diff --git a/lib/fileversions.php b/lib/fileversions.php index e690f1f3..957271c1 100644 --- a/lib/fileversions.php +++ b/lib/fileversions.php @@ -34,444 +34,444 @@ * @package OCA\Onlyoffice */ class FileVersions { - /** - * Application name - * - * @var string - */ - private static $appName = "onlyoffice"; - - /** - * Changes file extension - * - * @var string - */ - private static $changesExt = ".zip"; - - /** - * History file extension - * - * @var string - */ - private static $historyExt = ".json"; - - /** - * File name contain author - * - * @var string - */ - private static $authorExt = "_author.json"; - - /** - * Split file path and version id - * - * @param string $pathVersion - version path - * - * @return array - */ - public static function splitPathVersion($pathVersion) { - $pos = strrpos($pathVersion, ".v"); - if ($pos === false) { - return false; - } - $filePath = substr($pathVersion, 0, $pos); - $versionId = substr($pathVersion, 2 + $pos - \strlen($pathVersion)); - return [$filePath, $versionId]; - } - - /** - * Check if folder is not exist - * - * @param View $view - view - * @param string $path - folder path - * @param bool $createIfNotExist - create folder if not exist - * - * @return bool - */ - private static function checkFolderExist($view, $path, $createIfNotExist = false) { - if ($view->is_dir($path)) { - return true; - } - if (!$createIfNotExist) { - return false; - } - $view->mkdir($path); - return true; - } - - /** - * Get view and path for changes - * - * @param string $userId - user id - * @param string $fileId - file id - * @param bool $createIfNotExist - create folder if not exist - * - * @return array - */ - private static function getView($userId, $fileId, $createIfNotExist = false) { - $view = new View("/" . $userId); - - $path = self::$appName; - if (!self::checkFolderExist($view, $path, $createIfNotExist)) { - return [null, null]; - } - - if ($fileId === null) { - return [$view, $path]; - } - - $path = $path . "/" . $fileId; - if (!self::checkFolderExist($view, $path, $createIfNotExist)) { - return [null, null]; - } - - return [$view, $path]; - } - - /** - * Get changes from stored to history object - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - * @param string $prevVersion - previous version for check - * - * @return array - */ - public static function getHistoryData($ownerId, $fileId, $versionId, $prevVersion) { - $logger = \OC::$server->getLogger(); - - if ($ownerId === null || $fileId === null) { - return null; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return null; - } - - $historyPath = $path . "/" . $versionId . self::$historyExt; - if (!$view->file_exists($historyPath)) { - return null; - } - - $historyDataString = $view->file_get_contents($historyPath); - - try { - $historyData = json_decode($historyDataString, true); - - if ($historyData["prev"] !== $prevVersion) { - $logger->debug("getHistoryData: previous $prevVersion != " . $historyData["prev"], ["app" => self::$appName]); - - $view->unlink($historyPath); - $logger->debug("getHistoryData: delete $historyPath", ["app" => self::$appName]); - - $changesPath = $path . "/" . $versionId . self::$changesExt; - if ($view->file_exists($changesPath)) { - $view->unlink($changesPath); - $logger->debug("getHistoryData: delete $changesPath", ["app" => self::$appName]); - } - return null; - } - - return $historyData; - } catch (\Exception $e) { - $logger->logException($e, ["message" => "getHistoryData: $fileId $versionId", "app" => self::$appName]); - return null; - } - } - - /** - * Check if changes is stored - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - * - * @return bool - */ - public static function hasChanges($ownerId, $fileId, $versionId) { - if ($ownerId === null || $fileId === null) { - return false; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return false; - } - - $changesPath = $path . "/" . $versionId . self::$changesExt; - return $view->file_exists($changesPath); - } - - /** - * Get changes file - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - * - * @return File - */ - public static function getChangesFile($ownerId, $fileId, $versionId) { - if ($ownerId === null || $fileId === null) { - return null; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return null; - } - - $changesPath = $path . "/" . $versionId . self::$changesExt; - if (!$view->file_exists($changesPath)) { - return null; - } - - $changesInfo = $view->getFileInfo($changesPath); - $changes = new File($view->getRoot(), $view, $changesPath, $changesInfo); - - \OC::$server->getLogger()->debug("getChangesFile: $fileId for $ownerId get changes $changesPath", ["app" => self::$appName]); - - return $changes; - } - - /** - * Save history to storage - * - * @param FileInfo $fileInfo - file info - * @param array $history - file history - * @param string $changesurl - file changes - * @param string $prevVersion - previous version for check - */ - public static function saveHistory($fileInfo, $history, $changes, $prevVersion) { - $logger = \OC::$server->getLogger(); - - if ($fileInfo === null) { - return; - } - - $owner = $fileInfo->getOwner(); - if ($owner === null) { - return; - } - - if (empty($history) || empty($changes)) { - return; - } - - if ($fileInfo->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { - return; - } - - $ownerId = $owner->getUID(); - $fileId = $fileInfo->getId(); - $versionId = $fileInfo->getMtime(); - - list($view, $path) = self::getView($ownerId, $fileId, true); - - try { - $changesPath = $path . "/" . $versionId . self::$changesExt; - $view->touch($changesPath); - $view->file_put_contents($changesPath, $changes); - - $history["prev"] = $prevVersion; - $historyPath = $path . "/" . $versionId . self::$historyExt; - $view->touch($historyPath); - $view->file_put_contents($historyPath, json_encode($history)); - - $logger->debug("saveHistory: $fileId for $ownerId stored changes $changesPath history $historyPath", ["app" => self::$appName]); - } catch (\Exception $e) { - $logger->logException($e, ["message" => "saveHistory: save $fileId history error", "app" => self::$appName]); - } - } - - /** - * Delete all versions of file - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - */ - public static function deleteAllVersions($ownerId, $fileId = null) { - $logger = \OC::$server->getLogger(); - - $logger->debug("deleteAllVersions $ownerId $fileId", ["app" => self::$appName]); - - if ($ownerId === null) { - return; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return; - } - - $view->unlink($path); - } - - /** - * Delete changes and history - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - */ - public static function deleteVersion($ownerId, $fileId, $versionId) { - $logger = \OC::$server->getLogger(); - - $logger->debug("deleteVersion $fileId ($versionId)", ["app" => self::$appName]); - - if ($ownerId === null) { - return; - } - if ($fileId === null || empty($versionId)) { - return; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return null; - } - - $historyPath = $path . "/" . $versionId . self::$historyExt; - if ($view->file_exists($historyPath)) { - $view->unlink($historyPath); - $logger->debug("deleteVersion $historyPath", ["app" => self::$appName]); - } - - $changesPath = $path . "/" . $versionId . self::$changesExt; - if ($view->file_exists($changesPath)) { - $view->unlink($changesPath); - $logger->debug("deleteVersion $changesPath", ["app" => self::$appName]); - } - } - - /** - * Clear all version history - */ - public static function clearHistory() { - $logger = \OC::$server->getLogger(); - - $userDatabase = new Database(); - $userIds = $userDatabase->getUsers(); - - $view = new View("/"); - - foreach ($userIds as $userId) { - $path = $userId . "/" . self::$appName; - - if ($view->file_exists($path)) { - $view->unlink($path); - } - } - - $logger->debug("clear all history", ["app" => self::$appName]); - } - - /** - * Save file author - * - * @param FileInfo $fileInfo - file info - * @param IUser $author - version author - */ - public static function saveAuthor($fileInfo, $author) { - $logger = \OC::$server->getLogger(); - - if ($fileInfo === null || $author === null) { - return; - } - - $owner = $fileInfo->getOwner(); - if ($owner === null) { - return; - } - - if ($fileInfo->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { - return; - } - - $ownerId = $owner->getUID(); - $fileId = $fileInfo->getId(); - $versionId = $fileInfo->getMtime(); - - list($view, $path) = self::getView($ownerId, $fileId, true); - - try { - $authorPath = $path . "/" . $versionId . self::$authorExt; - $view->touch($authorPath); - - $authorData = [ - "id" => $author->getUID(), - "name" => $author->getDisplayName() - ]; - $view->file_put_contents($authorPath, json_encode($authorData)); - - $logger->debug("saveAuthor: $fileId for $ownerId stored author $authorPath", ["app" => self::$appName]); - } catch (\Exception $e) { - $logger->logException($e, ["message" => "saveAuthor: save $fileId author error", "app" => self::$appName]); - } - } - - /** - * Get version author id and name - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - * - * @return array - */ - public static function getAuthor($ownerId, $fileId, $versionId) { - if ($ownerId === null || $fileId === null) { - return null; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return null; - } - - $authorPath = $path . "/" . $versionId . self::$authorExt; - if (!$view->file_exists($authorPath)) { - return null; - } - - $authorDataString = $view->file_get_contents($authorPath); - $author = json_decode($authorDataString, true); - - \OC::$server->getLogger()->debug("getAuthor: $fileId v.$versionId for $ownerId get author $authorPath", ["app" => self::$appName]); - - return $author; - } - - /** - * Delete version author info - * - * @param string $ownerId - file owner id - * @param string $fileId - file id - * @param string $versionId - file version - */ - public static function deleteAuthor($ownerId, $fileId, $versionId) { - $logger = \OC::$server->getLogger(); - - $logger->debug("deleteAuthor $fileId ($versionId)", ["app" => self::$appName]); - - if ($ownerId === null) { - return; - } - if ($fileId === null || empty($versionId)) { - return; - } - - list($view, $path) = self::getView($ownerId, $fileId); - if ($view === null) { - return null; - } - - $authorPath = $path . "/" . $versionId . self::$authorExt; - if ($view->file_exists($authorPath)) { - $view->unlink($authorPath); - $logger->debug("deleteAuthor $authorPath", ["app" => self::$appName]); - } - } + /** + * Application name + * + * @var string + */ + private static $appName = "onlyoffice"; + + /** + * Changes file extension + * + * @var string + */ + private static $changesExt = ".zip"; + + /** + * History file extension + * + * @var string + */ + private static $historyExt = ".json"; + + /** + * File name contain author + * + * @var string + */ + private static $authorExt = "_author.json"; + + /** + * Split file path and version id + * + * @param string $pathVersion - version path + * + * @return array + */ + public static function splitPathVersion($pathVersion) { + $pos = strrpos($pathVersion, ".v"); + if ($pos === false) { + return false; + } + $filePath = substr($pathVersion, 0, $pos); + $versionId = substr($pathVersion, 2 + $pos - \strlen($pathVersion)); + return [$filePath, $versionId]; + } + + /** + * Check if folder is not exist + * + * @param View $view - view + * @param string $path - folder path + * @param bool $createIfNotExist - create folder if not exist + * + * @return bool + */ + private static function checkFolderExist($view, $path, $createIfNotExist = false) { + if ($view->is_dir($path)) { + return true; + } + if (!$createIfNotExist) { + return false; + } + $view->mkdir($path); + return true; + } + + /** + * Get view and path for changes + * + * @param string $userId - user id + * @param string $fileId - file id + * @param bool $createIfNotExist - create folder if not exist + * + * @return array + */ + private static function getView($userId, $fileId, $createIfNotExist = false) { + $view = new View("/" . $userId); + + $path = self::$appName; + if (!self::checkFolderExist($view, $path, $createIfNotExist)) { + return [null, null]; + } + + if ($fileId === null) { + return [$view, $path]; + } + + $path = $path . "/" . $fileId; + if (!self::checkFolderExist($view, $path, $createIfNotExist)) { + return [null, null]; + } + + return [$view, $path]; + } + + /** + * Get changes from stored to history object + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + * @param string $prevVersion - previous version for check + * + * @return array + */ + public static function getHistoryData($ownerId, $fileId, $versionId, $prevVersion) { + $logger = \OC::$server->getLogger(); + + if ($ownerId === null || $fileId === null) { + return null; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return null; + } + + $historyPath = $path . "/" . $versionId . self::$historyExt; + if (!$view->file_exists($historyPath)) { + return null; + } + + $historyDataString = $view->file_get_contents($historyPath); + + try { + $historyData = json_decode($historyDataString, true); + + if ($historyData["prev"] !== $prevVersion) { + $logger->debug("getHistoryData: previous $prevVersion != " . $historyData["prev"], ["app" => self::$appName]); + + $view->unlink($historyPath); + $logger->debug("getHistoryData: delete $historyPath", ["app" => self::$appName]); + + $changesPath = $path . "/" . $versionId . self::$changesExt; + if ($view->file_exists($changesPath)) { + $view->unlink($changesPath); + $logger->debug("getHistoryData: delete $changesPath", ["app" => self::$appName]); + } + return null; + } + + return $historyData; + } catch (\Exception $e) { + $logger->logException($e, ["message" => "getHistoryData: $fileId $versionId", "app" => self::$appName]); + return null; + } + } + + /** + * Check if changes is stored + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + * + * @return bool + */ + public static function hasChanges($ownerId, $fileId, $versionId) { + if ($ownerId === null || $fileId === null) { + return false; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return false; + } + + $changesPath = $path . "/" . $versionId . self::$changesExt; + return $view->file_exists($changesPath); + } + + /** + * Get changes file + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + * + * @return File + */ + public static function getChangesFile($ownerId, $fileId, $versionId) { + if ($ownerId === null || $fileId === null) { + return null; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return null; + } + + $changesPath = $path . "/" . $versionId . self::$changesExt; + if (!$view->file_exists($changesPath)) { + return null; + } + + $changesInfo = $view->getFileInfo($changesPath); + $changes = new File($view->getRoot(), $view, $changesPath, $changesInfo); + + \OC::$server->getLogger()->debug("getChangesFile: $fileId for $ownerId get changes $changesPath", ["app" => self::$appName]); + + return $changes; + } + + /** + * Save history to storage + * + * @param FileInfo $fileInfo - file info + * @param array $history - file history + * @param string $changesurl - file changes + * @param string $prevVersion - previous version for check + */ + public static function saveHistory($fileInfo, $history, $changes, $prevVersion) { + $logger = \OC::$server->getLogger(); + + if ($fileInfo === null) { + return; + } + + $owner = $fileInfo->getOwner(); + if ($owner === null) { + return; + } + + if (empty($history) || empty($changes)) { + return; + } + + if ($fileInfo->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { + return; + } + + $ownerId = $owner->getUID(); + $fileId = $fileInfo->getId(); + $versionId = $fileInfo->getMtime(); + + list($view, $path) = self::getView($ownerId, $fileId, true); + + try { + $changesPath = $path . "/" . $versionId . self::$changesExt; + $view->touch($changesPath); + $view->file_put_contents($changesPath, $changes); + + $history["prev"] = $prevVersion; + $historyPath = $path . "/" . $versionId . self::$historyExt; + $view->touch($historyPath); + $view->file_put_contents($historyPath, json_encode($history)); + + $logger->debug("saveHistory: $fileId for $ownerId stored changes $changesPath history $historyPath", ["app" => self::$appName]); + } catch (\Exception $e) { + $logger->logException($e, ["message" => "saveHistory: save $fileId history error", "app" => self::$appName]); + } + } + + /** + * Delete all versions of file + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + */ + public static function deleteAllVersions($ownerId, $fileId = null) { + $logger = \OC::$server->getLogger(); + + $logger->debug("deleteAllVersions $ownerId $fileId", ["app" => self::$appName]); + + if ($ownerId === null) { + return; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return; + } + + $view->unlink($path); + } + + /** + * Delete changes and history + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + */ + public static function deleteVersion($ownerId, $fileId, $versionId) { + $logger = \OC::$server->getLogger(); + + $logger->debug("deleteVersion $fileId ($versionId)", ["app" => self::$appName]); + + if ($ownerId === null) { + return; + } + if ($fileId === null || empty($versionId)) { + return; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return null; + } + + $historyPath = $path . "/" . $versionId . self::$historyExt; + if ($view->file_exists($historyPath)) { + $view->unlink($historyPath); + $logger->debug("deleteVersion $historyPath", ["app" => self::$appName]); + } + + $changesPath = $path . "/" . $versionId . self::$changesExt; + if ($view->file_exists($changesPath)) { + $view->unlink($changesPath); + $logger->debug("deleteVersion $changesPath", ["app" => self::$appName]); + } + } + + /** + * Clear all version history + */ + public static function clearHistory() { + $logger = \OC::$server->getLogger(); + + $userDatabase = new Database(); + $userIds = $userDatabase->getUsers(); + + $view = new View("/"); + + foreach ($userIds as $userId) { + $path = $userId . "/" . self::$appName; + + if ($view->file_exists($path)) { + $view->unlink($path); + } + } + + $logger->debug("clear all history", ["app" => self::$appName]); + } + + /** + * Save file author + * + * @param FileInfo $fileInfo - file info + * @param IUser $author - version author + */ + public static function saveAuthor($fileInfo, $author) { + $logger = \OC::$server->getLogger(); + + if ($fileInfo === null || $author === null) { + return; + } + + $owner = $fileInfo->getOwner(); + if ($owner === null) { + return; + } + + if ($fileInfo->getStorage()->instanceOfStorage(SharingExternalStorage::class)) { + return; + } + + $ownerId = $owner->getUID(); + $fileId = $fileInfo->getId(); + $versionId = $fileInfo->getMtime(); + + list($view, $path) = self::getView($ownerId, $fileId, true); + + try { + $authorPath = $path . "/" . $versionId . self::$authorExt; + $view->touch($authorPath); + + $authorData = [ + "id" => $author->getUID(), + "name" => $author->getDisplayName() + ]; + $view->file_put_contents($authorPath, json_encode($authorData)); + + $logger->debug("saveAuthor: $fileId for $ownerId stored author $authorPath", ["app" => self::$appName]); + } catch (\Exception $e) { + $logger->logException($e, ["message" => "saveAuthor: save $fileId author error", "app" => self::$appName]); + } + } + + /** + * Get version author id and name + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + * + * @return array + */ + public static function getAuthor($ownerId, $fileId, $versionId) { + if ($ownerId === null || $fileId === null) { + return null; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return null; + } + + $authorPath = $path . "/" . $versionId . self::$authorExt; + if (!$view->file_exists($authorPath)) { + return null; + } + + $authorDataString = $view->file_get_contents($authorPath); + $author = json_decode($authorDataString, true); + + \OC::$server->getLogger()->debug("getAuthor: $fileId v.$versionId for $ownerId get author $authorPath", ["app" => self::$appName]); + + return $author; + } + + /** + * Delete version author info + * + * @param string $ownerId - file owner id + * @param string $fileId - file id + * @param string $versionId - file version + */ + public static function deleteAuthor($ownerId, $fileId, $versionId) { + $logger = \OC::$server->getLogger(); + + $logger->debug("deleteAuthor $fileId ($versionId)", ["app" => self::$appName]); + + if ($ownerId === null) { + return; + } + if ($fileId === null || empty($versionId)) { + return; + } + + list($view, $path) = self::getView($ownerId, $fileId); + if ($view === null) { + return null; + } + + $authorPath = $path . "/" . $versionId . self::$authorExt; + if ($view->file_exists($authorPath)) { + $view->unlink($authorPath); + $logger->debug("deleteAuthor $authorPath", ["app" => self::$appName]); + } + } } diff --git a/lib/hookhandler.php b/lib/hookhandler.php index fe74c171..5d7973ca 100644 --- a/lib/hookhandler.php +++ b/lib/hookhandler.php @@ -31,20 +31,20 @@ * @package OCA\Onlyoffice */ class HookHandler { - public static function PublicPage() { - $appName = "onlyoffice"; + public static function PublicPage() { + $appName = "onlyoffice"; - $appConfig = new AppConfig($appName); + $appConfig = new AppConfig($appName); - if (!empty($appConfig->GetDocumentServerUrl()) && $appConfig->SettingsAreSuccessful()) { - Util::addScript("onlyoffice", "main"); - Util::addScript("onlyoffice", "share"); + if (!empty($appConfig->GetDocumentServerUrl()) && $appConfig->SettingsAreSuccessful()) { + Util::addScript("onlyoffice", "main"); + Util::addScript("onlyoffice", "share"); - if ($appConfig->GetSameTab()) { - Util::addScript("onlyoffice", "listener"); - } + if ($appConfig->GetSameTab()) { + Util::addScript("onlyoffice", "listener"); + } - Util::addStyle("onlyoffice", "main"); - } - } + Util::addStyle("onlyoffice", "main"); + } + } } diff --git a/lib/hooks.php b/lib/hooks.php index 87632d2f..2b464f00 100644 --- a/lib/hooks.php +++ b/lib/hooks.php @@ -32,154 +32,154 @@ * @package OCA\Onlyoffice */ class Hooks { - /** - * Application name - * - * @var string - */ - private static $appName = "onlyoffice"; - - public static function connectHooks() { - // Listen user deletion - Util::connectHook("OC_User", "pre_deleteUser", Hooks::class, "userDelete"); - - // Listen file change - Util::connectHook("OC_Filesystem", "write", Hooks::class, "fileUpdate"); - - // Listen file deletion - Util::connectHook("OC_Filesystem", "delete", Hooks::class, "fileDelete"); - - // Listen file version deletion - Util::connectHook("\OCP\Versions", "preDelete", Hooks::class, "fileVersionDelete"); - - // Listen file version restore - Util::connectHook("\OCP\Versions", "rollback", Hooks::class, "fileVersionRestore"); - } - - /** - * Erase user file versions - * - * @param array $params - hook params - */ - public static function userDelete($params) { - $userId = $params["uid"]; - - FileVersions::deleteAllVersions($userId); - } - - /** - * Listen of file change - * - * @param array $params - hook params - */ - public static function fileUpdate($params) { - $filePath = $params[Filesystem::signal_param_path]; - if (empty($filePath)) { - return; - } - - $fileInfo = Filesystem::getFileInfo($filePath); - if ($fileInfo === false) { - return; - } - - $fileId = $fileInfo->getId(); - - KeyManager::delete($fileId); - - \OC::$server->getLogger()->debug("Hook fileUpdate " . json_encode($params), ["app" => self::$appName]); - } - - /** - * Erase versions of deleted file - * - * @param array $params - hook params - */ - public static function fileDelete($params) { - $filePath = $params[Filesystem::signal_param_path]; - if (empty($filePath)) { - return; - } - - try { - $ownerId = Filesystem::getOwner($filePath); - - $fileInfo = Filesystem::getFileInfo($filePath); - if ($fileInfo === false) { - return; - } - - $fileId = $fileInfo->getId(); - - KeyManager::delete($fileId, true); - - FileVersions::deleteAllVersions($ownerId, $fileId); - } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileDelete " . json_encode($params), "app" => self::$appName]); - } - } - - /** - * Erase versions of deleted version of file - * - * @param array $params - hook param - */ - public static function fileVersionDelete($params) { - $pathVersion = $params["path"]; - if (empty($pathVersion)) { - return; - } - - try { - list($filePath, $versionId) = FileVersions::splitPathVersion($pathVersion); - if (empty($filePath)) { - return; - } - - $ownerId = Filesystem::getOwner($filePath); - - $fileInfo = Filesystem::getFileInfo($filePath); - if ($fileInfo === false) { - return; - } - - $fileId = $fileInfo->getId(); - - FileVersions::deleteVersion($ownerId, $fileId, $versionId); - FileVersions::deleteAuthor($ownerId, $fileId, $versionId); - } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileVersionDelete " . json_encode($params), "app" => self::$appName]); - } - } - - /** - * Erase versions of restored version of file - * - * @param array $params - hook param - */ - public static function fileVersionRestore($params) { - $filePath = $params["path"]; - if (empty($filePath)) { - return; - } - - $versionId = $params["revision"]; - - try { - $ownerId = Filesystem::getOwner($filePath); - - $fileInfo = Filesystem::getFileInfo($filePath); - if ($fileInfo === false) { - return; - } - - $fileId = $fileInfo->getId(); - - KeyManager::delete($fileId); - - FileVersions::deleteVersion($ownerId, $fileId, $versionId); - } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileVersionRestore " . json_encode($params), "app" => self::$appName]); - } - } + /** + * Application name + * + * @var string + */ + private static $appName = "onlyoffice"; + + public static function connectHooks() { + // Listen user deletion + Util::connectHook("OC_User", "pre_deleteUser", Hooks::class, "userDelete"); + + // Listen file change + Util::connectHook("OC_Filesystem", "write", Hooks::class, "fileUpdate"); + + // Listen file deletion + Util::connectHook("OC_Filesystem", "delete", Hooks::class, "fileDelete"); + + // Listen file version deletion + Util::connectHook("\OCP\Versions", "preDelete", Hooks::class, "fileVersionDelete"); + + // Listen file version restore + Util::connectHook("\OCP\Versions", "rollback", Hooks::class, "fileVersionRestore"); + } + + /** + * Erase user file versions + * + * @param array $params - hook params + */ + public static function userDelete($params) { + $userId = $params["uid"]; + + FileVersions::deleteAllVersions($userId); + } + + /** + * Listen of file change + * + * @param array $params - hook params + */ + public static function fileUpdate($params) { + $filePath = $params[Filesystem::signal_param_path]; + if (empty($filePath)) { + return; + } + + $fileInfo = Filesystem::getFileInfo($filePath); + if ($fileInfo === false) { + return; + } + + $fileId = $fileInfo->getId(); + + KeyManager::delete($fileId); + + \OC::$server->getLogger()->debug("Hook fileUpdate " . json_encode($params), ["app" => self::$appName]); + } + + /** + * Erase versions of deleted file + * + * @param array $params - hook params + */ + public static function fileDelete($params) { + $filePath = $params[Filesystem::signal_param_path]; + if (empty($filePath)) { + return; + } + + try { + $ownerId = Filesystem::getOwner($filePath); + + $fileInfo = Filesystem::getFileInfo($filePath); + if ($fileInfo === false) { + return; + } + + $fileId = $fileInfo->getId(); + + KeyManager::delete($fileId, true); + + FileVersions::deleteAllVersions($ownerId, $fileId); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileDelete " . json_encode($params), "app" => self::$appName]); + } + } + + /** + * Erase versions of deleted version of file + * + * @param array $params - hook param + */ + public static function fileVersionDelete($params) { + $pathVersion = $params["path"]; + if (empty($pathVersion)) { + return; + } + + try { + list($filePath, $versionId) = FileVersions::splitPathVersion($pathVersion); + if (empty($filePath)) { + return; + } + + $ownerId = Filesystem::getOwner($filePath); + + $fileInfo = Filesystem::getFileInfo($filePath); + if ($fileInfo === false) { + return; + } + + $fileId = $fileInfo->getId(); + + FileVersions::deleteVersion($ownerId, $fileId, $versionId); + FileVersions::deleteAuthor($ownerId, $fileId, $versionId); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileVersionDelete " . json_encode($params), "app" => self::$appName]); + } + } + + /** + * Erase versions of restored version of file + * + * @param array $params - hook param + */ + public static function fileVersionRestore($params) { + $filePath = $params["path"]; + if (empty($filePath)) { + return; + } + + $versionId = $params["revision"]; + + try { + $ownerId = Filesystem::getOwner($filePath); + + $fileInfo = Filesystem::getFileInfo($filePath); + if ($fileInfo === false) { + return; + } + + $fileId = $fileInfo->getId(); + + KeyManager::delete($fileId); + + FileVersions::deleteVersion($ownerId, $fileId, $versionId); + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, ["message" => "Hook: fileVersionRestore " . json_encode($params), "app" => self::$appName]); + } + } } diff --git a/lib/keymanager.php b/lib/keymanager.php index 1ab475c9..4cf4a0fa 100644 --- a/lib/keymanager.php +++ b/lib/keymanager.php @@ -25,125 +25,125 @@ * @package OCA\Onlyoffice */ class KeyManager { - /** - * Table name - */ - private const TableName_Key = "onlyoffice_filekey"; + /** + * Table name + */ + private const TableName_Key = "onlyoffice_filekey"; - /** - * Get document identifier - * - * @param integer $fileId - file identifier - * - * @return string - */ - public static function get($fileId) { - $connection = \OC::$server->getDatabaseConnection(); - $select = $connection->prepare(" + /** + * Get document identifier + * + * @param integer $fileId - file identifier + * + * @return string + */ + public static function get($fileId) { + $connection = \OC::$server->getDatabaseConnection(); + $select = $connection->prepare(" SELECT `key` FROM `*PREFIX*" . self::TableName_Key . "` WHERE `file_id` = ? "); - $result = $select->execute([$fileId]); + $result = $select->execute([$fileId]); - $keys = $result ? $select->fetch() : []; - $key = \is_array($keys) && isset($keys["key"]) ? $keys["key"] : ""; + $keys = $result ? $select->fetch() : []; + $key = \is_array($keys) && isset($keys["key"]) ? $keys["key"] : ""; - return $key; - } + return $key; + } - /** - * Store document identifier - * - * @param integer $fileId - file identifier - * @param integer $key - file key - * - * @return bool - */ - public static function set($fileId, $key) { - $connection = \OC::$server->getDatabaseConnection(); - $insert = $connection->prepare(" + /** + * Store document identifier + * + * @param integer $fileId - file identifier + * @param integer $key - file key + * + * @return bool + */ + public static function set($fileId, $key) { + $connection = \OC::$server->getDatabaseConnection(); + $insert = $connection->prepare(" INSERT INTO `*PREFIX*" . self::TableName_Key . "` (`file_id`, `key`) VALUES (?, ?) "); - return (bool)$insert->execute([$fileId, $key]); - } + return (bool)$insert->execute([$fileId, $key]); + } - /** - * Delete document identifier - * - * @param integer $fileId - file identifier - * @param bool $unlock - delete even with lock label - * - * @return bool - */ - public static function delete($fileId, $unlock = false) { - $connection = \OC::$server->getDatabaseConnection(); - $delete = $connection->prepare( - " + /** + * Delete document identifier + * + * @param integer $fileId - file identifier + * @param bool $unlock - delete even with lock label + * + * @return bool + */ + public static function delete($fileId, $unlock = false) { + $connection = \OC::$server->getDatabaseConnection(); + $delete = $connection->prepare( + " DELETE FROM `*PREFIX*" . self::TableName_Key . "` WHERE `file_id` = ? " . ($unlock === false ? "AND `lock` != 1" : "") - ); - return (bool)$delete->execute([$fileId]); - } + ); + return (bool)$delete->execute([$fileId]); + } - /** - * Change lock status - * - * @param integer $fileId - file identifier - * @param bool $lock - status - * - * @return bool - */ - public static function lock($fileId, $lock = true) { - $connection = \OC::$server->getDatabaseConnection(); - $update = $connection->prepare(" + /** + * Change lock status + * + * @param integer $fileId - file identifier + * @param bool $lock - status + * + * @return bool + */ + public static function lock($fileId, $lock = true) { + $connection = \OC::$server->getDatabaseConnection(); + $update = $connection->prepare(" UPDATE `*PREFIX*" . self::TableName_Key . "` SET `lock` = ? WHERE `file_id` = ? "); - return (bool)$update->execute([$lock === true ? 1 : 0, $fileId]); - } + return (bool)$update->execute([$lock === true ? 1 : 0, $fileId]); + } - /** - * Change forcesave status - * - * @param integer $fileId - file identifier - * @param bool $fs - status - * - * @return bool - */ - public static function setForcesave($fileId, $fs = true) { - $connection = \OC::$server->getDatabaseConnection(); - $update = $connection->prepare(" + /** + * Change forcesave status + * + * @param integer $fileId - file identifier + * @param bool $fs - status + * + * @return bool + */ + public static function setForcesave($fileId, $fs = true) { + $connection = \OC::$server->getDatabaseConnection(); + $update = $connection->prepare(" UPDATE `*PREFIX*" . self::TableName_Key . "` SET `fs` = ? WHERE `file_id` = ? "); - return (bool)$update->execute([$fs === true ? 1 : 0, $fileId]); - } + return (bool)$update->execute([$fs === true ? 1 : 0, $fileId]); + } - /** - * Get forcesave status - * - * @param integer $fileId - file identifier - * - * @return bool - */ - public static function wasForcesave($fileId) { - $connection = \OC::$server->getDatabaseConnection(); - $select = $connection->prepare(" + /** + * Get forcesave status + * + * @param integer $fileId - file identifier + * + * @return bool + */ + public static function wasForcesave($fileId) { + $connection = \OC::$server->getDatabaseConnection(); + $select = $connection->prepare(" SELECT `fs` FROM `*PREFIX*" . self::TableName_Key . "` WHERE `file_id` = ? "); - $result = $select->execute([$fileId]); + $result = $select->execute([$fileId]); - $rows = $result ? $select->fetch() : []; - $fs = \is_array($rows) && isset($rows["fs"]) ? $rows["fs"] : ""; + $rows = $result ? $select->fetch() : []; + $fs = \is_array($rows) && isset($rows["fs"]) ? $rows["fs"] : ""; - return $fs === "1"; - } + return $fs === "1"; + } } diff --git a/lib/notifier.php b/lib/notifier.php index 689a5b8d..ecdf1bbd 100644 --- a/lib/notifier.php +++ b/lib/notifier.php @@ -27,110 +27,110 @@ use OCP\Notification\INotifier; class Notifier implements INotifier { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * IFactory - * - * @var IFactory - */ - private $l10nFactory; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * User manager - * - * @var IUserManager - */ - private $userManager; - - /** - * @param string $AppName - application name - * @param IFactory $l10NFactory - l10n - * @param IURLGenerator $urlGenerator - url generator service - * @param ILogger $logger - logger - * @param IUserManager $userManager - user manager - */ - public function __construct( - string $appName, - IFactory $l10nFactory, - IURLGenerator $urlGenerator, - ILogger $logger, - IUserManager $userManager - ) { - $this->appName = $appName; - $this->l10nFactory = $l10nFactory; - $this->urlGenerator = $urlGenerator; - $this->logger = $logger; - $this->userManager = $userManager; - } - - /** - * @param INotification $notification - notification object - * @param string $languageCode - the code of the language that should be used to prepare the notification - * - * @return INotification - */ - public function prepare($notification, $languageCode) { - if ($notification->getApp() !== $this->appName) { - throw new \InvalidArgumentException("Notification not from " . $this->appName); - } - - $parameters = $notification->getSubjectParameters(); - $trans = $this->l10nFactory->get($this->appName, $languageCode); - - switch ($notification->getObjectType()) { - case "editorsCheck": - $message = $trans->t("Please check the settings to resolve the problem."); - $appSettingsLink = $this->urlGenerator->getAbsoluteURL("/settings/admin?sectionid=additional"); - $notification->setLink($appSettingsLink); - $notification->setParsedSubject($notification->getObjectId()) - ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, 'app-dark.svg'))); - $notification->setParsedMessage($message); - break; - case "mention": - $notifierId = $parameters["notifierId"]; - $fileId = $parameters["fileId"]; - $fileName = $parameters["fileName"]; - $anchor = $parameters["anchor"]; - - $this->logger->info("Notify prepare: from $notifierId about $fileId ", ["app" => $this->appName]); - - $notifier = $this->userManager->get($notifierId); - $notifierName = $notifier->getDisplayName(); - $trans = $this->l10nFactory->get($this->appName, $languageCode); - - $notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, "app-dark.svg"))); - $notification->setParsedSubject($trans->t("%1\$s mentioned in the %2\$s: \"%3\$s\".", [$notifierName, $fileName, $notification->getObjectId()])); - - $editorLink = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", [ - "fileId" => $fileId, - "anchor" => $anchor - ]); - - $notification->setLink($editorLink); - break; - default: - $this->logger->info("Unsupported notification object: ".$notification->getObjectType(), ["app" => $this->appName]); - } - return $notification; - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * IFactory + * + * @var IFactory + */ + private $l10nFactory; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * User manager + * + * @var IUserManager + */ + private $userManager; + + /** + * @param string $AppName - application name + * @param IFactory $l10NFactory - l10n + * @param IURLGenerator $urlGenerator - url generator service + * @param ILogger $logger - logger + * @param IUserManager $userManager - user manager + */ + public function __construct( + string $appName, + IFactory $l10nFactory, + IURLGenerator $urlGenerator, + ILogger $logger, + IUserManager $userManager + ) { + $this->appName = $appName; + $this->l10nFactory = $l10nFactory; + $this->urlGenerator = $urlGenerator; + $this->logger = $logger; + $this->userManager = $userManager; + } + + /** + * @param INotification $notification - notification object + * @param string $languageCode - the code of the language that should be used to prepare the notification + * + * @return INotification + */ + public function prepare($notification, $languageCode) { + if ($notification->getApp() !== $this->appName) { + throw new \InvalidArgumentException("Notification not from " . $this->appName); + } + + $parameters = $notification->getSubjectParameters(); + $trans = $this->l10nFactory->get($this->appName, $languageCode); + + switch ($notification->getObjectType()) { + case "editorsCheck": + $message = $trans->t("Please check the settings to resolve the problem."); + $appSettingsLink = $this->urlGenerator->getAbsoluteURL("/settings/admin?sectionid=additional"); + $notification->setLink($appSettingsLink); + $notification->setParsedSubject($notification->getObjectId()) + ->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, 'app-dark.svg'))); + $notification->setParsedMessage($message); + break; + case "mention": + $notifierId = $parameters["notifierId"]; + $fileId = $parameters["fileId"]; + $fileName = $parameters["fileName"]; + $anchor = $parameters["anchor"]; + + $this->logger->info("Notify prepare: from $notifierId about $fileId ", ["app" => $this->appName]); + + $notifier = $this->userManager->get($notifierId); + $notifierName = $notifier->getDisplayName(); + $trans = $this->l10nFactory->get($this->appName, $languageCode); + + $notification->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath($this->appName, "app-dark.svg"))); + $notification->setParsedSubject($trans->t("%1\$s mentioned in the %2\$s: \"%3\$s\".", [$notifierName, $fileName, $notification->getObjectId()])); + + $editorLink = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".editor.index", [ + "fileId" => $fileId, + "anchor" => $anchor + ]); + + $notification->setLink($editorLink); + break; + default: + $this->logger->info("Unsupported notification object: ".$notification->getObjectType(), ["app" => $this->appName]); + } + return $notification; + } } diff --git a/lib/preview.php b/lib/preview.php index 86e7444f..585a61b6 100644 --- a/lib/preview.php +++ b/lib/preview.php @@ -45,349 +45,349 @@ * @package OCA\Onlyoffice */ class Preview implements IProvider2 { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * Root folder - * - * @var IRootFolder - */ - private $root; - - /** - * User manager - * - * @var IUserManager - */ - private $userManager; - - /** - * Logger - * - * @var ILogger - */ - private $logger; - - /** - * l10n service - * - * @var IL10N - */ - private $trans; - - /** - * Application configuration - * - * @var AppConfig - */ - private $config; - - /** - * Url generator service - * - * @var IURLGenerator - */ - private $urlGenerator; - - /** - * Hash generator - * - * @var Crypt - */ - private $crypt; - - /** - * File version manager - * - * @var VersionManager - */ - private $versionManager; - - /** - * File utility - * - * @var FileUtility - */ - private $fileUtility; - - /** - * Capabilities mimetype - * - * @var Array - */ - public static $capabilities = [ - "text/csv", - "application/msword", - "application/vnd.ms-word.document.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform", - "application/vnd.openxmlformats-officedocument.wordprocessingml.template", - "application/epub+zip", - "text/html", - "application/vnd.oasis.opendocument.presentation", - "application/vnd.oasis.opendocument.spreadsheet", - "application/vnd.oasis.opendocument.text", - "application/vnd.oasis.opendocument.presentation-template", - "application/vnd.oasis.opendocument.spreadsheet-template", - "application/vnd.oasis.opendocument.text-template", - "application/pdf", - "application/vnd.ms-powerpoint.template.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.presentationml.template", - "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.presentationml.slideshow", - "application/vnd.ms-powerpoint", - "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "text/rtf", - "text/plain", - "application/vnd.ms-excel", - "application/vnd.ms-excel.sheet.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel.template.macroEnabled.12", - "application/vnd.openxmlformats-officedocument.spreadsheetml.template" - ]; - - /** - * Converted thumbnail format - */ - private const thumbExtension = "jpeg"; - - /** - * @param string $appName - application name - * @param IRootFolder $root - root folder - * @param ILogger $logger - logger - * @param IL10N $trans - l10n service - * @param AppConfig $config - application configuration - * @param IURLGenerator $urlGenerator - url generator service - * @param Crypt $crypt - hash generator - * @param IManager $shareManager - share manager - * @param ISession $session - session - * @param IUserManager $userManager - user manager - */ - public function __construct( - string $appName, - IRootFolder $root, - ILogger $logger, - IL10N $trans, - AppConfig $config, - IURLGenerator $urlGenerator, - Crypt $crypt, - IManager $shareManager, - ISession $session, - IUserManager $userManager - ) { - $this->appName = $appName; - $this->root = $root; - $this->logger = $logger; - $this->trans = $trans; - $this->config = $config; - $this->urlGenerator = $urlGenerator; - $this->crypt = $crypt; - $this->userManager = $userManager; - - $this->versionManager = new VersionManager($appName, $root); - - $this->fileUtility = new FileUtility($appName, $trans, $logger, $config, $shareManager, $session); - } - - /** - * Return mime type - */ - public static function getMimeTypeRegex() { - $mimeTypeRegex = ""; - foreach (self::$capabilities as $format) { - if (!empty($mimeTypeRegex)) { - $mimeTypeRegex = $mimeTypeRegex . "|"; - } - $mimeTypeRegex = $mimeTypeRegex . str_replace("/", "\/", $format); - } - $mimeTypeRegex = "/" . $mimeTypeRegex . "/"; - - return $mimeTypeRegex; - } - - /** - * Return mime type - */ - public function getMimeType() { - $m = self::getMimeTypeRegex(); - return $m; - } - - /** - * The method checks if the file can be converted - * - * @param FileInfo $fileInfo - File - * - * @return bool - */ - public function isAvailable(FileInfo $fileInfo) { - if ($this->config->GetPreview() !== true) { - return false; - } - if (!$fileInfo - || $fileInfo->getSize() === 0 - || $fileInfo->getSize() > $this->config->GetLimitThumbSize()) { - return false; - } - if (!\in_array($fileInfo->getMimetype(), self::$capabilities, true)) { - return false; - } - return true; - } - - /** - * The method is generated thumbnail for file and returned image object - * - * @param File $file - file - * @param int $maxX - The maximum X size of the thumbnail - * @param int $maxY - The maximum Y size of the thumbnail - * @param bool $scalingup - Disable/Enable upscaling of previews - * - * @return Image|bool false if no preview was generated - */ - public function getThumbnail($file, $maxX, $maxY, $scalingup) { - if (empty($file)) { - $this->logger->error("getThumbnail is impossible. File is null", ["app" => $this->appName]); - return false; - } - - $this->logger->debug("getThumbnail " . $file->getPath() . " $maxX $maxY", ["app" => $this->appName]); - - list($fileUrl, $extension, $key) = $this->getFileParam($file); - if ($fileUrl === null || $extension === null || $key === null) { - return false; - } - - $imageUrl = null; - $documentService = new DocumentService($this->trans, $this->config); - try { - $imageUrl = $documentService->GetConvertedUri($fileUrl, $extension, self::thumbExtension, $key); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "GetConvertedUri: from $extension to " . self::thumbExtension, "app" => $this->appName]); - return false; - } - - try { - $thumbnail = $documentService->Request($imageUrl); - } catch (\Exception $e) { - $this->logger->logException($e, ["message" => "Failed to download thumbnail", "app" => $this->appName]); - return false; - } - - $image = new Image(); - $image->loadFromData($thumbnail); - - if ($image->valid()) { - $image->scaleDownToFit($maxX, $maxY); - return $image; - } - - return false; - } - - /** - * Generate secure link to download document - * - * @param File $file - file - * @param IUser $user - user with access - * @param int $version - file version - * - * @return string - */ - private function getUrl($file, $user = null, $version = 0) { - $data = [ - "action" => "download", - "fileId" => $file->getId() - ]; - - $userId = null; - if (!empty($user)) { - $userId = $user->getUID(); - $data["userId"] = $userId; - } - if ($version > 0) { - $data["version"] = $version; - } - - $hashUrl = $this->crypt->GetHash($data); - - $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); - - if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { - $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); - } - - return $fileUrl; - } - - /** - * Generate array with file parameters - * - * @param File $file - file - * - * @return array - */ - private function getFileParam($file) { - if ($file->getSize() === 0) { - return [null, null, null]; - } - - $key = null; - $versionNum = 0; - if ($file instanceof MetaFileVersionNode) { - if ($this->versionManager->available !== true) { - return [null, null, null]; - } - - $fileVersion = $file->getName(); - $sourceFileId = $file->getId(); - - $storage = $file->getStorage(); - $path = $file->getContentDispositionFileName(); - - $ownerId = $storage->getOwner($path); - $owner = $this->userManager->get($ownerId); - if ($owner === null) { - return [null, null, null]; - } - - $files = $this->root->getUserFolder($ownerId)->getById($sourceFileId); - if (empty($files)) { - return [null, null, null]; - } - $file = $files[0]; - - $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); - - foreach ($versions as $version) { - $versionNum = $versionNum + 1; - - $versionId = $version->getRevisionId(); - if (strcmp($versionId, $fileVersion) === 0) { - $key = $this->fileUtility->getVersionKey($version); - $key = DocumentService::GenerateRevisionId($key); - - break; - } - } - } else { - $owner = $file->getOwner(); - - $key = $this->fileUtility->getKey($file); - $key = DocumentService::GenerateRevisionId($key); - } - - $fileUrl = $this->getUrl($file, $owner, $versionNum); - - $fileExtension = strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION)); - - return [$fileUrl, $fileExtension, $key]; - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * Root folder + * + * @var IRootFolder + */ + private $root; + + /** + * User manager + * + * @var IUserManager + */ + private $userManager; + + /** + * Logger + * + * @var ILogger + */ + private $logger; + + /** + * l10n service + * + * @var IL10N + */ + private $trans; + + /** + * Application configuration + * + * @var AppConfig + */ + private $config; + + /** + * Url generator service + * + * @var IURLGenerator + */ + private $urlGenerator; + + /** + * Hash generator + * + * @var Crypt + */ + private $crypt; + + /** + * File version manager + * + * @var VersionManager + */ + private $versionManager; + + /** + * File utility + * + * @var FileUtility + */ + private $fileUtility; + + /** + * Capabilities mimetype + * + * @var Array + */ + public static $capabilities = [ + "text/csv", + "application/msword", + "application/vnd.ms-word.document.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxf", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document.oform", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "application/epub+zip", + "text/html", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.presentation-template", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.text-template", + "application/pdf", + "application/vnd.ms-powerpoint.template.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.presentationml.template", + "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "application/vnd.ms-powerpoint", + "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "text/rtf", + "text/plain", + "application/vnd.ms-excel", + "application/vnd.ms-excel.sheet.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel.template.macroEnabled.12", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template" + ]; + + /** + * Converted thumbnail format + */ + private const thumbExtension = "jpeg"; + + /** + * @param string $appName - application name + * @param IRootFolder $root - root folder + * @param ILogger $logger - logger + * @param IL10N $trans - l10n service + * @param AppConfig $config - application configuration + * @param IURLGenerator $urlGenerator - url generator service + * @param Crypt $crypt - hash generator + * @param IManager $shareManager - share manager + * @param ISession $session - session + * @param IUserManager $userManager - user manager + */ + public function __construct( + string $appName, + IRootFolder $root, + ILogger $logger, + IL10N $trans, + AppConfig $config, + IURLGenerator $urlGenerator, + Crypt $crypt, + IManager $shareManager, + ISession $session, + IUserManager $userManager + ) { + $this->appName = $appName; + $this->root = $root; + $this->logger = $logger; + $this->trans = $trans; + $this->config = $config; + $this->urlGenerator = $urlGenerator; + $this->crypt = $crypt; + $this->userManager = $userManager; + + $this->versionManager = new VersionManager($appName, $root); + + $this->fileUtility = new FileUtility($appName, $trans, $logger, $config, $shareManager, $session); + } + + /** + * Return mime type + */ + public static function getMimeTypeRegex() { + $mimeTypeRegex = ""; + foreach (self::$capabilities as $format) { + if (!empty($mimeTypeRegex)) { + $mimeTypeRegex = $mimeTypeRegex . "|"; + } + $mimeTypeRegex = $mimeTypeRegex . str_replace("/", "\/", $format); + } + $mimeTypeRegex = "/" . $mimeTypeRegex . "/"; + + return $mimeTypeRegex; + } + + /** + * Return mime type + */ + public function getMimeType() { + $m = self::getMimeTypeRegex(); + return $m; + } + + /** + * The method checks if the file can be converted + * + * @param FileInfo $fileInfo - File + * + * @return bool + */ + public function isAvailable(FileInfo $fileInfo) { + if ($this->config->GetPreview() !== true) { + return false; + } + if (!$fileInfo + || $fileInfo->getSize() === 0 + || $fileInfo->getSize() > $this->config->GetLimitThumbSize()) { + return false; + } + if (!\in_array($fileInfo->getMimetype(), self::$capabilities, true)) { + return false; + } + return true; + } + + /** + * The method is generated thumbnail for file and returned image object + * + * @param File $file - file + * @param int $maxX - The maximum X size of the thumbnail + * @param int $maxY - The maximum Y size of the thumbnail + * @param bool $scalingup - Disable/Enable upscaling of previews + * + * @return Image|bool false if no preview was generated + */ + public function getThumbnail($file, $maxX, $maxY, $scalingup) { + if (empty($file)) { + $this->logger->error("getThumbnail is impossible. File is null", ["app" => $this->appName]); + return false; + } + + $this->logger->debug("getThumbnail " . $file->getPath() . " $maxX $maxY", ["app" => $this->appName]); + + list($fileUrl, $extension, $key) = $this->getFileParam($file); + if ($fileUrl === null || $extension === null || $key === null) { + return false; + } + + $imageUrl = null; + $documentService = new DocumentService($this->trans, $this->config); + try { + $imageUrl = $documentService->GetConvertedUri($fileUrl, $extension, self::thumbExtension, $key); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "GetConvertedUri: from $extension to " . self::thumbExtension, "app" => $this->appName]); + return false; + } + + try { + $thumbnail = $documentService->Request($imageUrl); + } catch (\Exception $e) { + $this->logger->logException($e, ["message" => "Failed to download thumbnail", "app" => $this->appName]); + return false; + } + + $image = new Image(); + $image->loadFromData($thumbnail); + + if ($image->valid()) { + $image->scaleDownToFit($maxX, $maxY); + return $image; + } + + return false; + } + + /** + * Generate secure link to download document + * + * @param File $file - file + * @param IUser $user - user with access + * @param int $version - file version + * + * @return string + */ + private function getUrl($file, $user = null, $version = 0) { + $data = [ + "action" => "download", + "fileId" => $file->getId() + ]; + + $userId = null; + if (!empty($user)) { + $userId = $user->getUID(); + $data["userId"] = $userId; + } + if ($version > 0) { + $data["version"] = $version; + } + + $hashUrl = $this->crypt->GetHash($data); + + $fileUrl = $this->urlGenerator->linkToRouteAbsolute($this->appName . ".callback.download", ["doc" => $hashUrl]); + + if (!$this->config->UseDemo() && !empty($this->config->GetStorageUrl())) { + $fileUrl = str_replace($this->urlGenerator->getAbsoluteURL("/"), $this->config->GetStorageUrl(), $fileUrl); + } + + return $fileUrl; + } + + /** + * Generate array with file parameters + * + * @param File $file - file + * + * @return array + */ + private function getFileParam($file) { + if ($file->getSize() === 0) { + return [null, null, null]; + } + + $key = null; + $versionNum = 0; + if ($file instanceof MetaFileVersionNode) { + if ($this->versionManager->available !== true) { + return [null, null, null]; + } + + $fileVersion = $file->getName(); + $sourceFileId = $file->getId(); + + $storage = $file->getStorage(); + $path = $file->getContentDispositionFileName(); + + $ownerId = $storage->getOwner($path); + $owner = $this->userManager->get($ownerId); + if ($owner === null) { + return [null, null, null]; + } + + $files = $this->root->getUserFolder($ownerId)->getById($sourceFileId); + if (empty($files)) { + return [null, null, null]; + } + $file = $files[0]; + + $versions = array_reverse($this->versionManager->getVersionsForFile($owner, $file->getFileInfo())); + + foreach ($versions as $version) { + $versionNum = $versionNum + 1; + + $versionId = $version->getRevisionId(); + if (strcmp($versionId, $fileVersion) === 0) { + $key = $this->fileUtility->getVersionKey($version); + $key = DocumentService::GenerateRevisionId($key); + + break; + } + } + } else { + $owner = $file->getOwner(); + + $key = $this->fileUtility->getKey($file); + $key = DocumentService::GenerateRevisionId($key); + } + + $fileUrl = $this->getUrl($file, $owner, $versionNum); + + $fileExtension = strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION)); + + return [$fileUrl, $fileExtension, $key]; + } } diff --git a/lib/remoteinstance.php b/lib/remoteinstance.php index 0d5c00db..8952667c 100644 --- a/lib/remoteinstance.php +++ b/lib/remoteinstance.php @@ -29,254 +29,254 @@ * @package OCA\Onlyoffice */ class RemoteInstance { - /** - * App name - */ - private const App_Name = "onlyoffice"; - - /** - * Table name - */ - private const TableName_Key = "onlyoffice_instance"; - - /** - * Time to live of remote instance (12 hours) - */ - private static $ttl = 60 * 60 * 12; - - /** - * Health remote list - */ - private static $healthRemote = []; - - /** - * Get remote instance - * - * @param string $remote - remote instance - * - * @return array - */ - private static function get($remote) { - $connection = \OC::$server->getDatabaseConnection(); - $select = $connection->prepare(" + /** + * App name + */ + private const App_Name = "onlyoffice"; + + /** + * Table name + */ + private const TableName_Key = "onlyoffice_instance"; + + /** + * Time to live of remote instance (12 hours) + */ + private static $ttl = 60 * 60 * 12; + + /** + * Health remote list + */ + private static $healthRemote = []; + + /** + * Get remote instance + * + * @param string $remote - remote instance + * + * @return array + */ + private static function get($remote) { + $connection = \OC::$server->getDatabaseConnection(); + $select = $connection->prepare(" SELECT remote, expire, status FROM `*PREFIX*" . self::TableName_Key . "` WHERE `remote` = ? "); - $result = $select->execute([$remote]); - - $dbremote = $result ? $select->fetch() : []; - - return $dbremote; - } - - /** - * Store remote instance - * - * @param string $remote - remote instance - * @param bool $status - remote status - * - * @return bool - */ - private static function set($remote, $status) { - $connection = \OC::$server->getDatabaseConnection(); - $insert = $connection->prepare(" + $result = $select->execute([$remote]); + + $dbremote = $result ? $select->fetch() : []; + + return $dbremote; + } + + /** + * Store remote instance + * + * @param string $remote - remote instance + * @param bool $status - remote status + * + * @return bool + */ + private static function set($remote, $status) { + $connection = \OC::$server->getDatabaseConnection(); + $insert = $connection->prepare(" INSERT INTO `*PREFIX*" . self::TableName_Key . "` (`remote`, `status`, `expire`) VALUES (?, ?, ?) "); - return (bool)$insert->execute([$remote, $status === true ? 1 : 0, time()]); - } - - /** - * Update remote instance - * - * @param string $remote - remote instance - * @param bool $status - remote status - * - * @return bool - */ - private static function update($remote, $status) { - $connection = \OC::$server->getDatabaseConnection(); - $update = $connection->prepare(" + return (bool)$insert->execute([$remote, $status === true ? 1 : 0, time()]); + } + + /** + * Update remote instance + * + * @param string $remote - remote instance + * @param bool $status - remote status + * + * @return bool + */ + private static function update($remote, $status) { + $connection = \OC::$server->getDatabaseConnection(); + $update = $connection->prepare(" UPDATE `*PREFIX*" . self::TableName_Key . "` SET status = ?, expire = ? WHERE remote = ? "); - return (bool)$update->execute([$status === true ? 1 : 0, time(), $remote]); - } - - /** - * Health check remote instance - * - * @param string $remote - remote instance - * - * @return bool - */ - public static function healthCheck($remote) { - $logger = \OC::$server->getLogger(); - $remote = rtrim($remote, "/") . "/"; - - if (\in_array($remote, self::$healthRemote)) { - $logger->debug("Remote instance " . $remote . " from local cache status " . $dbremote["status"], ["app" => self::App_Name]); - return true; - } - - $dbremote = self::get($remote); - if (!empty($dbremote) && $dbremote["expire"] + self::$ttl > time()) { - $logger->debug("Remote instance " . $remote . " from database status " . $dbremote["status"], ["app" => self::App_Name]); - self::$healthRemote[$remote] = $dbremote["status"]; - return self::$healthRemote[$remote]; - } - - $httpClientService = \OC::$server->getHTTPClientService(); - $client = $httpClientService->newClient(); - - $status = false; - try { - $response = $client->get($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/healthcheck?format=json"); - $body = json_decode($response->getBody(), true); - - $data = $body["ocs"]["data"]; - if (isset($data["alive"])) { - $status = $data["alive"] === true; - } - } catch (\Exception $e) { - $logger->logException($e, ["message" => "Failed to request federated health check for" . $remote, "app" => self::App_Name]); - } - - if (empty($dbremote)) { - self::set($remote, $status); - } else { - self::update($remote, $status); - } - - $logger->debug("Remote instance " . $remote . " was stored to database status " . $dbremote["status"], ["app" => self::App_Name]); - - self::$healthRemote[$remote] = $status; - - return self::$healthRemote[$remote]; - } - - /** - * Generate unique document identifier in federated share - * - * @param File $file - file - * - * @return string - */ - public function getRemoteKey($file) { - $logger = \OC::$server->getLogger(); - - $remote = $file->getStorage()->getRemote(); - $shareToken = $file->getStorage()->getToken(); - $internalPath = $file->getInternalPath(); - - $httpClientService = \OC::$server->getHTTPClientService(); - $client = $httpClientService->newClient(); - - try { - $response = $client->post($remote . "/ocs/v2.php/apps/" . self::App_Name . "/api/v1/key?format=json", [ - "timeout" => 5, - "json" => [ - "shareToken" => $shareToken, - "path" => $internalPath - ] - ]); - - $body = \json_decode($response->getBody(), true); - - $data = $body["ocs"]["data"]; - if (!empty($data["error"])) { - $logger->error("Error federated key " . $data["error"], ["app" => self::App_Name]); - return null; - } - - $key = $data["key"]; - $logger->debug("Federated key: $key", ["app" => self::App_Name]); - - return $key; - } catch (\Exception $e) { - $logger->logException($e, ["message" => "Failed to request federated key " . $file->getId(), "app" => self::App_Name]); - - if ($e->getResponse()->getStatusCode() === 404) { - self::update($remote, false); - $logger->debug("Changed status for remote instance $remote to false", ["app" => self::App_Name]); - } - - return null; - } - } - - /** - * Change lock status in the federated share - * - * @param File $file - file - * @param bool $lock - status - * @param bool $fs - status - * - * @return bool - */ - public static function lockRemoteKey($file, $lock, $fs) { - $logger = \OC::$server->getLogger(); - $action = $lock ? "lock" : "unlock"; - - $remote = $file->getStorage()->getRemote(); - $shareToken = $file->getStorage()->getToken(); - $internalPath = $file->getInternalPath(); - - $httpClientService = \OC::$server->getHTTPClientService(); - $client = $httpClientService->newClient(); - $data = [ - "timeout" => 5, - "json" => [ - "shareToken" => $shareToken, - "path" => $internalPath, - "lock" => $lock - ] - ]; - if (!empty($fs)) { - $data["json"]["fs"] = $fs; - } - - try { - $response = $client->post($remote . "/ocs/v2.php/apps/" . self::App_Name . "/api/v1/keylock?format=json", $data); - $body = \json_decode($response->getBody(), true); - - $data = $body["ocs"]["data"]; - - if (empty($data)) { - $logger->debug("Federated request" . $action . "for " . $file->getFileInfo()->getId() . " is successful", ["app" => self::App_Name]); - return true; - } - - if (!empty($data["error"])) { - $logger->error("Error" . $action . "federated key for " . $file->getFileInfo()->getId() . ": " . $data["error"], ["app" => self::App_Name]); - return false; - } - } catch(\Exception $e) { - $logger->logException($e, ["message" => "Failed to request federated " . $action . " for " . $file->getFileInfo()->getId(), "app" => self::App_Name]); - return false; - } - } - - /** - * Check of federated capable - * - * @param File $file - file - * - * @return bool - */ - public static function isRemoteFile($file) { - $storage = $file->getStorage(); - - $alive = false; - $isFederated = $storage->instanceOfStorage(SharingExternalStorage::class); - if (!$isFederated) { - return false; - } - - $alive = RemoteInstance::healthCheck($storage->getRemote()); - return $alive; - } + return (bool)$update->execute([$status === true ? 1 : 0, time(), $remote]); + } + + /** + * Health check remote instance + * + * @param string $remote - remote instance + * + * @return bool + */ + public static function healthCheck($remote) { + $logger = \OC::$server->getLogger(); + $remote = rtrim($remote, "/") . "/"; + + if (\in_array($remote, self::$healthRemote)) { + $logger->debug("Remote instance " . $remote . " from local cache status " . $dbremote["status"], ["app" => self::App_Name]); + return true; + } + + $dbremote = self::get($remote); + if (!empty($dbremote) && $dbremote["expire"] + self::$ttl > time()) { + $logger->debug("Remote instance " . $remote . " from database status " . $dbremote["status"], ["app" => self::App_Name]); + self::$healthRemote[$remote] = $dbremote["status"]; + return self::$healthRemote[$remote]; + } + + $httpClientService = \OC::$server->getHTTPClientService(); + $client = $httpClientService->newClient(); + + $status = false; + try { + $response = $client->get($remote . "ocs/v2.php/apps/" . self::App_Name . "/api/v1/healthcheck?format=json"); + $body = json_decode($response->getBody(), true); + + $data = $body["ocs"]["data"]; + if (isset($data["alive"])) { + $status = $data["alive"] === true; + } + } catch (\Exception $e) { + $logger->logException($e, ["message" => "Failed to request federated health check for" . $remote, "app" => self::App_Name]); + } + + if (empty($dbremote)) { + self::set($remote, $status); + } else { + self::update($remote, $status); + } + + $logger->debug("Remote instance " . $remote . " was stored to database status " . $dbremote["status"], ["app" => self::App_Name]); + + self::$healthRemote[$remote] = $status; + + return self::$healthRemote[$remote]; + } + + /** + * Generate unique document identifier in federated share + * + * @param File $file - file + * + * @return string + */ + public function getRemoteKey($file) { + $logger = \OC::$server->getLogger(); + + $remote = $file->getStorage()->getRemote(); + $shareToken = $file->getStorage()->getToken(); + $internalPath = $file->getInternalPath(); + + $httpClientService = \OC::$server->getHTTPClientService(); + $client = $httpClientService->newClient(); + + try { + $response = $client->post($remote . "/ocs/v2.php/apps/" . self::App_Name . "/api/v1/key?format=json", [ + "timeout" => 5, + "json" => [ + "shareToken" => $shareToken, + "path" => $internalPath + ] + ]); + + $body = \json_decode($response->getBody(), true); + + $data = $body["ocs"]["data"]; + if (!empty($data["error"])) { + $logger->error("Error federated key " . $data["error"], ["app" => self::App_Name]); + return null; + } + + $key = $data["key"]; + $logger->debug("Federated key: $key", ["app" => self::App_Name]); + + return $key; + } catch (\Exception $e) { + $logger->logException($e, ["message" => "Failed to request federated key " . $file->getId(), "app" => self::App_Name]); + + if ($e->getResponse()->getStatusCode() === 404) { + self::update($remote, false); + $logger->debug("Changed status for remote instance $remote to false", ["app" => self::App_Name]); + } + + return null; + } + } + + /** + * Change lock status in the federated share + * + * @param File $file - file + * @param bool $lock - status + * @param bool $fs - status + * + * @return bool + */ + public static function lockRemoteKey($file, $lock, $fs) { + $logger = \OC::$server->getLogger(); + $action = $lock ? "lock" : "unlock"; + + $remote = $file->getStorage()->getRemote(); + $shareToken = $file->getStorage()->getToken(); + $internalPath = $file->getInternalPath(); + + $httpClientService = \OC::$server->getHTTPClientService(); + $client = $httpClientService->newClient(); + $data = [ + "timeout" => 5, + "json" => [ + "shareToken" => $shareToken, + "path" => $internalPath, + "lock" => $lock + ] + ]; + if (!empty($fs)) { + $data["json"]["fs"] = $fs; + } + + try { + $response = $client->post($remote . "/ocs/v2.php/apps/" . self::App_Name . "/api/v1/keylock?format=json", $data); + $body = \json_decode($response->getBody(), true); + + $data = $body["ocs"]["data"]; + + if (empty($data)) { + $logger->debug("Federated request" . $action . "for " . $file->getFileInfo()->getId() . " is successful", ["app" => self::App_Name]); + return true; + } + + if (!empty($data["error"])) { + $logger->error("Error" . $action . "federated key for " . $file->getFileInfo()->getId() . ": " . $data["error"], ["app" => self::App_Name]); + return false; + } + } catch(\Exception $e) { + $logger->logException($e, ["message" => "Failed to request federated " . $action . " for " . $file->getFileInfo()->getId(), "app" => self::App_Name]); + return false; + } + } + + /** + * Check of federated capable + * + * @param File $file - file + * + * @return bool + */ + public static function isRemoteFile($file) { + $storage = $file->getStorage(); + + $alive = false; + $isFederated = $storage->instanceOfStorage(SharingExternalStorage::class); + if (!$isFederated) { + return false; + } + + $alive = RemoteInstance::healthCheck($storage->getRemote()); + return $alive; + } } diff --git a/lib/templatemanager.php b/lib/templatemanager.php index c90a9e4f..16e35515 100644 --- a/lib/templatemanager.php +++ b/lib/templatemanager.php @@ -28,190 +28,190 @@ * @package OCA\Onlyoffice */ class TemplateManager { - /** - * Application name - * - * @var string - */ - private static $appName = "onlyoffice"; - - /** - * Template folder name - * - * @var string - */ - private static $templateFolderName = "template"; - - /** - * Get global template directory - * - * @return Folder - */ - public static function GetGlobalTemplateDir() { - $dirPath = self::$appName . "/" . self::$templateFolderName; - - $rootFolder = \OC::$server->getRootFolder(); - $templateDir = null; - try { - $templateDir = $rootFolder->get($dirPath); - } catch (NotFoundException $e) { - $templateDir = $rootFolder->newFolder($dirPath); - } - - return $templateDir; - } - - /** - * Get global templates - * - * @param string $mimetype - mimetype of the template - * - * @return array - */ - public static function GetGlobalTemplates($mimetype = null) { - $templateDir = self::GetGlobalTemplateDir(); - - $templatesList = $templateDir->getDirectoryListing(); - if (!empty($mimetype) - && \is_array($templatesList) && \count($templatesList) > 0) { - $templatesList = $templateDir->searchByMime($mimetype); - } - - return $templatesList; - } - - /** - * Get template file - * - * @param string $templateId - identifier file template - * - * @return File - */ - public static function GetTemplate($templateId) { - $logger = \OC::$server->getLogger(); - - $templateDir = self::GetGlobalTemplateDir(); - try { - $templates = $templateDir->getById($templateId); - } catch(\Exception $e) { - $logger->logException($e, ["message" => "GetTemplate: $templateId", "app" => self::$appName]); - return null; - } - - if (empty($templates)) { - $logger->info("Template not found: $templateId", ["app" => self::$appName]); - return null; - } - - return $templates[0]; - } - - /** - * Get type template from mimetype - * - * @param string $mime - mimetype - * - * @return string - */ - public static function GetTypeTemplate($mime) { - switch($mime) { - case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": - return "document"; - case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": - return "spreadsheet"; - case "application/vnd.openxmlformats-officedocument.presentationml.presentation": - return "presentation"; - } - - return ""; - } - - /** - * Check template type - * - * @param string $name - template name - * - * @return bool - */ - public static function IsTemplateType($name) { - $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); - switch($ext) { - case "docx": - case "xlsx": - case "pptx": - return true; - } - - return false; - } - - /** - * Get empty template content - * - * @param string $fileName - target file name - * - * @return string - */ - public static function GetEmptyTemplate($fileName) { - $ext = strtolower("." . pathinfo($fileName, PATHINFO_EXTENSION)); - $lang = \OC::$server->getL10NFactory("")->get("")->getLanguageCode(); - - $templatePath = self::GetEmptyTemplatePath($lang, $ext); - - $template = file_get_contents($templatePath); - return $template; - } - - /** - * Get template path - * - * @param string $lang - language - * @param string $ext - file extension - * - * @return string - */ - public static function GetEmptyTemplatePath($lang, $ext) { - if (!\array_key_exists($lang, self::$localPath)) { - $lang = "en"; - } - - return \dirname(__DIR__) . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . self::$localPath[$lang] . DIRECTORY_SEPARATOR . "new" . $ext; - } - - /** - * Mapping local path to templates - * - * @var Array - */ - private static $localPath = [ - "az" => "az-Latn-AZ", - "bg_BG" => "bg-BG", - "cs" => "cs-CZ", - "de" => "de-DE", - "de_DE" => "de-DE", - "el" => "el-GR", - "en" => "en-US", - "en_GB" => "en-GB", - "es" => "es-ES", - "eu" => "eu-ES", - "fr" => "fr-FR", - "gl" => "gl-ES", - "it" => "it-IT", - "ja" => "ja-JP", - "ko" => "ko-KR", - "lv" => "lv-LV", - "nl" => "nl-NL", - "pl" => "pl-PL", - "pt_BR" => "pt-BR", - "pt_PT" => "pt-PT", - "ru" => "ru-RU", - "si_LK" => "si-LK", - "sk_SK" => "sk-SK", - "sv" => "sv-SE", - "tr" => "tr-TR", - "uk" => "uk-UA", - "vi" => "vi-VN", - "zh_CN" => "zh-CN", - "zh_TW" => "zh-TW" - ]; + /** + * Application name + * + * @var string + */ + private static $appName = "onlyoffice"; + + /** + * Template folder name + * + * @var string + */ + private static $templateFolderName = "template"; + + /** + * Get global template directory + * + * @return Folder + */ + public static function GetGlobalTemplateDir() { + $dirPath = self::$appName . "/" . self::$templateFolderName; + + $rootFolder = \OC::$server->getRootFolder(); + $templateDir = null; + try { + $templateDir = $rootFolder->get($dirPath); + } catch (NotFoundException $e) { + $templateDir = $rootFolder->newFolder($dirPath); + } + + return $templateDir; + } + + /** + * Get global templates + * + * @param string $mimetype - mimetype of the template + * + * @return array + */ + public static function GetGlobalTemplates($mimetype = null) { + $templateDir = self::GetGlobalTemplateDir(); + + $templatesList = $templateDir->getDirectoryListing(); + if (!empty($mimetype) + && \is_array($templatesList) && \count($templatesList) > 0) { + $templatesList = $templateDir->searchByMime($mimetype); + } + + return $templatesList; + } + + /** + * Get template file + * + * @param string $templateId - identifier file template + * + * @return File + */ + public static function GetTemplate($templateId) { + $logger = \OC::$server->getLogger(); + + $templateDir = self::GetGlobalTemplateDir(); + try { + $templates = $templateDir->getById($templateId); + } catch(\Exception $e) { + $logger->logException($e, ["message" => "GetTemplate: $templateId", "app" => self::$appName]); + return null; + } + + if (empty($templates)) { + $logger->info("Template not found: $templateId", ["app" => self::$appName]); + return null; + } + + return $templates[0]; + } + + /** + * Get type template from mimetype + * + * @param string $mime - mimetype + * + * @return string + */ + public static function GetTypeTemplate($mime) { + switch($mime) { + case "application/vnd.openxmlformats-officedocument.wordprocessingml.document": + return "document"; + case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": + return "spreadsheet"; + case "application/vnd.openxmlformats-officedocument.presentationml.presentation": + return "presentation"; + } + + return ""; + } + + /** + * Check template type + * + * @param string $name - template name + * + * @return bool + */ + public static function IsTemplateType($name) { + $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + switch($ext) { + case "docx": + case "xlsx": + case "pptx": + return true; + } + + return false; + } + + /** + * Get empty template content + * + * @param string $fileName - target file name + * + * @return string + */ + public static function GetEmptyTemplate($fileName) { + $ext = strtolower("." . pathinfo($fileName, PATHINFO_EXTENSION)); + $lang = \OC::$server->getL10NFactory("")->get("")->getLanguageCode(); + + $templatePath = self::GetEmptyTemplatePath($lang, $ext); + + $template = file_get_contents($templatePath); + return $template; + } + + /** + * Get template path + * + * @param string $lang - language + * @param string $ext - file extension + * + * @return string + */ + public static function GetEmptyTemplatePath($lang, $ext) { + if (!\array_key_exists($lang, self::$localPath)) { + $lang = "en"; + } + + return \dirname(__DIR__) . DIRECTORY_SEPARATOR . "assets" . DIRECTORY_SEPARATOR . self::$localPath[$lang] . DIRECTORY_SEPARATOR . "new" . $ext; + } + + /** + * Mapping local path to templates + * + * @var Array + */ + private static $localPath = [ + "az" => "az-Latn-AZ", + "bg_BG" => "bg-BG", + "cs" => "cs-CZ", + "de" => "de-DE", + "de_DE" => "de-DE", + "el" => "el-GR", + "en" => "en-US", + "en_GB" => "en-GB", + "es" => "es-ES", + "eu" => "eu-ES", + "fr" => "fr-FR", + "gl" => "gl-ES", + "it" => "it-IT", + "ja" => "ja-JP", + "ko" => "ko-KR", + "lv" => "lv-LV", + "nl" => "nl-NL", + "pl" => "pl-PL", + "pt_BR" => "pt-BR", + "pt_PT" => "pt-PT", + "ru" => "ru-RU", + "si_LK" => "si-LK", + "sk_SK" => "sk-SK", + "sv" => "sv-SE", + "tr" => "tr-TR", + "uk" => "uk-UA", + "vi" => "vi-VN", + "zh_CN" => "zh-CN", + "zh_TW" => "zh-TW" + ]; } diff --git a/lib/version.php b/lib/version.php index c5a6f981..c89b894f 100644 --- a/lib/version.php +++ b/lib/version.php @@ -27,84 +27,84 @@ * @package OCA\Onlyoffice */ class Version { - /** - * Time of creation - * - * @var int - * */ - private $timestamp; + /** + * Time of creation + * + * @var int + * */ + private $timestamp; - /** - * Version file - * - * @var int|string - * */ - private $revisionId; + /** + * Version file + * + * @var int|string + * */ + private $revisionId; - /** - * File path - * - * @var string - * */ - private $path; + /** + * File path + * + * @var string + * */ + private $path; - /** - * Source file properties - * - * @var FileInfo - * */ - private $sourceFileInfo; + /** + * Source file properties + * + * @var FileInfo + * */ + private $sourceFileInfo; - /** - * @param int $timestamp - file time stamp - * @param int $revisionId - revision id - * @param FileInfo $sourceFileInfo - source file info - */ - public function __construct( - int $timestamp, - int $revisionId, - string $path, - FileInfo $sourceFileInfo - ) { - $this->timestamp = $timestamp; - $this->revisionId = $revisionId; - $this->path = $path; - $this->sourceFileInfo = $sourceFileInfo; - } + /** + * @param int $timestamp - file time stamp + * @param int $revisionId - revision id + * @param FileInfo $sourceFileInfo - source file info + */ + public function __construct( + int $timestamp, + int $revisionId, + string $path, + FileInfo $sourceFileInfo + ) { + $this->timestamp = $timestamp; + $this->revisionId = $revisionId; + $this->path = $path; + $this->sourceFileInfo = $sourceFileInfo; + } - /** - * Get source file - * - * @return FileInfo - */ - public function getSourceFile() { - return $this->sourceFileInfo; - } + /** + * Get source file + * + * @return FileInfo + */ + public function getSourceFile() { + return $this->sourceFileInfo; + } - /** - * Get version file - * - * @return int|string - */ - public function getRevisionId() { - return $this->revisionId; - } + /** + * Get version file + * + * @return int|string + */ + public function getRevisionId() { + return $this->revisionId; + } - /** - * Get timestamp file - * - * @return int - */ - public function getTimestamp() { - return $this->timestamp; - } + /** + * Get timestamp file + * + * @return int + */ + public function getTimestamp() { + return $this->timestamp; + } - /** - * Get file path - * - * @return string - */ - public function getPath() { - return $this->path; - } + /** + * Get file path + * + * @return string + */ + public function getPath() { + return $this->path; + } } diff --git a/lib/versionmanager.php b/lib/versionmanager.php index 6598afe4..f71a9230 100644 --- a/lib/versionmanager.php +++ b/lib/versionmanager.php @@ -36,155 +36,155 @@ * @package OCA\Onlyoffice */ class VersionManager { - /** - * Application name - * - * @var string - */ - private $appName; - - /** - * Root folder - * - * @var IRootFolder - */ - private $rootFolder; - - /** - * File versions storage - * - * @var Storage - */ - private $storage; - - /** - * Version manager is available - * - * @var bool - */ - public $available; - - /** - * @param string $AppName - application name - * @param IRootFolder $rootFolder - root folder - */ - public function __construct(string $AppName, IRootFolder $rootFolder) { - $this->appName = $AppName; - $this->rootFolder = $rootFolder; - - if (\OC::$server->getAppManager()->isInstalled("files_versions")) { - try { - $this->storage = \OC::$server->query(Storage::class); - $this->available = true; - } catch (QueryException $e) { - \OC::$server->getLogger()->logException($e, ["message" => "VersionManager init error", "app" => $this->appName]); - } - } - } - - /** - * Get version folder - * - * @param IUser $user - file owner - * - * @return Folder - */ - private function getVersionFolder($user) { - $userRoot = $this->rootFolder->getUserFolder($user->getUID())->getParent(); - try { - $folder = $userRoot->get("files_versions"); - return $folder; - } catch (NotFoundException $e) { - \OC::$server->getLogger()->logException($e, ["message" => "VersionManager: not found user version folder " . $user->getUID(), "app" => $this->appName]); - return null; - } - } - - /** - * Get file version - * - * @param IUser $user - file owner - * @param FileInfo $sourceFile - file - * @param integer $version - file version - * - * @return File - */ - public function getVersionFile($user, $sourceFile, $version) { - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $versionsFolder = $this->getVersionFolder($user); - - $file = $versionsFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . ".v" . $version); - return $file; - } - - /** - * Get versions for file - * - * @param IUser $user - file owner - * @param FileInfo $file - file - * - * @return array - */ - public function getVersionsForFile($user, $file) { - $versions = []; - - $fileId = $file->getId(); - - try { - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); - $nodes = $userFolder->getById($fileId); - $sourceFile = $nodes[0]; - } catch (\Exception $e) { - \OC::$server->getLogger()->logException($e, ["message" => "VersionManager: $fileId", "app" => $this->appName]); - return $versions; - } - - $owner = $sourceFile->getOwner(); - if ($owner === null) { - return $versions; - } - - $ownerId = $owner->getUID(); - $userFolder = $this->rootFolder->getUserFolder($ownerId); - $sourceFilePath = $userFolder->getRelativePath($sourceFile->getPath()); - $propsVersions = $this->storage->getVersions($ownerId, $sourceFilePath); - - foreach ($propsVersions as $propVersion) { - $version = new Version( - $propVersion["timestamp"], - $propVersion["version"], - $propVersion["path"], - $file - ); - - array_push($versions, $version); - } - - return $versions; - } - - /** - * Restore version - * - * @param Version $version - version for restore - * - */ - public function rollback($version) { - $sourceFile = $version->getSourceFile(); - - $ownerId = null; - $owner = $sourceFile->getOwner(); - if (!empty($owner)) { - $ownerId = $owner->getUID(); - } - - $path = $version->getPath(); - $revision = $version->getTimestamp(); - - $versionFile = $this->getVersionFile($owner, $sourceFile, $revision); - $versionFileInfo = $versionFile->getFileInfo(); - $versionPath = $versionFileInfo->getInternalPath(); - - $this->storage->restoreVersion($ownerId, $path, $versionPath, $revision); - } + /** + * Application name + * + * @var string + */ + private $appName; + + /** + * Root folder + * + * @var IRootFolder + */ + private $rootFolder; + + /** + * File versions storage + * + * @var Storage + */ + private $storage; + + /** + * Version manager is available + * + * @var bool + */ + public $available; + + /** + * @param string $AppName - application name + * @param IRootFolder $rootFolder - root folder + */ + public function __construct(string $AppName, IRootFolder $rootFolder) { + $this->appName = $AppName; + $this->rootFolder = $rootFolder; + + if (\OC::$server->getAppManager()->isInstalled("files_versions")) { + try { + $this->storage = \OC::$server->query(Storage::class); + $this->available = true; + } catch (QueryException $e) { + \OC::$server->getLogger()->logException($e, ["message" => "VersionManager init error", "app" => $this->appName]); + } + } + } + + /** + * Get version folder + * + * @param IUser $user - file owner + * + * @return Folder + */ + private function getVersionFolder($user) { + $userRoot = $this->rootFolder->getUserFolder($user->getUID())->getParent(); + try { + $folder = $userRoot->get("files_versions"); + return $folder; + } catch (NotFoundException $e) { + \OC::$server->getLogger()->logException($e, ["message" => "VersionManager: not found user version folder " . $user->getUID(), "app" => $this->appName]); + return null; + } + } + + /** + * Get file version + * + * @param IUser $user - file owner + * @param FileInfo $sourceFile - file + * @param integer $version - file version + * + * @return File + */ + public function getVersionFile($user, $sourceFile, $version) { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $versionsFolder = $this->getVersionFolder($user); + + $file = $versionsFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . ".v" . $version); + return $file; + } + + /** + * Get versions for file + * + * @param IUser $user - file owner + * @param FileInfo $file - file + * + * @return array + */ + public function getVersionsForFile($user, $file) { + $versions = []; + + $fileId = $file->getId(); + + try { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $nodes = $userFolder->getById($fileId); + $sourceFile = $nodes[0]; + } catch (\Exception $e) { + \OC::$server->getLogger()->logException($e, ["message" => "VersionManager: $fileId", "app" => $this->appName]); + return $versions; + } + + $owner = $sourceFile->getOwner(); + if ($owner === null) { + return $versions; + } + + $ownerId = $owner->getUID(); + $userFolder = $this->rootFolder->getUserFolder($ownerId); + $sourceFilePath = $userFolder->getRelativePath($sourceFile->getPath()); + $propsVersions = $this->storage->getVersions($ownerId, $sourceFilePath); + + foreach ($propsVersions as $propVersion) { + $version = new Version( + $propVersion["timestamp"], + $propVersion["version"], + $propVersion["path"], + $file + ); + + array_push($versions, $version); + } + + return $versions; + } + + /** + * Restore version + * + * @param Version $version - version for restore + * + */ + public function rollback($version) { + $sourceFile = $version->getSourceFile(); + + $ownerId = null; + $owner = $sourceFile->getOwner(); + if (!empty($owner)) { + $ownerId = $owner->getUID(); + } + + $path = $version->getPath(); + $revision = $version->getTimestamp(); + + $versionFile = $this->getVersionFile($owner, $sourceFile, $revision); + $versionFileInfo = $versionFile->getFileInfo(); + $versionPath = $versionFileInfo->getInternalPath(); + + $this->storage->restoreVersion($ownerId, $path, $versionPath, $revision); + } }