From 35d723e5fbc22ea324ce1c174f299c3794ee5d76 Mon Sep 17 00:00:00 2001 From: Pol Dellaiera Date: Tue, 8 Mar 2022 15:48:52 +0100 Subject: [PATCH] refactor: Use `StoredObjectManager`. --- .../DocGeneratorTemplateController.php | 206 +++++++++--------- .../src/Service/Wopi/ChillDocumentManager.php | 70 +----- 2 files changed, 113 insertions(+), 163 deletions(-) diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index 000fcb03a..2f64ea81e 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -11,8 +11,6 @@ declare(strict_types=1); namespace Chill\DocGeneratorBundle\Controller; -use Base64Url\Base64Url; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use Chill\DocGeneratorBundle\Context\ContextManager; use Chill\DocGeneratorBundle\Context\DocGeneratorContextWithPublicFormInterface; use Chill\DocGeneratorBundle\Context\Exception\ContextNotFoundException; @@ -21,11 +19,11 @@ use Chill\DocGeneratorBundle\GeneratorDriver\DriverInterface; use Chill\DocGeneratorBundle\GeneratorDriver\Exception\TemplateException; use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; use Chill\DocStoreBundle\Entity\StoredObject; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use Chill\MainBundle\Pagination\PaginatorFactory; use Chill\MainBundle\Serializer\Model\Collection; +use Doctrine\ORM\EntityManagerInterface; use Exception; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\TransferException; use Psr\Log\LoggerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\FileType; @@ -34,14 +32,14 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; // TODO à mettre dans services use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; -use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Throwable; +use function strlen; final class DocGeneratorTemplateController extends AbstractController { @@ -53,13 +51,13 @@ final class DocGeneratorTemplateController extends AbstractController private DriverInterface $driver; - private KernelInterface $kernel; + private EntityManagerInterface $entityManager; private LoggerInterface $logger; private PaginatorFactory $paginatorFactory; - private TempUrlGeneratorInterface $tempUrlGenerator; + private StoredObjectManagerInterface $storedObjectManager; public function __construct( ContextManager $contextManager, @@ -67,18 +65,18 @@ final class DocGeneratorTemplateController extends AbstractController DriverInterface $driver, LoggerInterface $logger, PaginatorFactory $paginatorFactory, - TempUrlGeneratorInterface $tempUrlGenerator, - KernelInterface $kernel, - HttpClientInterface $client + HttpClientInterface $client, + StoredObjectManagerInterface $storedObjectManager, + EntityManagerInterface $entityManager ) { $this->contextManager = $contextManager; $this->docGeneratorTemplateRepository = $docGeneratorTemplateRepository; $this->driver = $driver; $this->logger = $logger; $this->paginatorFactory = $paginatorFactory; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->kernel = $kernel; $this->client = $client; + $this->storedObjectManager = $storedObjectManager; + $this->entityManager = $entityManager; } /** @@ -177,8 +175,10 @@ final class DocGeneratorTemplateController extends AbstractController return $this->redirectToRoute( '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 { $context = $this->contextManager->getContextByDocGeneratorTemplate($template); } 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) { - 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 ( $context instanceof DocGeneratorContextWithPublicFormInterface @@ -235,123 +245,109 @@ final class DocGeneratorTemplateController extends AbstractController $contextGenerationData = $form->getData(); } elseif (!$form->isSubmitted() || ($form->isSubmitted() && !$form->isValid())) { $templatePath = '@ChillDocGenerator/Generator/basic_form.html.twig'; - $templateOptions = ['entity' => $entity, 'form' => $form->createView(), - 'template' => $template, 'context' => $context, ]; + $templateOptions = [ + 'entity' => $entity, 'form' => $form->createView(), + 'template' => $template, 'context' => $context, + ]; return $this->render($templatePath, $templateOptions); } } - if ($isTest && null !== $contextGenerationData['test_file']) { - /** @var File $file */ - $file = $contextGenerationData['test_file']; - $templateResource = fopen($file->getPathname(), 'rb'); + $document = $template->getFile(); + + if ($isTest && ($contextGenerationData['test_file'] instanceof File)) { + $dataDecrypted = file_get_contents($contextGenerationData['test_file']->getPathname()); } else { - $getUrlGen = $this->tempUrlGenerator->generate( - 'GET', - $template->getFile()->getFilename() - ); - - $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); + try { + $dataDecrypted = $this->storedObjectManager->read($document); + } catch (Throwable $exception) { + throw $exception; } - - 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 { - $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) { - $msg = implode("\n", $e->getErrors()); - - return new Response($msg, 400, [ - 'Content-Type' => 'text/plain', - ]); + return new Response( + implode("\n", $e->getErrors()), + 400, + [ + 'Content-Type' => 'text/plain', + ] + ); } - fclose($templateResource); - if ($isTest) { - return new StreamedResponse( - static function () use ($generatedResource) { - fpassthru($generatedResource); - fclose($generatedResource); - }, + return new Response( + $generatedResource, Response::HTTP_OK, [ 'Content-Transfer-Encoding', 'binary', 'Content-Type' => 'application/vnd.oasis.opendocument.text', - 'Content-Disposition' => sprintf('attachment; filename="%s.odt"', 'generated'), - 'Content-Length' => fstat($generatedResource)['size'], + 'Content-Disposition' => 'attachment; filename="generated.odt"', + 'Content-Length' => strlen($generatedResource), ], ); } - $genDocName = 'doc_' . sprintf('%010d', mt_rand()) . 'odt'; - - $getUrlGen = $this->tempUrlGenerator->generate( - 'PUT', - $genDocName - ); - - $client = new Client(); + /** @var StoredObject $storedObject */ + $storedObject = (new ObjectNormalizer()) + ->denormalize( + [ + 'type' => $template->getFile()->getType(), + 'filename' => sprintf('%s_odt', uniqid('doc_', true)), + ], + StoredObject::class + ); try { - $putResponse = $client->request('PUT', $getUrlGen->url, [ - 'body' => $generatedResource, - ]); + $this->storedObjectManager->write($storedObject, $generatedResource); + } catch (Throwable $exception) { + throw $exception; + } - if ($putResponse->getStatusCode() === 201) { - $em = $this->getDoctrine()->getManager(); - $storedObject = new StoredObject(); - $storedObject - ->setType($template->getFile()->getType()) - ->setFilename($genDocName); + $this->entityManager->persist($storedObject); - $em->persist($storedObject); - - try { - $context->storeGenerated($template, $storedObject, $entity, $contextGenerationData); - } catch (Exception $e) { - $this->logger->error('Could not store the associated document to entity', [ + try { + $context + ->storeGenerated( + $template, + $storedObject, + $entity, + $contextGenerationData + ); + } catch (Exception $e) { + $this + ->logger + ->error( + 'Unable to store the associated document to entity', + [ 'entityClassName' => $entityClassName, 'entityId' => $entityId, '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 new Exception('Unable to generate document.'); + $this->entityManager->flush(); + + return $this + ->redirectToRoute( + 'chill_wopi_file_edit', + [ + 'fileId' => $storedObject->getUuid(), + 'returnPath' => $request->query->get('returnPath', '/'), + ] + ); } } diff --git a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php index 19ff3c62e..7cc71188f 100644 --- a/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php +++ b/src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillDocumentManager.php @@ -11,12 +11,12 @@ declare(strict_types=1); namespace Chill\WopiBundle\Service\Wopi; -use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; use ChampsLibres\WopiLib\Contract\Entity\Document; use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface; use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface; use Chill\DocStoreBundle\Entity\StoredObject; use Chill\DocStoreBundle\Repository\StoredObjectRepository; +use Chill\DocStoreBundle\Service\StoredObjectManagerInterface; use DateTimeInterface; use Doctrine\ORM\EntityManagerInterface; use Error; @@ -28,8 +28,6 @@ use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\Mime\MimeTypes; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Contracts\HttpClient\HttpClientInterface; -use Throwable; use function strlen; @@ -39,33 +37,29 @@ final class ChillDocumentManager implements DocumentManagerInterface private EntityManagerInterface $entityManager; - private HttpClientInterface $httpClient; - private Psr17Interface $psr17; private RequestInterface $request; private StoredObjectRepository $storedObjectRepository; - private TempUrlGeneratorInterface $tempUrlGenerator; + private StoredObjectManagerInterface $storedObjectManager; public function __construct( DocumentLockManagerInterface $documentLockManager, EntityManagerInterface $entityManager, - HttpClientInterface $httpClient, - Psr17Interface $psr17, - StoredObjectRepository $storedObjectRepository, - TempUrlGeneratorInterface $tempUrlGenerator, HttpMessageFactoryInterface $httpMessageFactory, - RequestStack $requestStack + Psr17Interface $psr17, + RequestStack $requestStack, + StoredObjectManagerInterface $storedObjectManager, + StoredObjectRepository $storedObjectRepository ) { + $this->documentLockManager = $documentLockManager; $this->entityManager = $entityManager; $this->psr17 = $psr17; - $this->storedObjectRepository = $storedObjectRepository; - $this->documentLockManager = $documentLockManager; - $this->tempUrlGenerator = $tempUrlGenerator; - $this->httpClient = $httpClient; $this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest()); + $this->storedObjectManager = $storedObjectManager; + $this->storedObjectRepository = $storedObjectRepository; } public function create(array $data): Document @@ -197,18 +191,7 @@ final class ChillDocumentManager implements DocumentManagerInterface public function remove(Document $document): void { - $entityIsDeleted = false; - - try { - $this->entityManager->remove($document); - $entityIsDeleted = true; - } catch (Throwable $e) { - $entityIsDeleted = false; - } - - if (true === $entityIsDeleted) { - $this->deleteContent($document); - } + // TODO: To implement when we have a clearer view and API. } public function write(Document $document, array $properties = []): void @@ -216,42 +199,13 @@ final class ChillDocumentManager implements DocumentManagerInterface $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 { - /** @var StdClass $object */ - $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(); + return $this->storedObjectManager->read($storedObject); } private function setContent(StoredObject $storedObject, string $content): void { - // TODO: Add strict typing in champs-libres/async-uploader-bundle - /** @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.'); - } + $this->storedObjectManager->write($storedObject, $content); } }