Skip to content
Snippets Groups Projects
DocumentController.php 82.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Damien's avatar
    Damien committed
    <?php
    
    /**
    
    * Copyright Maarch since 2008 under license.
    * See LICENSE.txt file at the root folder for more details.
    
    Damien's avatar
    Damien committed
    * This file is part of Maarch software.
    *
    */
    
    /**
    * @brief Resource Controller
    * @author dev@maarch.org
    */
    
    namespace Document\controllers;
    
    
    use Attachment\controllers\AttachmentController;
    
    use Configuration\models\ConfigurationModel;
    
    use Docserver\models\AdrModel;
    
    use Email\controllers\EmailController;
    
    use Group\controllers\PrivilegeController;
    
    use Notification\models\NotificationStackModel;
    
    Damien's avatar
    Damien committed
    use Respect\Validation\Validator;
    
    use setasign\Fpdi\Tcpdf\Fpdi;
    
    use SrcCore\controllers\UrlController;
    
    use SrcCore\models\CoreConfigModel;
    
    Damien's avatar
    Damien committed
    use Attachment\models\AttachmentModel;
    
    Damien's avatar
    Damien committed
    use Docserver\controllers\DocserverController;
    
    use Docserver\models\DocserverModel;
    
    Damien's avatar
    Damien committed
    use Document\models\DocumentModel;
    use Slim\Http\Request;
    use Slim\Http\Response;
    
    use SrcCore\models\DatabaseModel;
    
    use SrcCore\models\ValidatorModel;
    
    use User\controllers\SignatureController;
    
    Damien's avatar
    Damien committed
    use User\models\UserModel;
    
    use History\controllers\HistoryController;
    
    use Workflow\controllers\YousignController;
    use Workflow\models\WorkflowExternalInformationModel;
    
    use Workflow\models\WorkflowModel;
    
    Damien's avatar
    Damien committed
    
    class DocumentController
    {
    
        const ACTIONS = [1 => 'VAL', 2 => 'REF'];
        const MODES   = ['visa', 'sign', 'note'];
    
    Damien's avatar
    Damien committed
        public function get(Request $request, Response $response)
        {
    
            $queryParams = $request->getQueryParams();
    
    Damien's avatar
    Damien committed
    
    
            $queryParams['offset'] = empty($queryParams['offset']) ? 0 : (int)$queryParams['offset'];
            $queryParams['limit'] = empty($queryParams['limit']) ? 0 : (int)$queryParams['limit'];
    
    Damien's avatar
    Damien committed
    
    
            $userId = $GLOBALS['id'];
            if (!empty($queryParams['userId'])) {
    
    Damien's avatar
    Damien committed
                if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents'])) {
    
                    return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
                }
    
    Damien's avatar
    Damien committed
                if (!Validator::intVal()->notEmpty()->validate($queryParams['userId'])) {
                    return $response->withStatus(400)->withJson(['errors' => 'QueryParams userId is not an integer']);
                }
    
                $userId = $queryParams['userId'];
            }
    
            $substitutedUsers = UserModel::get(['select' => ['id'], 'where' => ['substitute = ?'], 'data' => [$userId]]);
    
            $users = [$userId];
    
            foreach ($substitutedUsers as $value) {
                $users[] = $value['id'];
            }
    
    
            $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 in (?)', "(id) in ({$workflowSelect})"];
            $data = [$users];
    
            $countWorkflows = WorkflowModel::get([
                'select'    => ['count(mode)', 'mode'],
                'where'     => $where,
                'data'      => $data,
                'groupBy'   => ['mode']
            ]);
    
    
            if (!empty($queryParams['mode']) && in_array($queryParams['mode'], DocumentController::MODES)) {
    
                $where[] = 'mode = ?';
                $data[] = $queryParams['mode'];
    
    
            $workflows = WorkflowModel::get([
    
                'select'    => ['main_document_id', 'mode', 'user_id'],
    
                'where'     => $where,
                'data'      => $data
    
            $documentIds = [];
            $workflowsShortcut = [];
            foreach ($workflows as $workflow) {
                $documentIds[] = $workflow['main_document_id'];
    
                $workflowsShortcut[$workflow['main_document_id']] = $workflow;
    
            }
    
            $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', 'mailing_id as "mailingId"', 'sender', 'creation_date as "creationDate"', 'count(1) OVER()'],
    
                    'where'     => $where,
                    'data'      => $data,
    
                    'limit'     => $queryParams['limit'],
                    'offset'    => $queryParams['offset'],
                    'orderBy'   => ['creation_date desc']
                ]);
            }
    
    Damien's avatar
    Damien committed
    
    
            $count = ['visa' => 0, 'sign' => 0, 'note' => 0, 'current' => empty($documents[0]['count']) ? 0 : $documents[0]['count']];
    
            foreach ($documents as $key => $document) {
                unset($documents[$key]['count']);
    
                $documents[$key]['mode'] = $workflowsShortcut[$document['id']]['mode'];
    
                $documents[$key]['owner'] = $workflowsShortcut[$document['id']]['user_id'] == $userId;
    
            foreach ($countWorkflows as $mode) {
                $count[$mode['mode']] = $mode['count'];
            }
    
            return $response->withJson(['documents' => $documents, 'count' => $count]);
    
    Damien's avatar
    Damien committed
        }
    
    
        public function getById(Request $request, Response $response, array $args)
        {
    
            $canManageDocuments = PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents']);
            if (!$canManageDocuments && !DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id'], 'readOnly' => true])) {
                return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
            }
    
    
            if (!DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id'], 'readOnly' => true]) && !PrivilegeController::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']);
            }
    
    
            $adr = AdrModel::getDocumentsAdr([
    
                'select'  => ['count(1)'],
    
                'where'   => ['main_document_id = ?', 'type != ?', 'type != ?', 'type != ?'],
                'data'    => [$args['id'], 'DOC', 'ESIGN', 'ORIGINAL']
    
            if (empty($adr[0]['count']) && $document['status'] != 'CONVERTING') {
    
                $configPath = CoreConfigModel::getConfigPath();
                exec("php src/app/convert/scripts/ThumbnailScript.php '{$configPath}' {$args['id']} 'document' '{$GLOBALS['id']}' > /dev/null");
    
            $formattedDocument = [
                'id'                => $document['id'],
                'title'             => $document['title'],
                'reference'         => $document['reference'],
                'description'       => $document['description'],
                'sender'            => $document['sender'],
    
                'notes'             => !empty($document['notes']) ? json_decode($document['notes'], true) : null,
    
                'creationDate'      => $document['creation_date'],
    
                'modificationDate'  => $document['modification_date'],
    
                'status'            => $document['status'],
                'mailingId'         => $document['mailing_id']
    
            ];
            if (!empty($document['deadline'])) {
                $date = new \DateTime($document['deadline']);
                $formattedDocument['deadline'] = $date->format('d-m-Y H:i');
            }
    
    
    Damien's avatar
    Damien committed
            $formattedDocument['metadata'] = [];
            $metadata = json_decode($document['metadata'], true);
    
    Damien's avatar
    Damien committed
            if (is_array($metadata)) {
                foreach ($metadata as $key => $value) {
                    $formattedDocument['metadata'][] = ['label' => $key, 'value' => $value];
                }
    
            $workflow = WorkflowModel::getByDocumentId(['select' => ['id', 'user_id', 'mode', 'process_date', 'signature_mode', 'status', 'note', 'signature_positions', 'date_positions', 'delegate'], 'documentId' => $args['id'], 'orderBy' => ['"order"']]);
    
            $currentFound = false;
    
            foreach ($workflow as $value) {
                if (!empty($value['process_date'])) {
    
                    $date = new \DateTime($value['process_date']);
    
                    $value['process_date'] = $date->format('d-m-Y H:i');
                }
    
                $userLabel = '';
                if (!empty($value['user_id'])) {
                    $userSignaturesModes = UserModel::getById(['id' => $value['user_id'], 'select' => ['signature_modes']]);
                    $userLabel = UserModel::getLabelledUserById(['id' => $value['user_id']]);
                }
    
    
                $workflowExternalInformations = WorkflowExternalInformationModel::getByWorkflowId(['select' => ['*'], 'workflowId' => $value['id']]);
                if (!empty($workflowExternalInformations)) {
                    $userLabel = "{$workflowExternalInformations['firstname']} {$workflowExternalInformations['lastname']}";
                    $workflowExternalInformations['informations'] = json_decode($workflowExternalInformations['informations'], true);
    
                    unset($workflowExternalInformations['informations']['yousignFileId'], $workflowExternalInformations['informations']['yousignProcedureId']);
    
                $noteCreator = null;
                if (!empty($value['note'])) {
                    $noteCreatorId = !empty($value['delegate']) ? $value['delegate'] : $value['user_id'];
                    $noteCreator   = UserModel::getLabelledUserById(['id' => $noteCreatorId]);
                }
    
    
                $formattedDocument['workflow'][] = [
    
                    'userId'                => $value['user_id'],
    
                    'mode'                  => $value['mode'],
                    'processDate'           => $value['process_date'],
    
                    'current'               => !$currentFound && empty($value['status']),
    
                    'signatureMode'         => $value['signature_mode'],
    
                    'signaturePositions'    => json_decode($value['signature_positions'], true),
    
                    'datePositions'         => json_decode($value['date_positions'], true),
    
                    'userSignatureModes'    => !empty($userSignaturesModes['signature_modes']) ? json_decode($userSignaturesModes['signature_modes'], true) : [],
    
                    'noteCreator'           => $noteCreator,
    
                    'status'                => $value['status'],
                    'externalInformations'  => $workflowExternalInformations
    
                if (!$currentId && empty($value['status'])) {
    
                    $currentFound = true;
    
            $formattedDocument['readOnly'] = !$canManageDocuments;
            if ($formattedDocument['readOnly'] && !empty($currentId)) {
                if ($currentId == $GLOBALS['id']) {
                    $formattedDocument['readOnly'] = false;
                } else {
                    $substitute = UserModel::getById(['id' => $currentId, 'select' => ['substitute']]);
                    $substitute = $substitute['substitute'] ?? null;
                    $formattedDocument['readOnly'] = $GLOBALS['id'] != $substitute;
                }
    
    Damien's avatar
    Damien committed
            $formattedDocument['attachments'] = [];
    
            $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
    
            if (!empty($document['link_id'])) {
                $formattedDocument['linkedDocuments'] = DocumentController::getLinkedDocuments(['id' => $args['id'], 'userId' => $GLOBALS['id'], 'linkId' => $document['link_id']]);
            }
    
    
    Damien's avatar
    Damien committed
            HistoryController::add([
    
    Damien's avatar
    Damien committed
                'code'          => 'OK',
                'objectType'    => 'main_documents',
                'objectId'      => $args['id'],
                'type'          => 'VIEW',
    
    Damien's avatar
    Damien committed
                'message'       => "{documentViewed} : {$document['title']}"
    
            return $response->withJson(['document' => $formattedDocument]);
    
        public function delete(Request $request, Response $response, array $args)
        {
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'can_purge'])) {
                return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
            }
    
    
            $lastStep = WorkflowModel::get([
                'select'  => ['process_date'],
                'where'   => ['main_document_id = ?'],
                'data'    => [$args['id']],
                'orderBy' => ['"order" desc'],
                'limit'   => 1
            ]);
            if (!empty($lastStep[0]) && empty($lastStep[0]['process_date'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Document workflow is still ongoing']);
    
            $document = DocumentModel::getById([
                'select' => ['title'],
                'id'     => $args['id']
            ]);
            if (empty($document['title'])) {
                return $response->withStatus(500)->withJson(['errors' => 'No document associated with this workflow']);
            }
    
            $queryParams['physicalPurge'] = filter_var($queryParams['physicalPurge'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
            if (!Validator::boolType()->validate($queryParams['physicalPurge'])) {
    
                return $response->withStatus(400)->withJson(['errors' => 'physicalPurge is not a boolean']);
    
            if($queryParams['physicalPurge']) {
    
                $docServers = DocserverModel::get(['select' => ['type', 'path']]);
    
                if (empty($docServers)) {
                    return $response->withStatus(500)->withJson(['errors' => 'No available Docserver']);
                }
    
    
                $filePaths = AdrModel::getDocumentsAdr([
    
                    'select'=> ['CONCAT(path, filename) as path', 'type'],
                    'where' => ['main_document_id = ?'],
                    'data'  => [$args['id']]
    
                    return $response->withStatus(500)->withJson(['errors' => 'Document does not exist']);
    
                $attachments = AttachmentModel::get([
                    'select'=> ['id'],
                    'where' => ['main_document_id = ?'],
                    'data'  => [$args['id']]
                ]);
                if (!empty($attachments)) {
                    $attachmentFilePaths = AdrModel::getAttachmentsAdr([
    
                        'select'=> ['CONCAT(path, filename) as path', 'type'],
    
                        'where' => ['attachment_id IN (?)'],
                        'data'  => [array_column($attachments, 'id')]
    
                    ]);
                    $filePaths = array_merge($filePaths, $attachmentFilePaths);
                }
    
    
                $docServers = array_column($docServers, 'path', 'type');
                foreach ($filePaths as $filePath) {
                    $docFilePath = $docServers[$filePath['type']] . $filePath['path'];
    
                    if (file_exists("$docFilePath")) {
                        AdrModel::deleteDocumentAdr([
                            'where' => ['id = ?'],
                            'data'  => [$filePath['id']]
                        ]);
                        unlink($docFilePath);
    
    
                if (!empty($attachments)) {
                    AdrModel::deleteAttachmentAdr([
                        'where' => ['attachment_id IN (?)'],
                        'data'  => [array_column($attachments, 'id')]
                    ]);
                }
                AdrModel::deleteDocumentAdr([
                    'where' => ['main_document_id = ?'],
                    'data'  => [$args['id']]
                ]);
    
                DocumentModel::update([
                    'set'   => ['status' => 'HARD_DEL'],
                    'where' => ['id = ?'],
                    'data'  => [$args['id']]
                ]);
    
                $historyInfo = "{documentDeleted} : {$document['title']}";
    
            } else {
                DocumentModel::update([
                    'set'   => ['status' => 'SOFT_DEL'],
                    'where' => ['id = ?'],
                    'data'  => [$args['id']]
                ]);
    
                $historyInfo = "{softDeleted} : {$document['title']}";
    
            HistoryController::add([
                'code'          => 'OK',
                'objectType'    => 'main_documents',
                'objectId'      => $args['id'],
                'type'          => 'SUPPRESSION',
    
            return $response->withStatus(204);
        }
    
    
        public function getContent(Request $request, Response $response, array $args)
        {
    
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_documents'])
                && !DocumentController::hasRightById(['id' => $args['id'], 'userId' => $GLOBALS['id'], 'readOnly' => true])) {
    
                return $response->withStatus(403)->withJson(['errors' => 'Document out of perimeter']);
            }
    
    
            $queryParams = $request->getQueryParams();
    
            if (!empty($queryParams['type']) && !in_array($queryParams['type'], ['esign', 'original', 'doc'])) {
                return $response->withStatus(403)->withJson(['errors' => 'Query param type is not allowed']);
            }
    
            $content = DocumentController::getContentPath(['id' => $args['id'], 'type' => strtoupper($queryParams['type'])]);
    
            if (!empty($content['errors'])) {
                return $response->withStatus($content['code'])->withJson(['errors' => $content['errors']]);
    
            } elseif (empty($content['path'])) {
                return $response->withStatus(404)->withJson(['encodedDocument' => null]);
    
            }
            $pathToDocument = $content['path'];
    
            $document = DocumentModel::getById(['select' => ['title'], 'id' => $args['id']]);
    
            HistoryController::add([
                'code'          => 'OK',
                'objectType'    => 'main_documents',
                'objectId'      => $args['id'],
                'type'          => 'VIEW',
                'message'       => "{documentViewed} : {$document['title']}"
            ]);
    
            $fileContent = file_get_contents($pathToDocument);
            $finfo       = new \finfo(FILEINFO_MIME_TYPE);
            $mimeType    = $finfo->buffer($fileContent);
    
            if (empty($queryParams['mode']) || $queryParams['mode'] == 'base64') {
    
                return $response->withJson(['encodedDocument' => base64_encode($fileContent)]);
            } else {
                $pathInfo = pathinfo($pathToDocument);
    
                $response->write($fileContent);
                $response = $response->withAddedHeader('Content-Disposition', "inline; filename=maarch.{$pathInfo['extension']}");
                return $response->withHeader('Content-Type', $mimeType);
            }
        }
    
    Damien's avatar
    Damien committed
        public function create(Request $request, Response $response)
        {
    
            if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'indexation'])) {
    
                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']);
    
            } elseif (!Validator::stringType()->length(0, 64)->validate($body['reference'])) {
                return $response->withStatus(400)->withJson(['errors' => 'Body reference is too loong or not a string']);
    
    Damien's avatar
    Damien committed
            }
    
            $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) {
    
                if (empty($workflow['processingUser']) && empty($workflow['userId']) && !empty($workflow['externalInformations'])) {
    
                    if (!Validator::intVal()->notEmpty()->validate($workflow['externalInformations']['sourceId'] ?? null)) {
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][externalInformations] sourceId is empty or not an integer"]);
                    } elseif (!Validator::stringType()->notEmpty()->validate($workflow['externalInformations']['firstname'] ?? null)) {
    
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][externalInformations] firstname is empty or not a string"]);
                    } elseif (!Validator::stringType()->notEmpty()->validate($workflow['externalInformations']['lastname'] ?? null)) {
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][externalInformations] lastname is empty or not a string"]);
                    } elseif (empty($workflow['externalInformations']['email']) || !filter_var($workflow['externalInformations']['email'], FILTER_VALIDATE_EMAIL) || !Validator::stringType()->notEmpty()->length(1, 128)->validate($workflow['externalInformations']['email'])) {
                        return $response->withStatus(400)->withJson(['errors' => 'Body workflow[{$key}][externalInformations] email is empty or not valid']);
    
                    } elseif (!Validator::stringType()->notEmpty()->length(1, 128)->validate($workflow['externalInformations']['phone'] ?? null)) {
                        return $response->withStatus(400)->withJson(['errors' => 'Body workflow[{$key}][externalInformations] phone is empty or not valid']);
    
                    }
                } else {
                    if (!empty($workflow['processingUser'])) {
                        $processingUser = UserModel::getByLogin(['select' => ['id'], 'login' => strtolower($workflow['processingUser'])]);
                    } elseif (!empty($workflow['userId'])) {
                        $processingUser = UserModel::getById(['select' => ['id'], 'id' => $workflow['userId']]);
                    }
                    if (empty($processingUser)) {
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] processingUser/userId 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'] ?? null;
    
                if ($hasElectronicSignature && $workflow['signatureMode'] == 'stamp' && $workflow['mode'] == 'sign') {
    
                    return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signatureMode cannot be stamp after an electronic signature", 'lang' => 'stampInTheMiddleImpossible']);
                }
    
                if ($workflow['externalInformations']['role'] ?? null == 'otp_sign_yousign' && empty($workflow['signaturePositions'])) {
    
                    return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signaturePositions must be set for sign_yousign role"]);
                }
    
                if (!empty($workflow['signaturePositions'])) {
                    if (!Validator::arrayType()->validate($workflow['signaturePositions'])) {
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] signaturePositions is not an array"]);
                    }
                    foreach ($workflow['signaturePositions'] as $keySP => $signaturePosition) {
    
                        if (!Validator::each(Validator::between(0, 100, true))->validate([$signaturePosition['positionX'], $signaturePosition['positionY']])
                            || !Validator::intVal()->positive()->validate($signaturePosition['page'])) {
    
                            return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][signaturePositions][{$keySP}] is wrong formatted"]);
                        }
                    }
                }
    
                if (!empty($workflow['datePositions'])) {
                    if (!Validator::arrayType()->validate($workflow['datePositions'])) {
                        return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}] datePositions is not an array"]);
                    }
                    foreach ($workflow['datePositions'] as $keyDP => $datePosition) {
    
                        if (!Validator::each(Validator::between(0, 100, true))->validate([$datePosition['positionX'], $datePosition['positionY']])
                            || !Validator::intVal()->positive()->validate($datePosition['page'])) {
    
                            return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][datePositions][{$keyDP}] is wrong formatted"]);
    
                        } elseif (empty($datePosition['color']) || empty($datePosition['font']) || empty($datePosition['format']) || empty($datePosition['width'])) {
    
                            return $response->withStatus(400)->withJson(['errors' => "Body workflow[{$key}][datePositions][{$keyDP}] is wrong formatted"]);
                        }
                    }
                }
    
                if (in_array($workflow['signatureMode'], ['eidas', 'rgs_2stars_timestamped', 'inca_card_eidas'])) {
    
                if ($workflow['signatureMode'] == 'eidas_metasign' && $workflow['mode'] == 'sign') {
                    $hasMetaSignSignature = true;
                }
    
                if ($workflow['signatureMode'] != 'stamp' && $workflow['mode'] == 'sign') {
    
            $libDir    = CoreConfigModel::getLibrariesDirectory();
            $loadedXml = CoreConfigModel::getConfig();
    
            if (($loadedXml->docaposteSignature->enable == 'true' || $loadedXml->metaSignSignature->enable == 'true') && 
                ($hasEidas || $hasMetaSignSignature) && (empty($libDir) || !is_file($libDir . 'SetaPDF-Signer/library/SetaPDF/Autoload.php'))) {
    
                return $response->withStatus(500)->withJson(['errors' => 'SetaPDF-Signer library is not installed', 'lang' => 'setAPdfSignerError']);
            }
    
    
            $isZipped = (!isset($body['isZipped']) || $body['isZipped']) ? true : false;
    
    
            if ($isZipped) {
                $encodedDocument = DocumentController::getEncodedDocumentFromEncodedZip(['encodedZipDocument' => $body['encodedDocument']]);
            } else {
                $encodedDocument['encodedDocument'] = $body['encodedDocument'];
            }
    
    Damien's avatar
    Damien committed
            if (!empty($encodedDocument['errors'])) {
                return $response->withStatus(500)->withJson(['errors' => $encodedDocument['errors']]);
            }
    
    
            $data     = base64_decode($encodedDocument['encodedDocument']);
            $finfo    = finfo_open();
            $mimeType = finfo_buffer($finfo, $data, FILEINFO_MIME_TYPE);
            if (strtolower($mimeType) != 'application/pdf') {
                return $response->withStatus(400)->withJson(['errors' => 'Document is not a pdf']);
            }
    
    
            if (!empty($libDir) && is_file($libDir . 'SetaPDF-FormFiller-Full/library/SetaPDF/Autoload.php')) {
    
                require_once($libDir . 'SetaPDF-FormFiller-Full/library/SetaPDF/Autoload.php');
    
    Damien's avatar
    Damien committed
    
                $targetFile = CoreConfigModel::getTmpPath() . "tmp_file_{$GLOBALS['id']}_" .rand(). "_target_watermark.pdf";
                $flattenedFile = CoreConfigModel::getTmpPath() . "tmp_file_{$GLOBALS['id']}_" .rand(). "_watermark.pdf";
                file_put_contents($targetFile, base64_decode($encodedDocument['encodedDocument']));
                $writer = new \SetaPDF_Core_Writer_File($flattenedFile);
                $document = \SetaPDF_Core_Document::loadByFilename($targetFile, $writer);
    
                $formFiller = new \SetaPDF_FormFiller($document);
                $fields = $formFiller->getFields();
    
    
                // only flatten if not signed already
                $allFields = $fields->getAll();
                $alreadySigned = false;
                foreach ($allFields as $fieldValue) {
                    if ($fieldValue instanceof \SetaPDF_FormFiller_Field_Signature) {
                        $alreadySigned = true;
                        break;
                    }
                }
    
                if (!$alreadySigned) {
                    $fields->flatten();
                    $document->save()->finish();
    
                    $file = file_get_contents($flattenedFile);
                    $encodedDocument['encodedDocument'] = base64_encode($file);
    
    Damien's avatar
    Damien committed
                unlink($targetFile);
            }
    
    
                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'],
                    'notes'         => $notes ?? null,
                    'link_id'       => !empty($body['linkId']) ? (string)$body['linkId'] : null,
                    'metadata'      => empty($body['metadata']) ? '{}' : json_encode($body['metadata']),
                    'status'        => 'CREATED',
                    'typist'        => $GLOBALS['id'],
                    'mailing_id'    => !empty($body['mailingId']) ? (string)$body['mailingId'] : null
                ]);
    
                $configuration = ConfigurationModel::getByIdentifier(['identifier' => 'customization']);
    
                $configuration = json_decode($configuration[0]['value'] ?? '[]', true);
    
                if (!empty($configuration['watermark']['enabled'])) {
                    $fileWithWaterMark = WatermarkController::watermarkDocument([
                        'config'          => $configuration['watermark'],
                        'encodedDocument' => $encodedDocument['encodedDocument'],
                        'id'              => $id
                    ]);
    
                    if (!empty($fileWithWaterMark)) {
                        $encodedDocument['encodedDocument'] = base64_encode($fileWithWaterMark);
                    }
                }
    
    
                $storeInfos = DocserverController::storeResourceOnDocServer([
                    'encodedFile'       => $encodedDocument['encodedDocument'],
                    'format'            => 'pdf',
                    'docserverType'     => 'DOC'
                ]);
    
                if (!empty($storeInfos['errors'])) {
    
                    DatabaseModel::rollbackTransaction();
    
                    return $response->withStatus(500)->withJson(['errors' => $storeInfos['errors']]);
                }
    
                if (!empty($body['notes']) && !empty($body['notes']['value'])) {
                    $notes = [
                        'value'         => $body['notes']['value'],
                        'creator'       => $body['notes']['creator'] ?? null,
                        'creationDate'  => $body['notes']['creationDate'] ?? null
                    ];
                    $notes = json_encode($notes);
                }
    
                AdrModel::createDocumentAdr([
                    'documentId'     => $id,
                    'type'           => 'DOC',
                    'path'           => $storeInfos['path'],
                    'filename'       => $storeInfos['filename'],
                    'fingerprint'    => $storeInfos['fingerprint']
    
                $storeInfos = DocserverController::storeResourceOnDocServer([
                    'encodedFile'       => $encodedDocument['encodedDocument'],
                    'format'            => 'pdf',
                    'docserverType'     => 'ORIGINAL'
                ]);
    
                if (!empty($storeInfos['errors'])) {
    
                    DatabaseModel::rollbackTransaction();
    
                    return $response->withStatus(500)->withJson(['errors' => $storeInfos['errors']]);
                }
                AdrModel::createDocumentAdr([
                    'documentId'     => $id,
                    'type'           => 'ORIGINAL',
                    'path'           => $storeInfos['path'],
                    'filename'       => $storeInfos['filename'],
                    'fingerprint'    => $storeInfos['fingerprint']
                ]);
    
                $firstWorkflowId = null;
    
                $configPath = CoreConfigModel::getConfigPath();
                $overrideFile = "{$configPath}/override/setasign/fpdi_pdf-parser/src/autoload.php";
                if (file_exists($overrideFile)) {
                    require_once($overrideFile);
                }
    
                $pdf = new Fpdi('P', 'pt');
                $veryTemporaryPath = CoreConfigModel::getTmpPath() . "tmp_file_{$GLOBALS['id']}_" .rand(). "_coord.pdf";
                file_put_contents($veryTemporaryPath, base64_decode($encodedDocument['encodedDocument']));
                $pdf->setSourceFile($veryTemporaryPath);
                unlink($veryTemporaryPath);
    
                foreach ($body['workflow'] as $key => $workflow) {
    
                    if (!SignatureController::isValidSignatureMode(['mode' => $workflow['signatureMode']])) {
                        $workflow['signatureMode'] = 'stamp';
                    }
    
    
                    $workflowId = WorkflowModel::create([
    
                        'userId'                => $workflow['userId'],
                        'mainDocumentId'        => $id,
                        'mode'                  => $workflow['mode'],
                        'order'                 => $key + 1,
                        'signatureMode'         => $workflow['signatureMode'],
    
                        'signaturePositions'    => empty($workflow['signaturePositions']) ? '[]' : json_encode($workflow['signaturePositions']),
                        'datePositions'         => empty($workflow['datePositions']) ? '[]' : json_encode($workflow['datePositions'])
    
                    if ($key === 0) {
                        $firstWorkflowId = $workflowId;
                    }
    
                    if (empty($workflow['userId'])) {
    
                        if (!empty($workflow['signaturePositions'])) {
                            foreach ($workflow['signaturePositions'] as $signaturePosition) {
    
                                $pageId = $pdf->importPage($signaturePosition['page']);
                                $dimensions = $pdf->getTemplateSize($pageId);
    
                                $llx = $dimensions['width'] * $signaturePosition['positionX'] / 100;
    
                                $llx = round($llx);
    
                                $lly = $dimensions['height'] - ($dimensions['height'] * $signaturePosition['positionY'] / 100) - 40;
    
                                $lly = round($lly);
                                $urx = $llx + 100;
                                $urx = round($urx);
                                $ury = $lly + 40;
                                $ury = round($ury);
                                $workflow['externalInformations']['signaturePositions'][] = ['page' => $signaturePosition['page'], 'position' => "{$llx},{$lly},{$urx},{$ury}"];
                            }
                        }
    
                        $informations = [];
                        if ($workflow['externalInformations']['type'] == 'yousign') {
                            $informations = YousignController::formatExternalInformations($workflow['externalInformations']);
                        }
    
                        WorkflowExternalInformationModel::create([
    
                            'workflow_id'                   => $workflowId,
                            'external_signatory_book_id'    => $workflow['externalInformations']['sourceId'],
                            'firstname'                     => $workflow['externalInformations']['firstname'],
                            'lastname'                      => $workflow['externalInformations']['lastname'],
                            'email'                         => $workflow['externalInformations']['email'],
                            'phone'                         => $workflow['externalInformations']['phone'],
                            'informations'                  => json_encode($informations)
    
                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']}"]);
                    }
                }
    
                $data = empty($body['reference']) ? [] : ['reference' => $body['reference']];
                if (!empty($body['metadata'])) {
                    foreach ($body['metadata'] as $key => $metadata) {
                        $data[ucwords($key)] = $metadata;
                    }
                }
    
    
                HistoryController::add([
                    'code'          => 'OK',
                    'objectType'    => 'main_documents',
                    'objectId'      => $id,
                    'type'          => 'CREATION',
    
                    'message'       => "{documentAdded} : {$body['title']}",
                    'data'          => empty($data) ? [] : $data
    
                ]);
    
                DatabaseModel::commitTransaction();
            } catch (\Exception $e) {
                return $response->withStatus(500)->withJson(['errors' => $e->getMessage()]);
            }
    
                'select'  => ['id', 'user_id', 'signature_mode'],
    
                'where'   => ['mode = ?', 'main_document_id = ?'],
                'data'    => ['sign', $id],
                'orderBy' => ['"order" asc']
            ]);
    
            if ($loadedXml->docaposteSignature->enable == 'true' && $hasEidas) {
    
                $result = DigitalSignatureController::createTransaction(['documentId' => $id, 'workflow' => $workflow, 'encodedDocument' => $encodedDocument['encodedDocument']]);
                if (!empty($result['errors'])) {
                    return $response->withStatus(500)->withJson(['errors' => $result['errors']]);
    
            if ($loadedXml->metaSignSignature->enable == 'true' && $hasMetaSignSignature) {
                $metaSignInit = MetaSignSignatureController::init();
                if (!empty($metaSignInit['errors'])) {
                    return $response->withStatus($metaSignInit['code'])->withJson(['errors' => $metaSignInit['errors']]);
                }
            }
    
            if (empty($body['workflow'][0]['userId'])) {
    
                if ($body['workflow'][0]['externalInformations']['type'] == 'yousign') {
                    $procedureCreated = YousignController::createProcedure([
                        'documentId'        => $id,
                        'encodedDocument'   => $encodedDocument['encodedDocument'],
                        'name'              => $body['title'],
                        'description'       => $body['description'],
                        'workflowId'        => $firstWorkflowId
                    ]);
                    if (!empty($procedureCreated['errors'])) {
                        return $response->withStatus(500)->withJson(['errors' => 'Yousign procedure creation failed : ' . $procedureCreated['errors']]);
                    }
                }
    
            } else {
                EmailController::sendNotification(['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]);
    
    Damien's avatar
    Damien committed
        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']);
    
            } elseif (empty(DocumentController::ACTIONS[$args['actionId']])) {
    
    Damien's avatar
    Damien committed
                return $response->withStatus(400)->withJson(['errors' => 'Action does not exist']);
    
            $workflow  = WorkflowModel::getCurrentStep(['select' => ['id', 'mode', 'user_id', 'signature_mode', 'digital_signature_id'], 'documentId' => $args['id']]);
    
    
            if (empty($workflow)) {
                return $response->withStatus(400)->withJson(['errors' => 'Workflow is over']);
            }
    
            $substitute = UserModel::getById(['id' => $workflow['user_id'], 'select' => ['substitute']]);
    
            if ($GLOBALS['id'] != $workflow['user_id'] && $GLOBALS['id'] != $substitute['substitute']) {
                return $response->withStatus(403)->withJson(['errors' => 'Current user unauthorized for this step']);
            }
    
    
    
            $libDir    = CoreConfigModel::getLibrariesDirectory();
    
            $loadedXml = CoreConfigModel::getConfig();
    
            $tmpPath   = CoreConfigModel::getTmpPath();
    
    
            if ($workflow['mode'] == 'sign' && $workflow['signature_mode'] != 'stamp') {
                if (empty($libDir) || !is_file($libDir . 'SetaPDF-Signer/library/SetaPDF/Autoload.php')) {
                    return $response->withStatus(500)->withJson(['errors' => 'SetaPDF-Signer library is not installed', 'lang' => 'setAPdfSignerError']);
                }
                require_once($libDir . 'SetaPDF-Signer/library/SetaPDF/Autoload.php');
    
                if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && !in_array($workflow['signature_mode'], ['eidas', 'eidas_metasign'])) {
    
                    $url = UrlController::getCoreUrl();
                    if (strpos($url, 'https://') !== 0) {
                        return $response->withStatus(400)->withJson(['errors' => 'Url is not secured (https needed)', 'lang' => 'securedUrlNeeded']);
                    }
                }
    
            if (in_array($workflow['signature_mode'], ['eidas', 'inca_card_eidas']) && $loadedXml->docaposteSignature->enable != 'true') {
                return $response->withStatus(400)->withJson(['errors' => 'docaposteSignature is disabled', 'lang' => 'docaposteSignatureDisabled']);
            }
            if ($workflow['signature_mode'] == 'eidas_metasign' && $loadedXml->metaSignSignature->enable != 'true') {
                return $response->withStatus(400)->withJson(['errors' => 'metaSignSignature is disabled', 'lang' => 'metaSignSignatureDisabled']);
    
            $body = $request->getParsedBody();
    
            if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && $workflow['signature_mode'] == 'stamp') {
    
                $isCertificateSigned = WorkflowModel::get([
                    'select' => [1],
                    'where' => [
                        'main_document_id = ?',
                        'signature_mode not in (?)',
                        'status is not null'
                    ],
                    'data' => [
                        $args['id'],
                        ['stamp', 'note']
                    ]
                ]);
    
                if (!empty($isCertificateSigned)) {
                    unset($body['signatures']);
                }
            }
    
            if (!empty($body['signatures']) && empty($body['signatureLength'])) {
    
                foreach ($body['signatures'] as $signature) {
    
    Damien's avatar
    Damien committed
                    foreach (['encodedImage', 'width', 'positionX', 'positionY', 'page', 'type'] as $value) {
                        if (!isset($signature[$value])) {
                            return $response->withStatus(400)->withJson(['errors' => $value . ' is empty']);
                        }
                    }
                }
    
                if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && in_array($workflow['signature_mode'], ['rgs_2stars', 'rgs_2stars_timestamped', 'inca_card', 'inca_card_eidas'])) {
    
                    if (!empty($body['step']) && $body['step'] == 'hashCertificate') {
    
                        $signWithServerCertificate = false;
                        if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && $workflow['mode'] == 'sign' && $loadedXml->electronicSignature->enable == 'true') {
                            $signWithServerCertificate = true;
                        }
    
    
                        if (empty($body['tmpUniqueId'])) {
                            $control = DocumentController::getDocumentPath(['id' => $args['id']]);
                            if (!empty($control['errors'])) {
                                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
                            }
    
                            $control = DocumentController::processSignatures(['path' => $control['path'], 'signature' => $body['signatures'][0], 'signWithServerCertificate' => $signWithServerCertificate]);
    
                            if (!empty($control['errors'])) {
                                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
                            }
    
    Damien's avatar
    Damien committed
    
    
                            $uniqueId = CoreConfigModel::getUniqueId();
                            $body['tmpUniqueId'] = $uniqueId;
                        } else {
                            $pathToDocument = "{$tmpPath}tmpSignatureDoc_{$GLOBALS['id']}_{$body['tmpUniqueId']}.pdf";
                            if (!is_file($pathToDocument)) {
                                return $response->withStatus(400)->withJson(['errors' => 'Unique temporary file does not exist']);
                            }
    
    
                            $control = DocumentController::processSignatures(['path' => $pathToDocument, 'signature' => $body['signatures'][0], 'signWithServerCertificate' => $signWithServerCertificate]);
    
                            if (!empty($control['errors'])) {
                                return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
                            }
                        }
    
                        file_put_contents("{$tmpPath}tmpSignatureDoc_{$GLOBALS['id']}_{$body['tmpUniqueId']}.pdf", $control['fileContent']);
    
                    }
                } else {
                    $control = DocumentController::getDocumentPath(['id' => $args['id']]);
    
                    if (!empty($control['errors'])) {
                        return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
    
                    $pathToDocument = $control['path'];
    
                    $tmpFilename = $tmpPath . $GLOBALS['id'] . '_' . rand() . 'adr.pdf';
                    copy($pathToDocument, $tmpFilename);
    
    Damien's avatar
    Damien committed
    
    
                    $configPath = CoreConfigModel::getConfigPath();
                    $overrideFile = "{$configPath}/override/setasign/fpdi_pdf-parser/src/autoload.php";
                    if (file_exists($overrideFile)) {
                        require_once($overrideFile);
                    }
                    $pdf            = new Fpdi('P');
                    $pagesNumber    = $pdf->setSourceFile($tmpFilename);
    
    Damien's avatar
    Damien committed
    
    
                    $control = DocumentController::setSignaturesOnPdf(['signatures' => $body['signatures'], 'pagesNumber' => $pagesNumber], $pdf);
                    if (!empty($control['errors'])) {
                        return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
                    }
                    $affectedPages = $control['affectedPages'];
    
                    if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && $workflow['mode'] == 'sign' && $loadedXml->electronicSignature->enable == 'true') {
                        $control = CertificateSignatureController::signWithServerCertificate($pdf);
                        if (!empty($control['errors'])) {
                            return $response->withStatus(400)->withJson(['errors' => $control['errors']]);
                        }
                    }
    
                    $fileContent = $pdf->Output('', 'S');
    
                    $storeInfos = DocserverController::storeResourceOnDocServer([
                        'encodedFile'     => base64_encode($fileContent),
                        'format'          => 'pdf',
                        'docserverType'   => 'DOC'
                    ]);
                    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']
                    ]);
                    AdrModel::createDocumentAdr([
                        'documentId'    => $args['id'],
                        'type'          => 'DOC',
                        'path'          => $storeInfos['path'],
                        'filename'      => $storeInfos['filename'],
                        'fingerprint'   => $storeInfos['fingerprint']
    
                    if (DocumentController::ACTIONS[$args['actionId']] == 'VAL' && $workflow['signature_mode'] == 'stamp') {
    
                        $storeInfos = DocserverController::storeResourceOnDocServer([
                            'encodedFile'     => base64_encode($fileContent),
                            'format'          => 'pdf',
                            'docserverType'   => 'ESIGN'
                        ]);
                        if (!empty($storeInfos['errors'])) {
                            return $response->withStatus(500)->withJson(['errors' => $storeInfos['errors']]);
                        }
    
                        AdrModel::deleteDocumentAdr([
                            'where' => ['main_document_id = ?', 'type = ?'],
                            'data'  => [$args['id'], 'ESIGN']
                        ]);
                        AdrModel::createDocumentAdr([
                            'documentId'    => $args['id'],
                            'type'          => 'ESIGN',
                            'path'          => $storeInfos['path'],
                            'filename'      => $storeInfos['filename'],
                            'fingerprint'   => $storeInfos['fingerprint']
                        ]);
                    }
    
    
                    $tnlPages = [];