Skip to content
Snippets Groups Projects
OnlyOfficeController.php 19.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?php
    
    /**
     * Copyright Maarch since 2008 under licence GPLv3.
     * See LICENCE.txt file at the root folder for more details.
     * This file is part of Maarch software.
     */
    
    /**
     * @brief Only Office Controller
     *
     * @author dev@maarch.org
     */
    
    namespace ContentManagement\controllers;
    
    use Attachment\models\AttachmentModel;
    use Docserver\models\DocserverModel;
    
    use Docserver\models\DocserverTypeModel;
    
    use Resource\controllers\ResController;
    
    use Resource\controllers\StoreController;
    
    use Resource\models\ResModel;
    
    use Respect\Validation\Validator;
    use Slim\Http\Request;
    use Slim\Http\Response;
    
    use SrcCore\controllers\UrlController;
    
    use SrcCore\models\CoreConfigModel;
    
    use Template\models\TemplateModel;
    
    class OnlyOfficeController
    {
    
        public function getConfiguration(Request $request, Response $response)
    
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
    
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false' || empty($loadedXml->onlyoffice->server_uri)) {
    
                return $response->withJson(['enabled' => false]);
            }
    
    
            $coreUrl = str_replace('rest/', '', UrlController::getCoreUrl());
    
    
                'enabled'    => true,
                'serverUri'  => (string)$loadedXml->onlyoffice->server_uri,
                'serverPort' => (int)$loadedXml->onlyoffice->server_port,
                'serverSsl'  => filter_var((string)$loadedXml->onlyoffice->server_ssl, FILTER_VALIDATE_BOOLEAN),
                'coreUrl'    => $coreUrl
    
            ];
    
            return $response->withJson($configurations);
    
        public function getToken(Request $request, Response $response)
    
        {
            $body = $request->getParsedBody();
            if (!Validator::notEmpty()->validate($body['config'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body params config is empty']);
            }
    
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
    
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false' || empty($loadedXml->onlyoffice->server_uri)) {
                return $response->withStatus(400)->withJson(['errors' => 'OnlyOffice server is disabled']);
            }
    
    
            $jwt = null;
            $serverSecret = (string)$loadedXml->onlyoffice->server_secret;
            if (!empty($serverSecret)) {
    
                $header = [
                    "alg" => "HS256",
                    "typ" => "JWT"
                ];
    
    
                $jwt = JWT::encode($body['config'], $serverSecret, 'HS256', null, $header);
    
        public function saveMergedFile(Request $request, Response $response)
    
        {
            $body = $request->getParsedBody();
    
            if (!Validator::stringType()->notEmpty()->validate($body['onlyOfficeKey'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body params onlyOfficeKey is empty']);
    
            } elseif (!preg_match('/[A-Za-z0-9]/i', $body['onlyOfficeKey'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Body params onlyOfficeKey is forbidden']);
    
            }
    
            if ($body['objectType'] == 'templateCreation') {
    
                $customId = CoreConfigModel::getCustomId();
                if (!empty($customId) && is_dir("custom/{$customId}/modules/templates/templates/styles/")) {
                    $stylesPath = "custom/{$customId}/modules/templates/templates/styles/";
                } else {
                    $stylesPath = 'modules/templates/templates/styles/';
                }
                if (strpos($body['objectId'], $stylesPath) !== 0 || substr_count($body['objectId'], '.') != 1) {
                    return $response->withStatus(400)->withJson(['errors' => 'Template path is not valid']);
                }
    
    
                $path = $body['objectId'];
    
                
                $fileContent = @file_get_contents($path);
                if ($fileContent == false) {
                    return $response->withStatus(400)->withJson(['errors' => 'No content found']);
                }
    
            } elseif ($body['objectType'] == 'templateModification') {
                $docserver = DocserverModel::getCurrentDocserver(['typeId' => 'TEMPLATES', 'collId' => 'templates', 'select' => ['path_template']]);
                $template = TemplateModel::getById(['id' => $body['objectId'], 'select' => ['template_path', 'template_file_name']]);
                if (empty($template)) {
                    return $response->withStatus(400)->withJson(['errors' => 'Template does not exist']);
                }
    
                $path = $docserver['path_template'] . str_replace('#', DIRECTORY_SEPARATOR, $template['template_path']) . $template['template_file_name'];
    
                if (!is_file($path)) {
                    return $response->withStatus(400)->withJson(['errors' => 'Template does not exist on docserver']);
                }
    
                $fileContent = file_get_contents($path);
            } elseif ($body['objectType'] == 'resourceCreation' || $body['objectType'] == 'attachmentCreation') {
                $docserver = DocserverModel::getCurrentDocserver(['typeId' => 'TEMPLATES', 'collId' => 'templates', 'select' => ['path_template']]);
                $template = TemplateModel::getById(['id' => $body['objectId'], 'select' => ['template_path', 'template_file_name']]);
                if (empty($template)) {
                    return $response->withStatus(400)->withJson(['errors' => 'Template does not exist']);
                }
    
                $path = $docserver['path_template'] . str_replace('#', DIRECTORY_SEPARATOR, $template['template_path']) . $template['template_file_name'];
    
                $dataToMerge = ['userId' => $GLOBALS['id']];
    
                if (!empty($body['data']) && is_array($body['data'])) {
                    $dataToMerge = array_merge($dataToMerge, $body['data']);
    
                }
                $mergedDocument = MergeController::mergeDocument([
                    'path' => $path,
                    'data' => $dataToMerge
                ]);
                $fileContent = base64_decode($mergedDocument['encodedDocument']);
    
            } elseif ($body['objectType'] == 'resourceModification') {
                if (!ResController::hasRightByResId(['resId' => [$body['objectId']], 'userId' => $GLOBALS['id']])) {
                    return $response->withStatus(400)->withJson(['errors' => 'Resource out of perimeter']);
                }
    
                $resource = ResModel::getById(['resId' => $body['objectId'], 'select' => ['docserver_id', 'path', 'filename', 'fingerprint']]);
    
                if (empty($resource['filename'])) {
                    return $response->withStatus(400)->withJson(['errors' => 'Resource has no file']);
                }
    
    
                $docserver  = DocserverModel::getByDocserverId(['docserverId' => $resource['docserver_id'], 'select' => ['path_template', 'docserver_type_id']]);
    
    
                $path = $docserver['path_template'] . str_replace('#', DIRECTORY_SEPARATOR, $resource['path']) . $resource['filename'];
    
    
                $docserverType = DocserverTypeModel::getById(['id' => $docserver['docserver_type_id'], 'select' => ['fingerprint_mode']]);
                $fingerprint = StoreController::getFingerPrint(['filePath' => $path, 'mode' => $docserverType['fingerprint_mode']]);
                if (empty($resource['fingerprint'])) {
                    ResModel::update(['set' => ['fingerprint' => $fingerprint], 'where' => ['res_id = ?'], 'data' => [$body['objectId']]]);
                    $resource['fingerprint'] = $fingerprint;
                }
    
                if ($resource['fingerprint'] != $fingerprint) {
                    return $response->withStatus(400)->withJson(['errors' => 'Fingerprints do not match']);
                }
    
    
                $fileContent = file_get_contents($path);
    
            } elseif ($body['objectType'] == 'attachmentModification') {
    
                $attachment = AttachmentModel::getById(['id' => $body['objectId'], 'select' => ['docserver_id', 'path', 'filename', 'res_id_master', 'fingerprint']]);
    
                if (empty($attachment)) {
                    return $response->withStatus(400)->withJson(['errors' => 'Attachment does not exist']);
                }
    
                if (!ResController::hasRightByResId(['resId' => [$attachment['res_id_master']], 'userId' => $GLOBALS['id']])) {
                    return $response->withStatus(400)->withJson(['errors' => 'Attachment out of perimeter']);
                }
    
    
                $docserver  = DocserverModel::getByDocserverId(['docserverId' => $attachment['docserver_id'], 'select' => ['path_template', 'docserver_type_id']]);
    
    
                $path = $docserver['path_template'] . str_replace('#', DIRECTORY_SEPARATOR, $attachment['path']) . $attachment['filename'];
    
    
                $docserverType = DocserverTypeModel::getById(['id' => $docserver['docserver_type_id'], 'select' => ['fingerprint_mode']]);
                $fingerprint = StoreController::getFingerPrint(['filePath' => $path, 'mode' => $docserverType['fingerprint_mode']]);
                if (empty($attachment['fingerprint'])) {
                    AttachmentModel::update(['set' => ['fingerprint' => $fingerprint], 'where' => ['res_id = ?'], 'data' => [$body['objectId']]]);
                    $attachment['fingerprint'] = $fingerprint;
                }
    
                if ($attachment['fingerprint'] != $fingerprint) {
                    return $response->withStatus(400)->withJson(['errors' => 'Fingerprints do not match']);
                }
    
    
                $fileContent = file_get_contents($path);
    
            } elseif ($body['objectType'] == 'encodedResource') {
                if (empty($body['format'])) {
                    return $response->withStatus(400)->withJson(['errors' => 'Body format is empty']);
                }
    
                $fileContent = base64_decode($body['objectId']);
    
                $extension   = $body['format'];
    
            } else {
                return $response->withStatus(400)->withJson(['errors' => 'Query param objectType does not exist']);
            }
    
    
            if (empty($extension)) {
                $extension = pathinfo($path, PATHINFO_EXTENSION);
            }
    
            $tmpPath = CoreConfigModel::getTmpPath();
            $filename = "onlyOffice_{$GLOBALS['id']}_{$body['onlyOfficeKey']}.{$extension}";
    
            $put = file_put_contents($tmpPath . $filename, $fileContent);
            if ($put === false) {
                return $response->withStatus(400)->withJson(['errors' => 'File put contents failed']);
            }
    
            $halfFilename = substr($filename, 11);
            return $response->withJson(['filename' => $halfFilename]);
        }
    
    
        public function getMergedFile(Request $request, Response $response)
    
        {
            $queryParams = $request->getQueryParams();
    
            if (!Validator::stringType()->notEmpty()->validate($queryParams['filename'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Query params filename is empty']);
    
            } elseif (substr_count($queryParams['filename'], '\\') > 0 || substr_count($queryParams['filename'], '.') != 1) {
                return $response->withStatus(400)->withJson(['errors' => 'Query params filename forbidden']);
    
            $tmpPath  = CoreConfigModel::getTmpPath();
    
            $filename = "onlyOffice_{$queryParams['filename']}";
    
            $fileContent = file_get_contents($tmpPath . $filename);
            if ($fileContent == false) {
                return $response->withStatus(400)->withJson(['errors' => 'No content found']);
            }
    
    
            $finfo     = new \finfo(FILEINFO_MIME_TYPE);
            $mimeType  = $finfo->buffer($fileContent);
    
            $extension = pathinfo($tmpPath . $filename, PATHINFO_EXTENSION);
    
            unlink($tmpPath . $filename);
    
    
            $response->write($fileContent);
            $response = $response->withAddedHeader('Content-Disposition', "attachment; filename=maarch.{$extension}");
    
            return $response->withHeader('Content-Type', $mimeType);
        }
    
    
        public function getEncodedFileFromUrl(Request $request, Response $response)
    
        {
            $queryParams = $request->getQueryParams();
    
            if (!Validator::stringType()->notEmpty()->validate($queryParams['url'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Query params url is empty']);
            }
    
    
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false' || empty($loadedXml->onlyoffice->server_uri)) {
                return $response->withStatus(400)->withJson(['errors' => 'Onlyoffice is not enabled']);
            }
    
    
            $checkUrl   = str_replace('http://', '', $queryParams['url']);
            $checkUrl   = str_replace('https://', '', $checkUrl);
            $uri        = (string)$loadedXml->onlyoffice->server_uri;
            $uriPaths   = explode('/', $uri, 2);
    
            $masterPath = $uriPaths[0];
    
            $lastPath   = !empty($uriPaths[1]) ? rtrim("/{$uriPaths[1]}", '/') : '';
            $port       = (string)$loadedXml->onlyoffice->server_port;
    
            if (strpos($checkUrl, "{$masterPath}:{$port}{$lastPath}/cache/files/") !== 0 && (($port != 80 && $port != 443) || strpos($checkUrl, "{$masterPath}{$lastPath}/cache/files/") !== 0)) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Query params url is not allowed']);
            }
    
            $fileContent = file_get_contents($queryParams['url']);
            if ($fileContent == false) {
                return $response->withStatus(400)->withJson(['errors' => 'No content found']);
            }
    
            return $response->withJson(['encodedFile' => base64_encode($fileContent)]);
        }
    
        public function isAvailable(Request $request, Response $response)
    
        {
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
    
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false') {
    
                return $response->withStatus(400)->withJson(['errors' => 'Onlyoffice is not enabled', 'lang' => 'onlyOfficeNotEnabled']);
    
            } elseif (empty($loadedXml->onlyoffice->server_uri)) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Onlyoffice server_uri is empty', 'lang' => 'uriIsEmpty']);
    
            } elseif (empty($loadedXml->onlyoffice->server_port)) {
    
                return $response->withStatus(400)->withJson(['errors' => 'Onlyoffice server_port is empty', 'lang' => 'portIsEmpty']);
    
            $uri  = (string)$loadedXml->onlyoffice->server_uri;
    
            $port = (string)$loadedXml->onlyoffice->server_port;
    
            $isAvailable = DocumentEditorController::isAvailable(['uri' => $uri, 'port' => $port]);
    
            if (!empty($isAvailable['errors'])) {
                return $response->withStatus(400)->withJson($isAvailable);
    
            return $response->withJson(['isAvailable' => $isAvailable]);
        }
    
    
        public static function canConvert()
        {
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false') {
                return false;
            } elseif (empty($loadedXml->onlyoffice->server_uri)) {
                return false;
            } elseif (empty($loadedXml->onlyoffice->server_port)) {
                return false;
            }
    
            $uri  = (string)$loadedXml->onlyoffice->server_uri;
            $port = (string)$loadedXml->onlyoffice->server_port;
    
            $isAvailable = DocumentEditorController::isAvailable(['uri' => $uri, 'port' => $port]);
    
            if (!empty($isAvailable['errors'])) {
                return false;
            }
    
            return $isAvailable;
        }
    
        public static function convert(array $args)
        {
            $loadedXml = CoreConfigModel::getXmlLoaded(['path' => 'apps/maarch_entreprise/xml/documentEditorsConfig.xml']);
            if (empty($loadedXml) || empty($loadedXml->onlyoffice->enabled) || $loadedXml->onlyoffice->enabled == 'false') {
                return ['errors' => 'Onlyoffice is not enabled', 'lang' => 'onlyOfficeNotEnabled'];
            } elseif (empty($loadedXml->onlyoffice->server_uri)) {
                return ['errors' => 'Onlyoffice server_uri is empty', 'lang' => 'uriIsEmpty'];
            } elseif (empty($loadedXml->onlyoffice->server_port)) {
                return ['errors' => 'Onlyoffice server_port is empty', 'lang' => 'portIsEmpty'];
            }
    
            $uri  = (string)$loadedXml->onlyoffice->server_uri;
            $port = (string)$loadedXml->onlyoffice->server_port;
    
            $tmpPath = CoreConfigModel::getTmpPath();
            $docInfo = pathinfo($args['fullFilename']);
    
            $payload = [
                'userId'       => $GLOBALS['id'],
                'fullFilename' => $args['fullFilename']
            ];
    
            $jwt = JWT::encode($payload, CoreConfigModel::getEncryptKey());
    
            $file = CoreConfigModel::getJsonLoaded(['path' => 'apps/maarch_entreprise/xml/config.json']);
            if (empty($file['config']['maarchUrl'])) {
                return ['errors' => 'Cannot convert with OnlyOffice : maarchUrl not set in config.json'];
            }
            $coreUrl = $file['config']['maarchUrl'];
            $docUrl = $coreUrl . 'rest/onlyOffice/content?token=' . $jwt;
    
            $response = CurlModel::execSimple([
                'url'     => $uri . ':' . $port . '/ConvertService.ashx',
                'headers' => ['accept: application/json'],
                'method'  => 'POST',
                'body'    => json_encode([
                    'async'      => false,
                    'filetype'   => $docInfo['extension'],
                    'key'        => CoreConfigModel::uniqueId(),
                    'outputtype' => 'pdf',
                    'title'      => $docInfo['filename'] . 'pdf',
                    'url'        => $docUrl
                ])
            ]);
    
            if ($response['code'] != 200) {
                return ['errors' => 'OnlyOffice conversion failed '];
            }
            if (!empty($response['response']['error'])) {
                return ['errors' => 'OnlyOffice conversion failed : ' . $response['response']['error']];
            }
    
            $convertedFile = file_get_contents($response['response']['fileUrl']);
    
            if ($convertedFile === false) {
                return ['errors' => 'Cannot get converted document'];
            }
    
            $filename = $tmpPath . $docInfo['filename'] . '.pdf';
            $saveTmp = file_put_contents($filename, $convertedFile);
            if ($saveTmp == false) {
                return ['errors' => 'Cannot save converted document'];
            }
    
            return true;
        }
    
        public function getTmpFile(Request $request, Response $response)
        {
            $queryParams = $request->getQueryParams();
    
            try {
                $jwt = JWT::decode($queryParams['token'], CoreConfigModel::getEncryptKey(), ['HS256']);
            } catch (\Exception $e) {
                return $response->withStatus(401)->withJson(['errors' => 'Token is invalid']);
            }
    
            CoreController::setGlobals(['userId' => $jwt->userId]);
    
            $fileContent = file_get_contents($jwt->fullFilename);
            if ($fileContent === false) {
                return $response->withStatus(404)->withJson(['errors' => 'Document not found']);
            }
    
            $finfo    = new \finfo(FILEINFO_MIME_TYPE);
            $mimeType = $finfo->buffer($fileContent);
            $pathInfo = pathinfo($jwt->fullFilename);
    
            $response->write($fileContent);
            $response = $response->withAddedHeader('Content-Disposition', "attachment; filename=maarch.{$pathInfo['extension']}");
            return $response->withHeader('Content-Type', $mimeType);
        }