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 Resource Controller
* @author dev@maarch.org
*/
namespace Document\controllers;
use Attachment\controllers\AttachmentController;
use Email\controllers\EmailController;
use setasign\Fpdi\Tcpdf\Fpdi;
use Document\models\DocumentModel;
use Slim\Http\Request;
use Slim\Http\Response;
use SrcCore\models\DatabaseModel;
use User\controllers\UserController;
use History\controllers\HistoryController;
const ACTIONS = [1 => 'VAL', 2 => 'REF'];
const MODES = ['visa', 'sign', 'note'];
$queryParams = $request->getQueryParams();
$queryParams['offset'] = empty($queryParams['offset']) ? 0 : (int)$queryParams['offset'];
$queryParams['limit'] = empty($queryParams['limit']) ? 0 : (int)$queryParams['limit'];
$workflowSelect = "SELECT id FROM workflows ws WHERE workflows.main_document_id = main_document_id AND process_date IS NULL AND status IS NULL ORDER BY \"order\" LIMIT 1";
$where = ['user_id = ?', "(id) in ({$workflowSelect})"];
$data = [$GLOBALS['id']];
if (!empty($queryParams['mode']) && in_array($queryParams['mode'], DocumentController::MODES)) {
$where[] = 'mode = ?';
$data[] = $queryParams['mode'];
$workflows = WorkflowModel::get([
'select' => ['main_document_id', 'mode'],
'where' => $where,
'data' => $data
$documentIds = [];
$workflowsShortcut = [];
foreach ($workflows as $workflow) {
$documentIds[] = $workflow['main_document_id'];
$workflowsShortcut[$workflow['main_document_id']] = $workflow['mode'];
}
$documents = [];
if (!empty($documentIds)) {
$documents = DocumentModel::get([
'select' => ['id', 'title', 'reference', 'count(1) OVER()'],
'where' => ['id in (?)'],
'data' => [$documentIds],
'limit' => $queryParams['limit'],
'offset' => $queryParams['offset'],
'orderBy' => ['creation_date desc']
]);
}
$count = empty($documents[0]['count']) ? 0 : $documents[0]['count'];
foreach ($documents as $key => $document) {
unset($documents[$key]['count']);
$documents[$key]['mode'] = $workflowsShortcut[$document['id']];
return $response->withJson(['documents' => $documents, 'count' => $count]);
public function getById(Request $request, Response $response, array $args)
{
if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']]) && !UserController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents'])) {
return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
}
$document = DocumentModel::getById(['select' => ['*'], 'id' => $args['id']]);
if (empty($document)) {
return $response->withStatus(400)->withJson(['errors' => 'Document does not exist']);
}
$formattedDocument = [
'id' => $document['id'],
'title' => $document['title'],
'reference' => $document['reference'],
'description' => $document['description'],
'sender' => $document['sender'],
'creationDate' => $document['creation_date'],
'modificationDate' => $document['modification_date']
];
if (!empty($document['deadline'])) {
$date = new \DateTime($document['deadline']);
$formattedDocument['deadline'] = $date->format('d-m-Y H:i');
}
$creator = UserModel::getById(['select' => ['firstname', 'lastname', 'login'], 'id' => $document['creator']]);
$formattedDocument['creator'] = $creator['login'];
$formattedDocument['creatorDisplay'] = "{$creator['firstname']} {$creator['lastname']}";
$formattedDocument['metadata'] = [];
$metadata = json_decode($document['metadata'], true);
if (is_array($metadata)) {
foreach ($metadata as $key => $value) {
$formattedDocument['metadata'][] = ['label' => $key, 'value' => $value];
}
$workflow = WorkflowModel::getByDocumentId(['select' => ['user_id', 'mode', 'process_date'], 'documentId' => $args['id'], 'orderBy' => ['"order"']]);
$currentFound = false;
foreach ($workflow as $value) {
if (!empty($value['process_date'])) {
$date = new \DateTime($document['process_date']);
$value['process_date'] = $date->format('d-m-Y H:i');
}
$user = UserModel::getById(['select' => ['firstname', 'lastname', 'picture'], 'id' => $value['user_id']]);
$formattedDocument['workflow'][] = [
'userDisplay' => "{$user['firstname']} {$user['lastname']}",
'mode' => $value['mode'],
'processDate' => $value['process_date'],
'userPicture' => $user['picture'],
'current' => !$currentFound && empty($value['process_date'])
];
if (empty($value['process_date'])) {
$currentFound = true;
}
}
$attachments = AttachmentModel::getByDocumentId(['select' => ['id', 'title'], 'documentId' => $args['id']]);
foreach ($attachments as $attachment) {
$formattedDocument['attachments'][] = [
'id' => $attachment['id'],
'title' => $attachment['title']
];
'code' => 'OK',
'objectType' => 'main_documents',
'objectId' => $args['id'],
'type' => 'VIEW',
return $response->withJson(['document' => $formattedDocument]);
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public function getContent(Request $request, Response $response, array $args)
{
if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']]) && !UserController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents'])) {
return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
}
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename', 'fingerprint'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'DOC']
]);
if (empty($adr[0])) {
return $response->withJson(['encodedDocument' => null]);
}
$docserver = DocserverModel::getByType(['type' => 'DOC', 'select' => ['path']]);
if (empty($docserver['path']) || !file_exists($docserver['path'])) {
return $response->withStatus(400)->withJson(['errors' => 'Docserver does not exist']);
}
$pathToDocument = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
if (!is_file($pathToDocument)) {
return $response->withStatus(404)->withJson(['errors' => 'Document not found on docserver']);
}
$fingerprint = DocserverController::getFingerPrint(['path' => $pathToDocument]);
if ($adr[0]['fingerprint'] != $fingerprint) {
return $response->withStatus(400)->withJson(['errors' => 'Fingerprints do not match']);
}
$document = DocumentModel::getById(['select' => ['title'], 'id' => $args['id']]);
HistoryController::add([
'code' => 'OK',
'objectType' => 'main_documents',
'objectId' => $args['id'],
'type' => 'VIEW',
'message' => "{documentViewed} : {$document['title']}"
]);
return $response->withJson(['encodedDocument' => base64_encode(file_get_contents($pathToDocument))]);
}
public function create(Request $request, Response $response)
{
if (!UserController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents'])) {
return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
}
$body = $request->getParsedBody();
if (empty($body)) {
return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
} elseif (!Validator::notEmpty()->validate($body['encodedDocument'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body encodedDocument is empty']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['title'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body title is empty or not a string']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['sender'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body sender is empty or not a string']);
} elseif (!Validator::arrayType()->notEmpty()->validate($body['workflow'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body workflow is empty or not an array']);
$body['attachments'] = empty($body['attachments']) ? [] : $body['attachments'];
foreach ($body['attachments'] as $key => $attachment) {
if (!Validator::notEmpty()->validate($attachment['encodedDocument'])) {
return $response->withStatus(400)->withJson(['errors' => "Body attachments[{$key}] encodedDocument is empty"]);
} elseif (!Validator::stringType()->notEmpty()->validate($attachment['title'])) {
return $response->withStatus(400)->withJson(['errors' => "Body attachments[{$key}] title is empty"]);
foreach ($body['workflow'] as $key => $workflow) {
$processingUser = UserModel::getByLogin(['select' => ['id'], 'login' => $workflow['processingUser']]);
if (empty($processingUser)) {
return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] processingUser is empty or does not exist"]);
} elseif (!Validator::stringType()->notEmpty()->validate($workflow['mode']) || !in_array($workflow['mode'], DocumentController::MODES)) {
return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] mode is empty or not a string in ('visa', 'sign', 'note')"]);
}
$body['workflow'][$key]['userId'] = $processingUser['id'];
}
$encodedDocument = DocumentController::getEncodedDocumentFromEncodedZip(['encodedZipDocument' => $body['encodedDocument']]);
if (!empty($encodedDocument['errors'])) {
return $response->withStatus(500)->withJson(['errors' => $encodedDocument['errors']]);
}
$storeInfos = DocserverController::storeResourceOnDocServer([
'encodedFile' => $encodedDocument['encodedDocument'],
'format' => 'pdf',
'docserverType' => 'DOC'
]);
if (!empty($storeInfos['errors'])) {
return $response->withStatus(500)->withJson(['errors' => $storeInfos['errors']]);
}
DatabaseModel::beginTransaction();
$id = DocumentModel::create([
'title' => $body['title'],
'reference' => empty($body['reference']) ? null : $body['reference'],
'description' => empty($body['description']) ? null : $body['description'],
'sender' => $body['sender'],
'deadline' => empty($body['deadline']) ? null : $body['deadline'],
'metadata' => empty($body['metadata']) ? '{}' : json_encode($body['metadata']),
'creator' => $GLOBALS['id']
]);
AdrModel::createDocumentAdr([
'documentId' => $id,
'type' => 'DOC',
'path' => $storeInfos['path'],
'filename' => $storeInfos['filename'],
'fingerprint' => $storeInfos['fingerprint']
]);
foreach ($body['workflow'] as $key => $workflow) {
WorkflowModel::create([
'userId' => $workflow['userId'],
'mainDocumentId' => $id,
'mode' => $workflow['mode'],
'order' => $key + 1
]);
}
foreach ($body['attachments'] as $key => $value) {
$value['mainDocumentId'] = $id;
$attachment = AttachmentController::create($value);
if (!empty($attachment['errors'])) {
DatabaseModel::rollbackTransaction();
return $response->withStatus(500)->withJson(['errors' => "An error occured for attachment {$key} : {$attachment['errors']}"]);
}
}
'code' => 'OK',
'objectType' => 'main_documents',
'objectId' => $id,
'type' => 'CREATION',
DatabaseModel::commitTransaction();
EmailController::sendNotificationToNextUserInWorkflow(['documentId' => $id, 'userId' => $GLOBALS['id']]);
$configPath = CoreConfigModel::getConfigPath();
exec("php src/app/convert/scripts/ThumbnailScript.php '{$configPath}' {$id} 'document' '{$GLOBALS['id']}' > /dev/null &");
return $response->withJson(['id' => $id]);
public function setAction(Request $request, Response $response, array $args)
if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
}
if (empty(DocumentController::ACTIONS[$args['actionId']])) {
return $response->withStatus(400)->withJson(['errors' => 'Action does not exist']);
$workflow = WorkflowModel::getCurrentStep(['select' => ['id', 'mode'], 'documentId' => $args['id']]);
$body = $request->getParsedBody();
if (!empty($body['signatures'])) {
foreach ($body['signatures'] as $signature) {
foreach (['encodedImage', 'width', 'positionX', 'positionY', 'page', 'type'] as $value) {
if (!isset($signature[$value])) {
return $response->withStatus(400)->withJson(['errors' => $value . ' is empty']);
}
}
}
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'DOC']
]);
if (empty($adr)) {
return $response->withStatus(400)->withJson(['errors' => 'Document does not exist']);
}
$docserver = DocserverModel::getByType(['type' => 'DOC', 'select' => ['path']]);
if (empty($docserver['path']) || !file_exists($docserver['path'])) {
return $response->withStatus(400)->withJson(['errors' => 'Docserver does not exist']);
}
$pathToDocument = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
if (!is_file($pathToDocument) || !is_readable($pathToDocument)) {
return $response->withStatus(404)->withJson(['errors' => 'Document not found on docserver or not readable']);
$tmpFilename = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_' . $adr[0]['filename'];
copy($pathToDocument, $tmpFilename);
$pdf = new Fpdi('P');
$nbPages = $pdf->setSourceFile($tmpFilename);
$pdf->setPrintHeader(false);
for ($i = 1; $i <= $nbPages; $i++) {
$page = $pdf->importPage($i);
$size = $pdf->getTemplateSize($page);
$pdf->AddPage($size['orientation'], $size);
$pdf->useImportedPage($page);
$pdf->SetAutoPageBreak(false, 0);
$pdf->SetMargins(0, 0, 0);
$pdf->SetAutoPageBreak(false, 0);
foreach ($body['signatures'] as $signature) {
if ($signature['positionX'] == 0 && $signature['positionY'] == 0) {
$signWidth = $size['width']*2;
$signPosX = 0;
$signPosY = 0;
} else {
$signWidth = $size['width'] / 4;
$signPosX = ($signature['positionX'] * $size['width']) / 100;
$signPosY = ($signature['positionY'] * $size['height']) / 100;
}
$image = str_replace('data:image/svg+xml;base64,', '', $signature['encodedImage']);
$image = base64_decode($image);
if ($image === false) {
return $response->withStatus(400)->withJson(['errors' => 'base64_decode failed']);
$imageTmpPath = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_writing.svg';
file_put_contents($imageTmpPath, $image);
$pdf->ImageSVG($imageTmpPath, $signPosX, $signPosY, $signWidth);
if ($image === false) {
return $response->withStatus(400)->withJson(['errors' => 'base64_decode failed']);
}
$imageTmpPath = $tmpPath . $GLOBALS['id'] . '_' . rand() . '_writing.png';
file_put_contents($imageTmpPath, $image);
$pdf->Image($imageTmpPath, $signPosX, $signPosY, $signWidth);
}
if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && $workflow['mode'] == 'sign') {

Florian Azizian
committed
$loadedXml = CoreConfigModel::getConfig();
if ($loadedXml->electronicSignature->enable == 'true') {
$certPath = realpath((string)$loadedXml->electronicSignature->certPath);
$privateKeyPath = realpath((string)$loadedXml->electronicSignature->privateKeyPath);
if (is_file($certPath) && is_file($privateKeyPath)) {
$certificate = 'file://' . $certPath;
$privateKey = 'file://' . $privateKeyPath;
$info = [
'Name' => (string)$loadedXml->electronicSignature->certInfo->name,
'Location' => (string)$loadedXml->electronicSignature->certInfo->location,
'Reason' => (string)$loadedXml->electronicSignature->certInfo->reason,
'ContactInfo' => (string)$loadedXml->electronicSignature->certInfo->contactInfo
];
$pdf->setSignature($certificate, $privateKey, (string)$loadedXml->electronicSignature->password, '', 2, $info);
} else {
return $response->withStatus(400)->withJson(['errors' => 'certPath or privateKeyPath is not valid']);
}
$fileContent = $pdf->Output('', 'S');
$storeInfos = DocserverController::storeResourceOnDocServer([
'encodedFile' => base64_encode($fileContent),
'format' => 'pdf',
if (!empty($storeInfos['errors'])) {
return $response->withStatus(500)->withJson(['errors' => $storeInfos['errors']]);
}
unlink($pathToDocument);
AdrModel::deleteDocumentAdr([
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'DOC']
]);
'documentId' => $args['id'],
'type' => 'DOC',
'path' => $storeInfos['path'],
'filename' => $storeInfos['filename'],
'fingerprint' => $storeInfos['fingerprint']
$set = ['process_date' => 'CURRENT_TIMESTAMP', 'status' => DocumentController::ACTIONS[$args['actionId']]];
if (!empty($body['note'])) {
$set['note'] = $body['note'];
}
if (DocumentController::ACTIONS[$args['actionId']] == 'REF') {
WorkflowModel::update([
'set' => ['process_date' => 'CURRENT_TIMESTAMP', 'status' => 'END'],
'where' => ['main_document_id = ?', 'process_date is null'],
'data' => [$args['id']]
]);
}
EmailController::sendNotificationToNextUserInWorkflow(['documentId' => $args['id'], 'userId' => $GLOBALS['id']]);
'code' => 'OK',
'objectType' => 'main_documents',
'objectId' => $args['id'],
'type' => 'ACTION',
'message' => "{actionDone} : " . DocumentController::ACTIONS[$args['actionId']],
]);
return $response->withJson(['success' => 'success']);
}
public function getThumbnailContent(Request $request, Response $response, array $args)
{
if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id']])) {
return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
}
$adr = AdrModel::getDocumentsAdr([
'select' => ['path', 'filename'],
'where' => ['main_document_id = ?', 'type = ?'],
'data' => [$args['id'], 'TNL' . $args['page']]
]);
$docserver = DocserverModel::getByType(['type' => 'DOC', 'select' => ['path']]);
if (empty($docserver['path']) || !file_exists($docserver['path'])) {
['errors' => 'Docserver does not exist'];
}
$pathToThumbnail = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
if (!is_file($pathToThumbnail) || !is_readable($pathToThumbnail)) {
return ['errors' => 'Document not found on docserver or not readable'];
}
$fileContent = file_get_contents($pathToThumbnail);
if ($fileContent === false) {
return $response->withStatus(404)->withJson(['errors' => 'Thumbnail not found on docserver']);
}
$finfo = new \finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->buffer($fileContent);
$pathInfo = pathinfo($pathToThumbnail);
$response->write($fileContent);
$response = $response->withAddedHeader('Content-Disposition', "inline; filename=maarch.{$pathInfo['extension']}");
return $response->withHeader('Content-Type', $mimeType);
}
public static function hasRightById(array $args)
{
ValidatorModel::notEmpty($args, ['id', 'userId']);
ValidatorModel::intVal($args, ['id', 'userId']);
$workflow = WorkflowModel::getCurrentStep(['select' => ['user_id'], 'documentId' => $args['id']]);
if (empty($workflow) || $workflow['user_id'] != $args['userId']) {
return false;
}
return true;
}
public static function getEncodedDocumentFromEncodedZip(array $args)
{
ValidatorModel::notEmpty($args, ['encodedZipDocument']);
ValidatorModel::stringType($args, ['encodedZipDocument']);
$tmpPath = CoreConfigModel::getTmpPath();
$zipDocumentOnTmp = $tmpPath . mt_rand() . '_parapheur.zip';
file_put_contents($zipDocumentOnTmp, base64_decode($args['encodedZipDocument']));
$zipArchive = new \ZipArchive();
$open = $zipArchive->open($zipDocumentOnTmp);
if ($open != true) {
return ['errors' => "getDocumentFromEncodedZip : $open"];
}
$dirOnTmp = $tmpPath . mt_rand() . '_parapheur';
return ['errors' => "getDocumentFromEncodedZip : Extract failed"];
}
$filesOnTmp = scandir($dirOnTmp);
foreach ($filesOnTmp as $fileOnTmp) {
if ($fileOnTmp != '.' && $fileOnTmp != '..') {
$base64Content = base64_encode(file_get_contents("{$dirOnTmp}/{$fileOnTmp}"));
unlink($zipDocumentOnTmp);
return ['encodedDocument' => $base64Content];
}
}
return ['errors' => "getDocumentFromEncodedZip : No document was found in Zip"];
}