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)) {
$where = ['id in (?)'];
$data = [$documentIds];
if (!empty($queryParams['search'])) {
$where[] = '(reference ilike ? OR translate(title, \'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿŔŕ\', \'aaaaaaaceeeeiiiidnoooooouuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyrr\') ilike translate(?, \'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿŔŕ\', \'aaaaaaaceeeeiiiidnoooooouuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyrr\'))';
$data[] = "%{$queryParams['search']}%";
$data[] = "%{$queryParams['search']}%";
}
$documents = DocumentModel::get([
'select' => ['id', 'title', 'reference', 'count(1) OVER()'],
'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']);
}
'select' => ['count(1)'],
'where' => ['main_document_id = ?', 'type != ?'],
'data' => [$args['id'], 'DOC']
]);
if (empty($adr[0]['count'])) {
return $response->withStatus(400)->withJson(['errors' => 'Document thumbnails do 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'],
'pages' => $adr[0]['count']
];
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' => ['id', 'firstname', 'lastname'], 'id' => $value['user_id']]);
$formattedDocument['workflow'][] = [
'userDisplay' => "{$user['firstname']} {$user['lastname']}",
'mode' => $value['mode'],
'processDate' => $value['process_date'],
'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) {
$pagesCount = 0;
$adr = AdrModel::getAttachmentsAdr([
'select' => ['count(1)'],
'where' => ['attachment_id = ?', 'type != ?'],
'data' => [$attachment['id'], 'ATTACH']
if (!empty($adr[0]['count'])) {
$pagesCount = $adr[0]['count'];
$formattedDocument['attachments'][] = [
'id' => $attachment['id'],
'title' => $attachment['title'],
'pages' => $pagesCount
'code' => 'OK',
'objectType' => 'main_documents',
'objectId' => $args['id'],
'type' => 'VIEW',
return $response->withJson(['document' => $formattedDocument]);
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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) {
$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' => ['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'])) {
return $response->withStatus(400)->withJson(['errors' => 'Docserver does not exist']);
}
$pathToThumbnail = $docserver['path'] . $adr[0]['path'] . $adr[0]['filename'];
if (!is_file($pathToThumbnail) || !is_readable($pathToThumbnail)) {
return $response->withStatus(400)->withJson(['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']);
}
$base64Content = base64_encode($fileContent);
return $response->withJson(['fileContent' => $base64Content]);
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"];
}