source/View/Controller/ExportController.php line 68

Open in your IDE?
  1. <?php
  2. namespace App\View\Controller;
  3. use App\Domain\Model\Filemanagement\AdditionalMaterial;
  4. use App\Domain\Model\Filemanagement\Dataset;
  5. use App\Domain\Model\Study\Experiment;
  6. use Doctrine\Common\Annotations\AnnotationReader;
  7. use Doctrine\Common\Collections\Collection;
  8. use Doctrine\ORM\EntityManagerInterface;
  9. use League\Flysystem\FileNotFoundException;
  10. use League\Flysystem\Filesystem;
  11. use Psr\Log\LoggerInterface;
  12. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  13. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpFoundation\Response;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. use Symfony\Component\Serializer\Encoder\CsvEncoder;
  18. use Symfony\Component\Serializer\Encoder\JsonEncoder;
  19. use Symfony\Component\Serializer\Encoder\XmlEncoder;
  20. use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
  21. use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
  22. use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter;
  23. use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
  24. use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
  25. use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
  26. use Symfony\Component\Serializer\Serializer;
  27. use ZipArchive;
  28. /**
  29.  * @IsGranted("ROLE_USER")
  30.  */
  31. class ExportController extends AbstractController
  32. {
  33.     private LoggerInterface $logger;
  34.     private EntityManagerInterface $em;
  35.     private Serializer $serializer;
  36.     private Filesystem $filesystem;
  37.     /**
  38.      * @param LoggerInterface $logger
  39.      * @param EntityManagerInterface $em
  40.      * @param Filesystem $filesystem
  41.      */
  42.     public function __construct(LoggerInterface $loggerEntityManagerInterface $emFilesystem $filesystem)
  43.     {
  44.         $this->logger $logger;
  45.         $this->em $em;
  46.         $this->filesystem $filesystem;
  47.         $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
  48.         $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
  49.         $this->serializer = new Serializer(
  50.             [new DateTimeNormalizer(), new ObjectNormalizer($classMetadataFactory$metadataAwareNameConverter)],
  51.             ['json' => new JsonEncoder(), 'xml' => new XmlEncoder(), 'csv' => new CsvEncoder()]
  52.         );
  53.     }
  54.     /**
  55.      * @Route("/export/{uuid}", name="export_index", methods={"GET"})
  56.      *
  57.      * @param string $uuid
  58.      * @return Response
  59.      */
  60.     public function exportIndex(string $uuid): Response
  61.     {
  62.         $this->logger->debug("Enter ExportController::exportAction(GET) for UUID: $uuid");
  63.         $experiment $this->em->getRepository(Experiment::class)->find($uuid);
  64.         return $this->render('Pages/Export/export.html.twig', ['export_error' => null"experiment" => $experiment]);
  65.     }
  66.     /**
  67.      * @Route("/export/{uuid}", name="export_action", methods={"POST"})
  68.      *
  69.      * @param Request $request
  70.      * @param string $uuid
  71.      * @return Response
  72.      */
  73.     public function exportAction(Request $requeststring $uuid): Response
  74.     {
  75.         $this->logger->debug("Enter ExportController::exportAction(POST) for UUID: $uuid");
  76.         $experiment $this->em->getRepository(Experiment::class)->find($uuid);
  77.         $exportFormat $request->get('exportFormat');
  78.         $exportDataset $request->get('exportDataset');
  79.         $exportMaterial $request->get('exportMaterial');
  80.         if ($experiment) {
  81.             $zip = new ZipArchive();
  82.             $zipError false;
  83.             $zipName sys_get_temp_dir().'/'.$this->sanitizeFilename($experiment->getSettingsMetaDataGroup()->getShortName()).'.zip';
  84.             if ($zip->open($zipNameZipArchive::CREATE ZipArchive::OVERWRITE) === true) {
  85.                 $experiment->getOriginalDatasets()->clear();
  86.                 if (null !== $exportDataset && != sizeof($exportDataset)) {
  87.                     foreach ($exportDataset as $dataset) {
  88.                         $experiment->addOriginalDatasets($this->em->getRepository(Dataset::class)->find($dataset));
  89.                     }
  90.                     $zipError $this->appendDatasetToZip($experiment$exportFormat$zip) ?: $zipError;
  91.                 }
  92.                 $experiment->getAdditionalMaterials()->clear();
  93.                 if (null !== $exportMaterial && != sizeof($exportMaterial)) {
  94.                     foreach ($exportMaterial as $material) {
  95.                         $experiment->addAdditionalMaterials($this->em->getRepository(AdditionalMaterial::class)->find($material));
  96.                     }
  97.                     $zipError $this->appendMaterialToZip($experiment$zip) ?: $zipError;
  98.                 }
  99.                 if ('study' === $request->get('exportStudy')) {
  100.                     $zipError $this->appendStudyToZip($experiment$exportFormat$zip) ?: $zipError;
  101.                 }
  102.                 if (!$zipError && $zip->numFiles != 0) {
  103.                     $zip->close();
  104.                     $response = new Response(
  105.                         file_get_contents($zipName),
  106.                         Response::HTTP_OK,
  107.                         [
  108.                             'Content-Type' => 'application/zip',
  109.                             'Content-Disposition' => 'attachment; filename="'.basename($zipName).'"',
  110.                             'Content-Length' => filesize($zipName),
  111.                         ]
  112.                     );
  113.                 } else {
  114.                     $this->logger->warning("ExportController::exportAction(POST): Error during creating ZIP file: Zip file is empty or corrupt");
  115.                     $zip->addFromString('empty.txt''No file exported!');
  116.                     $zip->close();
  117.                     if ($zipError) {
  118.                         $exportError "error.export.zip.create";
  119.                     } else {
  120.                         $exportError "error.export.zip.empty";
  121.                     }
  122.                 }
  123.                 unlink($zipName);
  124.             } else {
  125.                 $this->logger->critical("ExportController::exportAction(POST): Error during creating ZIP file: Could not create temp file for export");
  126.                 $exportError "error.export.zip.tempFile";
  127.             }
  128.         } else {
  129.             $this->logger->critical("ExportController::exportAction(POST): Error during getting experiment: Experiment == null");
  130.             $exportError "error.experiment.empty";
  131.         }
  132.         return $response ?? $this->render('Pages/Export/export.html.twig', ['export_error' => $exportError ?? null"experiment" => $experiment]);
  133.     }
  134.     /**
  135.      * @param Experiment $experiment
  136.      * @param string $format
  137.      * @param ZipArchive $zip
  138.      * @return bool Error:True on failure, False on success
  139.      */
  140.     private function appendStudyToZip(Experiment $experimentstring $formatZipArchive &$zip): bool
  141.     {
  142.         $design "Experimental" === $experiment->getMethodMetaDataGroup()->getResearchDesign(
  143.         ) ? 'experimental' : ("Non-experimental" === $experiment->getMethodMetaDataGroup()->getResearchDesign() ? 'non_experimental' null);
  144.         $json $this->serializer->serialize(
  145.             $experiment,
  146.             $format,
  147.             [
  148.                 'xml_root_node_name' => 'study',
  149.                 'xml_encoding' => 'utf-8',
  150.                 'xml_format_output' => true,
  151.                 AbstractNormalizer::GROUPS => ['study'$design'dataset''material'],
  152.                 'json_encode_options' => JSON_UNESCAPED_UNICODE JSON_PRETTY_PRINT,
  153.             ]
  154.         );
  155.         return !$zip->addFromString('study.'.$format$json);
  156.     }
  157.     /**
  158.      * @param Experiment $experiment
  159.      * @param string $format
  160.      * @param ZipArchive $zip
  161.      * @return bool Error:True on failure, False on success
  162.      */
  163.     private function appendDatasetToZip(Experiment $experimentstring $formatZipArchive &$zip): bool
  164.     {
  165.         $error false;
  166.         foreach ($experiment->getOriginalDatasets() as $dataset) {
  167.             $folderName $dataset->getOriginalName();
  168.             if (str_contains($folderName'.')) {
  169.                 $folderName explode('.'$folderName)[0];
  170.             }
  171.             $folder 'datasets/'.$this->sanitizeFilename($folderName);
  172.             $error $zip->addEmptyDir($folder) ? $error true;
  173.             try {
  174.                 if ($this->filesystem->has($dataset->getStorageName())) {
  175.                     $error $zip->addFromString(
  176.                         "$folder/original_".$dataset->getOriginalName(),
  177.                         $this->filesystem->read($dataset->getStorageName())
  178.                     ) ? $error true;
  179.                 }
  180.                 if ($this->filesystem->has("matrix/{$dataset->getId()}.csv")) {
  181.                     $matrix $this->filesystem->read("matrix/{$dataset->getId()}.csv");
  182.                     if ($matrix) {
  183.                         $matrix $this->buildCSVHeader($dataset->getCodebook()).$matrix;
  184.                         $error $zip->addFromString("$folder/datamatrix.csv"$matrix) ? $error true;
  185.                     }
  186.                 }
  187.             } catch (FileNotFoundException $e) {
  188.                 $this->logger->error("ExportController::appendDatasetToZip: Error read file from filesystem: {$e->getMessage()}");
  189.                 $error true;
  190.             }
  191.             $error $zip->addFromString(
  192.                 "$folder/codebook.$format",
  193.                 $this->serializer->serialize(
  194.                     $dataset->getCodebook(),
  195.                     $format,
  196.                     [
  197.                         'xml_root_node_name' => 'codebook',
  198.                         'xml_encoding' => 'utf-8',
  199.                         'xml_format_output' => true,
  200.                         AbstractNormalizer::GROUPS => ['codebook'],
  201.                         'json_encode_options' => JSON_UNESCAPED_UNICODE JSON_PRETTY_PRINT,
  202.                     ]
  203.                 )
  204.             ) ? $error true;
  205.             if ($error) {
  206.                 break;
  207.             }
  208.         }
  209.         return $error;
  210.     }
  211.     /**
  212.      * @param Experiment $experiment
  213.      * @param ZipArchive $zip
  214.      * @return bool|mixed
  215.      */
  216.     private function appendMaterialToZip(Experiment $experimentZipArchive &$zip): bool
  217.     {
  218.         $error false;
  219.         foreach ($experiment->getAdditionalMaterials() as $material) {
  220.             if ($this->filesystem->has($material->getStorageName())) {
  221.                 try {
  222.                     $error $zip->addFromString(
  223.                         "material/{$material->getOriginalName()}",
  224.                         $this->filesystem->read($material->getStorageName())
  225.                     ) ? $error true;
  226.                 } catch (FileNotFoundException $e) {
  227.                     $this->logger->error("ExportController::appendMaterialToZip: Error read file from filesystem: {$e->getMessage()}");
  228.                     $error true;
  229.                 }
  230.             }
  231.         }
  232.         return $error;
  233.     }
  234.     /**
  235.      * @param Collection $codebook
  236.      * @return string
  237.      */
  238.     private function buildCSVHeader(Collection $codebook): string
  239.     {
  240.         $header = [];
  241.         foreach ($codebook as $var) {
  242.             $header[] = $var->getName();
  243.         }
  244.         return implode(","$header).PHP_EOL;
  245.     }
  246.     /**
  247.      * @param string|null $name
  248.      * @return string|null
  249.      */
  250.     private function sanitizeFilename(?string $name): ?string
  251.     {
  252.         $chars = array(" "'"'"'""&""/""\\""?""#""<"">""."",");
  253.         return null != $name strtolower(trim(str_replace($chars'_'$name))) : 'unnamed';
  254.     }
  255. }