refactor: Use StoredObjectManager.

This commit is contained in:
Pol Dellaiera 2022-03-08 15:48:52 +01:00
parent 62af980ea5
commit 35d723e5fb
No known key found for this signature in database
GPG Key ID: D476DFE9C67467CA
2 changed files with 113 additions and 163 deletions

View File

@ -11,8 +11,6 @@ declare(strict_types=1);
namespace Chill\DocGeneratorBundle\Controller; namespace Chill\DocGeneratorBundle\Controller;
use Base64Url\Base64Url;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use Chill\DocGeneratorBundle\Context\ContextManager; use Chill\DocGeneratorBundle\Context\ContextManager;
use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface;
use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException;
@ -21,11 +19,11 @@ use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface;
use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException; use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException;
use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection; use Chill\MainBundle\Serializer\Model\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Exception; use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\FileType;
@ -34,14 +32,14 @@ use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
// TODO à mettre dans services // TODO à mettre dans services
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable;
use function strlen;
final class DocGeneratorTemplateController extends AbstractController final class DocGeneratorTemplateController extends AbstractController
{ {
@ -53,13 +51,13 @@ final class DocGeneratorTemplateController extends AbstractController
private DriverInterface $driver; private DriverInterface $driver;
private KernelInterface $kernel; private EntityManagerInterface $entityManager;
private LoggerInterface $logger; private LoggerInterface $logger;
private PaginatorFactory $paginatorFactory; private PaginatorFactory $paginatorFactory;
private TempUrlGeneratorInterface $tempUrlGenerator; private StoredObjectManagerInterface $storedObjectManager;
public function __construct( public function __construct(
ContextManager $contextManager, ContextManager $contextManager,
@ -67,18 +65,18 @@ final class DocGeneratorTemplateController extends AbstractController
DriverInterface $driver, DriverInterface $driver,
LoggerInterface $logger, LoggerInterface $logger,
PaginatorFactory $paginatorFactory, PaginatorFactory $paginatorFactory,
TempUrlGeneratorInterface $tempUrlGenerator, HttpClientInterface $client,
KernelInterface $kernel, StoredObjectManagerInterface $storedObjectManager,
HttpClientInterface $client EntityManagerInterface $entityManager
) { ) {
$this->contextManager = $contextManager; $this->contextManager = $contextManager;
$this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository;
$this->driver = $driver; $this->driver = $driver;
$this->logger = $logger; $this->logger = $logger;
$this->paginatorFactory = $paginatorFactory; $this->paginatorFactory = $paginatorFactory;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->kernel = $kernel;
$this->client = $client; $this->client = $client;
$this->storedObjectManager = $storedObjectManager;
$this->entityManager = $entityManager;
} }
/** /**
@ -177,8 +175,10 @@ final class DocGeneratorTemplateController extends AbstractController
return $this->redirectToRoute( return $this->redirectToRoute(
'chill_docgenerator_test_generate_from_template', 'chill_docgenerator_test_generate_from_template',
['template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId, [
'returnPath' => $request->query->get('returnPath', '/'), ] 'template' => $template, 'entityClassName' => $entityClassName, 'entityId' => $entityId,
'returnPath' => $request->query->get('returnPath', '/'),
]
); );
} }
@ -192,16 +192,26 @@ final class DocGeneratorTemplateController extends AbstractController
try { try {
$context = $this->contextManager->getContextByDocGeneratorTemplate($template); $context = $this->contextManager->getContextByDocGeneratorTemplate($template);
} catch (ContextNotFoundException $e) { } catch (ContextNotFoundException $e) {
throw new NotFoundHttpException($e->getMessage(), $e); throw new NotFoundHttpException(
'Context not found.',
$e
);
} }
$entity = $this->getDoctrine()->getRepository($context->getEntityClass())->find($entityId); $entity = $this
->entityManager
->getRepository($context->getEntityClass())
->find($entityId);
if (null === $entity) { if (null === $entity) {
throw new NotFoundHttpException("Entity with classname {$entityClassName} and id {$entityId} is not found"); throw new NotFoundHttpException(
sprintf('Entity with classname %s and id %s is not found', $entityClassName, $entityId)
);
} }
$contextGenerationData = []; $contextGenerationData = [
'test_file' => null,
];
if ( if (
$context instanceof DocGeneratorContextWithPublicFormInterface $context instanceof DocGeneratorContextWithPublicFormInterface
@ -235,123 +245,109 @@ final class DocGeneratorTemplateController extends AbstractController
$contextGenerationData = $form->getData(); $contextGenerationData = $form->getData();
} elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) { } elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) {
$templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig'; $templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig';
$templateOptions = ['entity' => $entity, 'form' => $form->createView(), $templateOptions = [
'template' => $template, 'context' => $context, ]; 'entity' => $entity, 'form' => $form->createView(),
'template' => $template, 'context' => $context,
];
return $this->render($templatePath, $templateOptions); return $this->render($templatePath, $templateOptions);
} }
} }
if ($isTest && null !== $contextGenerationData['test_file']) { $document = $template->getFile();
/** @var File $file */
$file = $contextGenerationData['test_file']; if ($isTest && ($contextGenerationData['test_file'] instanceof File)) {
$templateResource = fopen($file->getPathname(), 'rb'); $dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname());
} else { } else {
$getUrlGen = $this->tempUrlGenerator->generate( try {
'GET', $dataDecrypted = $this->storedObjectManager->read($document);
$template->getFile()->getFilename() } catch (Throwable $exception) {
); throw $exception;
$data = $this->client->request('GET', $getUrlGen->url);
$iv = $template->getFile()->getIv(); // iv as an Array
$ivGoodFormat = pack('C*', ...$iv); // iv as a String (ok for openssl_decrypt)
$method = 'AES-256-CBC';
$key = $template->getFile()->getKeyInfos()['k'];
$keyGoodFormat = Base64Url::decode($key);
$dataDecrypted = openssl_decrypt($data->getContent(), $method, $keyGoodFormat, 1, $ivGoodFormat);
if (false === $dataDecrypted) {
throw new Exception('Error during Decrypt ', 1);
} }
if (false === $templateResource = fopen('php://memory', 'r+b')) {
$this->logger->error('Could not write data to memory');
throw new HttpException(500);
}
fwrite($templateResource, $dataDecrypted);
rewind($templateResource);
} }
$datas = $context->getData($template, $entity, $contextGenerationData);
try { try {
$generatedResource = $this->driver->generateFromResource($templateResource, $template->getFile()->getType(), $datas, $template->getFile()->getFilename()); $generatedResource = $this
->driver
->generateFromString(
$dataDecrypted,
$template->getFile()->getType(),
$context->getData($template, $entity, $contextGenerationData),
$template->getFile()->getFilename()
);
} catch (TemplateException $e) { } catch (TemplateException $e) {
$msg = implode("\n", $e->getErrors()); return new Response(
implode("\n", $e->getErrors()),
return new Response($msg, 400, [ 400,
'Content-Type' => 'text/plain', [
]); 'Content-Type' => 'text/plain',
]
);
} }
fclose($templateResource);
if ($isTest) { if ($isTest) {
return new StreamedResponse( return new Response(
static function () use ($generatedResource) { $generatedResource,
fpassthru($generatedResource);
fclose($generatedResource);
},
Response::HTTP_OK, Response::HTTP_OK,
[ [
'Content-Transfer-Encoding', 'binary', 'Content-Transfer-Encoding', 'binary',
'Content-Type' => 'application/vnd.oasis.opendocument.text', 'Content-Type' => 'application/vnd.oasis.opendocument.text',
'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'), 'Content-Disposition' => 'attachment; filename="generated.odt"',
'Content-Length' => fstat($generatedResource)['size'], 'Content-Length' => strlen($generatedResource),
], ],
); );
} }
$genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt'; /** @var StoredObject $storedObject */
$storedObject = (new ObjectNormalizer())
$getUrlGen = $this->tempUrlGenerator->generate( ->denormalize(
'PUT', [
$genDocName 'type' => $template->getFile()->getType(),
); 'filename' => sprintf('%s_odt', uniqid('doc_', true)),
],
$client = new Client(); StoredObject::class
);
try { try {
$putResponse = $client->request('PUT', $getUrlGen->url, [ $this->storedObjectManager->write($storedObject, $generatedResource);
'body' => $generatedResource, } catch (Throwable $exception) {
]); throw $exception;
}
if ($putResponse->getStatusCode() === 201) { $this->entityManager->persist($storedObject);
$em = $this->getDoctrine()->getManager();
$storedObject = new StoredObject();
$storedObject
->setType($template->getFile()->getType())
->setFilename($genDocName);
$em->persist($storedObject); try {
$context
try { ->storeGenerated(
$context->storeGenerated($template, $storedObject, $entity, $contextGenerationData); $template,
} catch (Exception $e) { $storedObject,
$this->logger->error('Could not store the associated document to entity', [ $entity,
$contextGenerationData
);
} catch (Exception $e) {
$this
->logger
->error(
'Unable to store the associated document to entity',
[
'entityClassName' => $entityClassName, 'entityClassName' => $entityClassName,
'entityId' => $entityId, 'entityId' => $entityId,
'contextKey' => $context->getName(), 'contextKey' => $context->getName(),
]); ]
);
throw $e;
}
$em->flush();
return $this->redirectToRoute('chill_wopi_file_edit', [
'fileId' => $storedObject->getUuid(),
'returnPath' => $request->query->get('returnPath', '/'),
]);
}
} catch (TransferException $e) {
throw $e; throw $e;
} }
throw new Exception('Unable to generate document.'); $this->entityManager->flush();
return $this
->redirectToRoute(
'chill_wopi_file_edit',
[
'fileId' => $storedObject->getUuid(),
'returnPath' => $request->query->get('returnPath', '/'),
]
);
} }
} }

