TD-322: [BACK]. [реализовать сервис для подписания документов в контейнере signer]
This commit is contained in:
parent
16e81cf34e
commit
9883e5f9db
|
|
@ -4,8 +4,6 @@
|
|||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/backend/src" isTestSource="false" packagePrefix="App\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/backend/tests" isTestSource="true" packagePrefix="App\Tests\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/backend/vendor/composer" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/backend/vendor/doctrine/annotations" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/backend/vendor/doctrine/cache" />
|
||||
|
|
|
|||
19
backend/.env
19
backend/.env
|
|
@ -1,23 +1,5 @@
|
|||
# In all environments, the following files are loaded if they exist,
|
||||
# the latter taking precedence over the former:
|
||||
#
|
||||
# * .env contains default values for the environment variables needed by the app
|
||||
# * .env.local uncommitted file with local overrides
|
||||
# * .env.$APP_ENV committed environment-specific defaults
|
||||
# * .env.$APP_ENV.local uncommitted environment-specific overrides
|
||||
#
|
||||
# Real environment variables win over .env files.
|
||||
#
|
||||
# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES.
|
||||
# https://symfony.com/doc/current/configuration/secrets.html
|
||||
#
|
||||
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
|
||||
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
|
||||
|
||||
###> symfony/framework-bundle ###
|
||||
APP_ENV=dev
|
||||
APP_SECRET=850da55654c68f779822ea80d2b66a94
|
||||
###< symfony/framework-bundle ###
|
||||
|
||||
###> doctrine/doctrine-bundle ###
|
||||
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
|
||||
|
|
@ -28,3 +10,4 @@ APP_SECRET=850da55654c68f779822ea80d2b66a94
|
|||
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
###< doctrine/doctrine-bundle ###
|
||||
DOT_DOT_URL='http://dot-dot.local'
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
"symfony/flex": "^2",
|
||||
"symfony/framework-bundle": "6.2.*",
|
||||
"symfony/runtime": "6.2.*",
|
||||
"symfony/serializer": "6.2.*",
|
||||
"symfony/ux-chartjs": "*",
|
||||
"symfony/yaml": "6.2.*"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bf87fa37fe523eb581063fa93c0fda64",
|
||||
"content-hash": "0d9c45fd694083582028c80344c66518",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/package-versions-deprecated",
|
||||
|
|
@ -5393,6 +5393,107 @@
|
|||
],
|
||||
"time": "2023-07-13T14:28:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/serializer",
|
||||
"version": "v6.2.13",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/serializer.git",
|
||||
"reference": "19083104e606ecf8a48baa8ed310c7a073887037"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/serializer/zipball/19083104e606ecf8a48baa8ed310c7a073887037",
|
||||
"reference": "19083104e606ecf8a48baa8ed310c7a073887037",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/polyfill-ctype": "~1.8"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.12",
|
||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/dependency-injection": "<5.4",
|
||||
"symfony/property-access": "<5.4",
|
||||
"symfony/property-info": "<5.4.24|>=6,<6.2.11",
|
||||
"symfony/uid": "<5.4",
|
||||
"symfony/yaml": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.12|^2",
|
||||
"phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0",
|
||||
"symfony/cache": "^5.4|^6.0",
|
||||
"symfony/config": "^5.4|^6.0",
|
||||
"symfony/dependency-injection": "^5.4|^6.0",
|
||||
"symfony/error-handler": "^5.4|^6.0",
|
||||
"symfony/filesystem": "^5.4|^6.0",
|
||||
"symfony/form": "^5.4|^6.0",
|
||||
"symfony/http-foundation": "^5.4|^6.0",
|
||||
"symfony/http-kernel": "^5.4|^6.0",
|
||||
"symfony/mime": "^5.4|^6.0",
|
||||
"symfony/property-access": "^5.4|^6.0",
|
||||
"symfony/property-info": "^5.4.24|^6.2.11",
|
||||
"symfony/uid": "^5.4|^6.0",
|
||||
"symfony/validator": "^5.4|^6.0",
|
||||
"symfony/var-dumper": "^5.4|^6.0",
|
||||
"symfony/var-exporter": "^5.4|^6.0",
|
||||
"symfony/yaml": "^5.4|^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/cache-implementation": "For using the metadata cache.",
|
||||
"symfony/config": "For using the XML mapping loader.",
|
||||
"symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.",
|
||||
"symfony/property-access": "For using the ObjectNormalizer.",
|
||||
"symfony/property-info": "To deserialize relations.",
|
||||
"symfony/var-exporter": "For using the metadata compiler.",
|
||||
"symfony/yaml": "For using the default YAML mapping loader."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Serializer\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/serializer/tree/v6.2.13"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-27T16:18:16+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.5.0",
|
||||
|
|
|
|||
|
|
@ -14,11 +14,20 @@ services:
|
|||
# makes classes in src/ available to be used as services
|
||||
# this creates a service per class whose id is the fully-qualified class name
|
||||
App\:
|
||||
resource: '../src/'
|
||||
resource: '../src/*'
|
||||
exclude:
|
||||
- '../src/DependencyInjection/'
|
||||
- '../src/Entity/'
|
||||
- '../src/Kernel.php'
|
||||
- '../src/*/Api/{Request,Response}'
|
||||
- '../src/*/{Exception,Entity,Dto,Enum,Helper,Model}'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
||||
guzzle.http_client:
|
||||
class: GuzzleHttp\Client
|
||||
|
||||
GuzzleHttp\Client: '@guzzle.http_client'
|
||||
|
||||
App\Api\ApiParams:
|
||||
arguments:
|
||||
$endPointUrl: '%env(DOT_DOT_URL)%'
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class SignController extends AbstractController
|
||||
{
|
||||
#[Route(path: '/test', name: 'app.test')]
|
||||
public function __invoke(): Response
|
||||
{
|
||||
return new Response('test123');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\External\Api;
|
||||
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
|
||||
/**
|
||||
* Class ApiAbstract Абстрактный API класс
|
||||
*
|
||||
* Универсальный клас для создания API интерфейсов
|
||||
*/
|
||||
abstract class AbstractApi
|
||||
{
|
||||
protected Client $client;
|
||||
protected ResponseHandler $responseHandler;
|
||||
|
||||
public function __construct(Client $client, ResponseHandler $responseHandler)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->responseHandler = $responseHandler;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\External\Api;
|
||||
|
||||
trait ApiSleep
|
||||
{
|
||||
protected function sleep(int $time = 5): void
|
||||
{
|
||||
if ($time > 0) {
|
||||
sleep($time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\External\Api;
|
||||
|
||||
/**
|
||||
* Враппер бинарных фалов, при создании класса содает новый временный файл,
|
||||
* при необходимости сохраняет файл под новым именем
|
||||
*/
|
||||
class BinaryStringFileResult
|
||||
{
|
||||
public string $tempFileName;
|
||||
|
||||
public function __construct(
|
||||
string $content,
|
||||
) {
|
||||
$this->saveToTempFile($content);
|
||||
}
|
||||
|
||||
private function saveToTempFile(string $content): void
|
||||
{
|
||||
$this->tempFileName = sprintf('%s/%s_%s', sys_get_temp_dir(), 'Document', time());
|
||||
file_put_contents($this->tempFileName, $content);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\External\Api;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* Class ResponseHandler Обработчик ответа API.
|
||||
*
|
||||
* После того как получили ответ api, оборачиваем его обработчик из которого мы можем получить ответ в любом формате,
|
||||
* а так же применит дополнительную фильтрацию или корректировку ответа
|
||||
*/
|
||||
class ResponseHandler
|
||||
{
|
||||
protected ResponseInterface $response;
|
||||
private array $contentFilters = [];
|
||||
|
||||
public function contentIsEmpty(): bool
|
||||
{
|
||||
return empty($this->response->getBody()->getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает ответ в текстовом виде, применит фильтры при их наличие.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
$content = (string)$this->response->getBody();
|
||||
|
||||
return $this->filterContent($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавляет фильтр контента иногда необходимо поправить ответ, что бы в итоге возвращался нужный тип данных.
|
||||
* Бывает иногда сервер, возвращает массив данных и при преобразовании json_decode возвращается
|
||||
* массив вместо stdClass.
|
||||
*/
|
||||
public function addContentFilter(callable $filter): self
|
||||
{
|
||||
$this->contentFilters[] = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует json в объект
|
||||
*
|
||||
* @return stdClass
|
||||
*/
|
||||
public function getContentStdFromJson(): stdClass
|
||||
{
|
||||
return json_decode($this->getContent());
|
||||
}
|
||||
|
||||
public function getContentAsBinaryStingResult(): BinaryStringFileResult
|
||||
{
|
||||
return new BinaryStringFileResult($this->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function getContentArrayStdFromJson(): array
|
||||
{
|
||||
return json_decode($this->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразует json в массив.
|
||||
*/
|
||||
public function getContentJsonToArray(): array
|
||||
{
|
||||
return $this->contentIsEmpty() ? [] : json_decode($this->getContent(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает ответ в обработчик.
|
||||
*/
|
||||
public function setResponse(ResponseInterface $response): self
|
||||
{
|
||||
$this->response = $response;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Фильтруем ответ
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function filterContent(string $content): string
|
||||
{
|
||||
if (!$this->contentFilters) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
foreach ($this->contentFilters as $filter) {
|
||||
$content = $filter($content);
|
||||
}
|
||||
|
||||
$this->contentFilters = [];
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\Http;
|
||||
|
||||
interface RequestDtoInterface
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace App\Infrastructure\Http;
|
||||
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
|
||||
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
|
||||
use Symfony\Component\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
class RequestDtoResolver implements ValueResolverInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly ValidatorInterface $validator
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(Request $request, ArgumentMetadata $argument): bool
|
||||
{
|
||||
$type = $argument->getType();
|
||||
|
||||
if (empty($type) || !class_exists($type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new ReflectionClass($type);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $reflection->implementsInterface(RequestDtoInterface::class);
|
||||
}
|
||||
|
||||
public function resolve(Request $request, ArgumentMetadata $argument): iterable
|
||||
{
|
||||
if (($class = $argument->getType()) === null || !class_exists($class)) {
|
||||
throw new RuntimeException('dto exception');
|
||||
}
|
||||
|
||||
$object = $this->serializer->deserialize($request->getContent(), $class, 'json');
|
||||
|
||||
if (is_object($object)) {
|
||||
$this->validateObject($object);
|
||||
}
|
||||
|
||||
yield $object;
|
||||
}
|
||||
|
||||
private function validateObject(object $object): void
|
||||
{
|
||||
$errors = $this->validator->validate($object);
|
||||
|
||||
if (count($errors) > 0) {
|
||||
/* todo получать из вне сообщение для ошибки */
|
||||
throw new RuntimeException($errors, 'Ошибка валидации');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sign\Api;
|
||||
|
||||
use App\Infrastructure\External\Api\AbstractApi;
|
||||
use App\Infrastructure\External\Api\BinaryStringFileResult;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
class Api extends AbstractApi
|
||||
{
|
||||
private const string TYPE_OTHER = 'other';
|
||||
private const string API_DOCUMENT = '/api/v1/document/upload/batch/';
|
||||
public ApiParams $apiParams;
|
||||
|
||||
public function send(string $url, string $token, $urlDocument, int $batch): array
|
||||
{
|
||||
$headers = [
|
||||
'Accept' => 'application/json',
|
||||
'Authorization' => $token,
|
||||
];
|
||||
$options = [
|
||||
'multipart' => [
|
||||
[
|
||||
'name' => 'type',
|
||||
'contents' => self::TYPE_OTHER
|
||||
],
|
||||
[
|
||||
'name' => 'file',
|
||||
'contents' => fopen($urlDocument, 'r'),
|
||||
'filename' => 'sign_attorney.pdf',
|
||||
'headers' => [
|
||||
'Content-Type' => '<Content-type header>'
|
||||
]
|
||||
]
|
||||
]];
|
||||
$request = new Request('POST', sprintf('%s%s%s', $url, self::API_DOCUMENT, $batch), $headers);
|
||||
$res = $this->client->sendAsync($request, $options)->wait();
|
||||
|
||||
return $this->responseHandler->setResponse($res)->getContentJsonToArray();
|
||||
}
|
||||
|
||||
public function download(string $url, string $token): BinaryStringFileResult
|
||||
{
|
||||
$params = [
|
||||
RequestOptions::HEADERS => [
|
||||
'Authorization' => $token,
|
||||
],
|
||||
];
|
||||
|
||||
$response = $this->client->get($url, $params);
|
||||
|
||||
return $this->responseHandler->setResponse($response)->getContentAsBinaryStingResult();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sign\Api;
|
||||
|
||||
class ApiParams
|
||||
{
|
||||
public function __construct(
|
||||
public string $endPointUrl
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sign\Api\Request;
|
||||
|
||||
use App\Infrastructure\Http\RequestDtoInterface;
|
||||
|
||||
class SignRequest implements RequestDtoInterface
|
||||
{
|
||||
public string $url;
|
||||
public int $batch;
|
||||
public string $apiKey;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sign\Controller;
|
||||
|
||||
use App\Sign\Api\Request\SignRequest;
|
||||
use App\Sign\SignService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
|
||||
|
||||
class SignController extends AbstractController
|
||||
{
|
||||
private const string API_KEY = 'ldskfjhgsdkl;ftghwerot239845yjsdknbgedklhhg';
|
||||
|
||||
public function __construct(
|
||||
private readonly SignService $signService
|
||||
){
|
||||
}
|
||||
|
||||
#[Route(path: '/sign', name: 'app.test', methods: ['POST'])]
|
||||
public function __invoke(Request $request, SignRequest $signRequest): Response
|
||||
{
|
||||
if ($signRequest->apiKey !== self::API_KEY) {
|
||||
throw new AccessDeniedException('доступ запрещен');
|
||||
}
|
||||
|
||||
$token = $request->server->get('HTTP_AUTHORIZATION');
|
||||
|
||||
return new JsonResponse($this->signService->signDocument($signRequest->url, $token, $signRequest->batch));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Sign;
|
||||
|
||||
use App\Infrastructure\External\Api\BinaryStringFileResult;
|
||||
use App\Sign\Api\Api;
|
||||
use App\Sign\Api\ApiParams;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class SignService
|
||||
{
|
||||
public function __construct(
|
||||
private Api $api,
|
||||
private ApiParams $apiParams
|
||||
){
|
||||
}
|
||||
public function signDocument(string $url, string $token, int $batch): array
|
||||
{
|
||||
$this->api->apiParams = $this->apiParams;
|
||||
|
||||
try {
|
||||
$document = $this->api->download($url, $token);
|
||||
|
||||
$this->sign($document->tempFileName);
|
||||
|
||||
$response = $this->api->send($this->apiParams->endPointUrl, $token, $document->tempFileName . '_sign.pdf', $batch);
|
||||
|
||||
$this->removeExistingDocument($document);
|
||||
|
||||
return $response;
|
||||
} catch (Exception $e) {
|
||||
throw new RuntimeException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function sign (string $documentUrl): void
|
||||
{
|
||||
match ($_ENV['APP_ENV']) {
|
||||
'dev' => $this->signDev($documentUrl),
|
||||
'prod' => $this->signProd($documentUrl),
|
||||
};
|
||||
}
|
||||
|
||||
private function signDev(string $documentUrl): void
|
||||
{
|
||||
exec(sprintf('cp %s %s_sign.pdf', $documentUrl, $documentUrl));
|
||||
}
|
||||
|
||||
private function signProd(string $documentUrl): void
|
||||
{
|
||||
exec(sprintf('cp %s %s.pdf', $documentUrl, $documentUrl));
|
||||
exec(sprintf('pdfcpro sign /mnt/t/%s.pdf -out /mnt/t/%s_sign.pdf -cert ${SHA} -text "\n\t\tПодписано ЭП\n\t\t{subject/cn}\n\t\tСертификат {sha1}\n\t\tДействителен от {since} до {until}\n\t\tДата {date}\n\t\t{subject/t}\n\t\t{subject/fullname}\n\t\t" -fontfile /usr/local/share/fonts/Inter-Bold.ttf -fontsize 8 -x 2 -y 2 -w 96 -h 9', $documentUrl, $documentUrl));
|
||||
}
|
||||
|
||||
private function removeExistingDocument(BinaryStringFileResult $document): void
|
||||
{
|
||||
if (file_exists($document->tempFileName)) {
|
||||
unlink($document->tempFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue