Newer
Older
<?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 Firebase\JWT\JWT;
use Resource\controllers\ResController;
use Resource\controllers\StoreController;
use Resource\models\ResModel;
use Respect\Validation\Validator;
use Slim\Http\Request;
use Slim\Http\Response;
Guillaume Heurtier
committed
use SrcCore\controllers\CoreController;
use SrcCore\controllers\UrlController;
use SrcCore\models\CoreConfigModel;
Guillaume Heurtier
committed
use SrcCore\models\CurlModel;
Guillaume Heurtier
committed
use SrcCore\models\ValidatorModel;
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());
$configurations = [
'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);
return $response->withJson($jwt);
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']);
}
$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']);
}
$path = null;
$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);
$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;
Guillaume Heurtier
committed
$isAvailable = DocumentEditorController::isAvailable(['uri' => $uri, 'port' => $port]);
Guillaume Heurtier
committed
if (!empty($isAvailable['errors'])) {
return $response->withStatus(400)->withJson($isAvailable);
}
return $response->withJson(['isAvailable' => $isAvailable]);
}
Guillaume Heurtier
committed
Guillaume Heurtier
committed
public static function canConvert(array $args)
Guillaume Heurtier
committed
{
Guillaume Heurtier
committed
ValidatorModel::notEmpty($args, ['url']);
ValidatorModel::stringType($args, ['url']);
Guillaume Heurtier
committed
$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;
}
Guillaume Heurtier
committed
if (!$isAvailable) {
return false;
}
Guillaume Heurtier
committed
if (strpos($args['url'], 'localhost') !== false || strpos($args['url'], '127.0.0.1') !== false ) {
Guillaume Heurtier
committed
return false;
}
return true;
Guillaume Heurtier
committed
}
public static function convert(array $args)
{
Guillaume Heurtier
committed
ValidatorModel::notEmpty($args, ['url', 'fullFilename', 'userId']);
ValidatorModel::stringType($args, ['url', 'fullFilename']);
ValidatorModel::intVal($args, ['userId']);
Guillaume Heurtier
committed
$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 = [
Guillaume Heurtier
committed
'userId' => $args['userId'],
Guillaume Heurtier
committed
'fullFilename' => $args['fullFilename']
];
$jwt = JWT::encode($payload, CoreConfigModel::getEncryptKey());
Guillaume Heurtier
committed
$docUrl = $args['url'] . 'rest/onlyOffice/content?token=' . $jwt;
Guillaume Heurtier
committed
$body = [
'async' => false,
'filetype' => $docInfo['extension'],
'key' => CoreConfigModel::uniqueId(),
'outputtype' => 'pdf',
'title' => $docInfo['filename'] . 'pdf',
'url' => $docUrl
];
$serverSecret = (string)$loadedXml->onlyoffice->server_secret;
$serverAuthorizationHeader = (string)$loadedXml->onlyoffice->server_authorization_header;
$serverSsl = filter_var((string)$loadedXml->onlyoffice->server_ssl, FILTER_VALIDATE_BOOLEAN);
$uri = explode("/", $uri);
$domain = $uri[0];
$path = array_slice($uri, 1);
$path = implode("/", $path);
if (!empty($serverSsl)) {
$convertUrl = 'https://';
} else {
$convertUrl = 'http://';
}
$convertUrl .= $domain;
if ($port != 80) {
$convertUrl .= ":{$port}";
}
if (!empty($path)) {
$convertUrl .= '/' . $path;
}
if (substr($convertUrl, -1) != '/') {
$convertUrl .= '/';
}
$convertUrl .= 'ConvertService.ashx';
$headers = [
'Accept: application/json',
'Content-Type: application/json'
];
if (!empty($serverSecret)) {
$header = [
"alg" => "HS256",
"typ" => "JWT"
];
$tokenOnlyOffice = JWT::encode($body, $serverSecret, 'HS256', null, $header);
$authorizationHeader = empty($serverAuthorizationHeader) ? 'Authorization' : $serverAuthorizationHeader;
$authorizationHeader .= ': Bearer ' . $tokenOnlyOffice;
$headers[] = $authorizationHeader;
Guillaume Heurtier
committed
$response = CurlModel::execSimple([
'url' => $convertUrl,
'headers' => $headers,
Guillaume Heurtier
committed
'method' => 'POST',
'body' => json_encode($body)
Guillaume Heurtier
committed
]);
if ($response['code'] != 200) {
return ['errors' => 'OnlyOffice conversion failed'];
Guillaume Heurtier
committed
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
}
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']);
}
if (!file_exists($jwt->fullFilename)) {
return $response->withStatus(404)->withJson(['errors' => 'Document not found']);
}
Guillaume Heurtier
committed
$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);
}