View File

@ -11,12 +11,12 @@ declare(strict_types=1);
namespace Chill\WopiBundle\Service\Wopi; namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use ChampsLibres\WopiLib\Contract\Entity\Document; use ChampsLibres\WopiLib\Contract\Entity\Document;
use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepository; use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Error; use Error;
@ -28,8 +28,6 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable;
use function strlen; use function strlen;
@ -39,33 +37,29 @@ final class ChillDocumentManager implements DocumentManagerInterface
private EntityManagerInterface $entityManager; private EntityManagerInterface $entityManager;
private HttpClientInterface $httpClient;
private Psr17Interface $psr17; private Psr17Interface $psr17;
private RequestInterface $request; private RequestInterface $request;
private StoredObjectRepository $storedObjectRepository; private StoredObjectRepository $storedObjectRepository;
private TempUrlGeneratorInterface $tempUrlGenerator; private StoredObjectManagerInterface $storedObjectManager;
public function __construct( public function __construct(
DocumentLockManagerInterface $documentLockManager, DocumentLockManagerInterface $documentLockManager,
EntityManagerInterface $entityManager, EntityManagerInterface $entityManager,
HttpClientInterface $httpClient,
Psr17Interface $psr17,
StoredObjectRepository $storedObjectRepository,
TempUrlGeneratorInterface $tempUrlGenerator,
HttpMessageFactoryInterface $httpMessageFactory, HttpMessageFactoryInterface $httpMessageFactory,
RequestStack $requestStack Psr17Interface $psr17,
RequestStack $requestStack,
StoredObjectManagerInterface $storedObjectManager,
StoredObjectRepository $storedObjectRepository
) { ) {
$this->documentLockManager = $documentLockManager;
$this->entityManager = $entityManager; $this->entityManager = $entityManager;
$this->psr17 = $psr17; $this->psr17 = $psr17;
$this->storedObjectRepository = $storedObjectRepository;
$this->documentLockManager = $documentLockManager;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->httpClient = $httpClient;
$this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest()); $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest());
$this->storedObjectManager = $storedObjectManager;
$this->storedObjectRepository = $storedObjectRepository;
} }
public function create(array $data): Document public function create(array $data): Document
@ -197,18 +191,7 @@ final class ChillDocumentManager implements DocumentManagerInterface
public function remove(Document $document): void public function remove(Document $document): void
{ {
$entityIsDeleted = false; // TODO: To implement when we have a clearer view and API.
try {
$this->entityManager->remove($document);
$entityIsDeleted = true;
} catch (Throwable $e) {
$entityIsDeleted = false;
}
if (true === $entityIsDeleted) {
$this->deleteContent($document);
}
} }
public function write(Document $document, array $properties = []): void public function write(Document $document, array $properties = []): void
@ -216,42 +199,13 @@ final class ChillDocumentManager implements DocumentManagerInterface
$this->setContent($document, $properties['content']); $this->setContent($document, $properties['content']);
} }
private function deleteContent(StoredObject $storedObject): void
{
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename());
$response = $this->httpClient->request('DELETE', $object->url);
if (200 !== $response->getStatusCode()) {
throw new Error('Unable to delete stored object.');
}
}
private function getContent(StoredObject $storedObject): string private function getContent(StoredObject $storedObject): string
{ {
/** @var StdClass $object */ return $this->storedObjectManager->read($storedObject);
$object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename());
$response = $this->httpClient->request('GET', $object->url);
if (200 !== $response->getStatusCode()) {
throw new Error('Unable to retrieve stored object.');
}
return $response->getContent();
} }
private function setContent(StoredObject $storedObject, string $content): void private function setContent(StoredObject $storedObject, string $content): void
{ {
// TODO: Add strict typing in champs-libres/async-uploader-bundle $this->storedObjectManager->write($storedObject, $content);
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename());
$response = $this->httpClient->request('PUT', $object->url, ['body' => $content]);
if (201 !== $response->getStatusCode()) {
throw new Error('Unable to save stored object.');
}
} }
} }