src/Controller/Workroom/WorkroomController.php line 96

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Workroom;
  3. use App\Entity\User;
  4. use App\Entity\Workroom;
  5. use App\Entity\WorkroomArena;
  6. use App\Entity\WorkroomFolder;
  7. use App\Form\Project\WorkroomType;
  8. use App\Form\Workroom\ArenaType;
  9. use App\Form\Workroom\SectionArenaType;
  10. use App\Form\Workroom\SectionRenameType;
  11. use App\Form\Workroom\SectionRevisionType;
  12. use App\Form\Workroom\SectionType;
  13. use App\Form\Workroom\SemanticTagType;
  14. use App\Form\Workroom\WorkroomRenameType;
  15. use App\Form\Workroom\WorkroomRevisionType;
  16. use App\Manager\HistoryManager;
  17. use App\Manager\ResourceManager;
  18. use App\Manager\ZoteroManager;
  19. use App\Repository\WorkroomFolderRepository;
  20. use App\Security\Voter\WorkroomArenaVoter;
  21. use App\Security\Voter\WorkroomVoter;
  22. use App\Service\Account\SattAccountService;
  23. use App\Service\Common\SattFormService;
  24. use App\Service\Workroom\SattWorkroomService;
  25. use App\Form\Zotero\ZoteroAccessType;
  26. use Doctrine\ORM\EntityManagerInterface;
  27. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  28. use Symfony\Component\HttpFoundation\JsonResponse;
  29. use Symfony\Component\HttpFoundation\RedirectResponse;
  30. use Symfony\Component\HttpFoundation\Request;
  31. use Symfony\Component\HttpFoundation\Response;
  32. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
  33. use Symfony\Component\HttpFoundation\Session\Session;
  34. use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
  35. use Symfony\Component\Routing\Annotation\Route;
  36. use App\Service\JwtService;
  37. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  38. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  39. use Symfony\Component\HttpFoundation\RequestStack;
  40. /**
  41.  * Class WorkroomController.
  42.  */
  43. class WorkroomController extends AbstractController
  44. {
  45.     private SattFormService $sattForm;
  46.     private EntityManagerInterface $em;
  47.     private SattWorkroomService $workroomService;
  48.     private ResourceManager $resourceManager;
  49.     private HistoryManager $historyManager;
  50.     private SattAccountService $sattAccountService;
  51.     private ZoteroManager $zoteroManager;
  52.     private JwtService $jwtService;
  53.     private TokenStorageInterface $tokenStorage;
  54.     private ParameterBagInterface $parameterBag;
  55.     private RequestStack $requestStack;
  56.     /**
  57.      * WorkroomController constructor.
  58.      */
  59.     public function __construct(
  60.         SattFormService $sattForm,
  61.         SattWorkroomService $workroomService,
  62.         SattAccountService $sattAccountService,
  63.         ResourceManager $resourceManager,
  64.         HistoryManager $historyManager,
  65.         ZoteroManager $zoteroManager,
  66.         EntityManagerInterface $em,
  67.         JwtService $jwtService,
  68.         TokenStorageInterface $tokenStorage,
  69.         ParameterBagInterface $parameterBag,
  70.         RequestStack $requestStack
  71.     ) {
  72.         $this->sattForm $sattForm;
  73.         $this->workroomService $workroomService;
  74.         $this->sattAccountService $sattAccountService;
  75.         $this->resourceManager $resourceManager;
  76.         $this->historyManager $historyManager;
  77.         $this->zoteroManager $zoteroManager;
  78.         $this->em $em;
  79.         $this->jwtService $jwtService;
  80.         $this->tokenStorage $tokenStorage;
  81.         $this->parameterBag $parameterBag;
  82.         $this->requestStack $requestStack;
  83.     }
  84.     /**
  85.      * Workroom entry point.
  86.      */
  87.     #[Route(path'/workroom/{uuid}'name'workroom_index'methods: ['GET''POST'], options: ['expose' => true])]
  88.     public function index(Request $requestWorkroom $workroom): Response
  89.     {
  90.         $this->denyAccessUnlessGranted(WorkroomVoter::WORKROOM_VIEW$workroom);
  91.         $session $this->requestStack->getSession();
  92.         $session->set('workroom_id', (int) $workroom->getId());
  93.         $session->set('project_id', (int) $workroom->getProject()->getId());
  94.         // Get all sections of workroom
  95.         $sections $this->workroomService->getAllSections($workroom);
  96.         // Get first section id of workroom
  97.         $firstSectionId $this->workroomService->getFirstSectionId($workroom);
  98.         /** @var User $user */
  99.         $user $this->getUser();
  100.         // Add event history
  101.         $this->historyManager->setEventEnterWorkroom($user$workroom);
  102.         $userProfileImage null;
  103.         if ($user->getUserProfile() ? $user->getUserProfile()->getPicture() : false) {
  104.             $userProfileImage $this->resourceManager->getPublicProfilePicture($user->getUserProfile()->getPicture());
  105.         }
  106.         // Get all workrooms of the current user
  107.         $workroomsProject $this->workroomService->getAllWorkroomsOfUser($user$workroom->getProject());
  108.         // Get all arenas of the current user
  109.         $arenasProject $this->workroomService->getAllArenasOfUser($user$workroom->getProject());
  110.         // Get role on project & workroom
  111.         $userRoleProject $this->sattAccountService->getUserRoleProject($user$workroom->getProject());
  112.         $userRoleWorkroom $this->sattAccountService->getUserRoleWorkroom($user$workroom);
  113.         $session->set('user_role_on_project'$userRoleProject);
  114.         $session->set('user_role_on_workroom'$userRoleWorkroom);
  115.         // Get data Chat
  116.         $chat $this->workroomService->getChat($workroom);
  117.         // Get all users email Labomega
  118.         $emails $this->sattAccountService->getEmailsLabomega($user->getEmail());
  119.         return $this->render('workroom/index.html.twig', [
  120.             'project' => $workroom->getProject(),
  121.             'workroomCurrent' => $workroom,
  122.             'workrooms' => $workroomsProject,
  123.             'sections' => $sections,
  124.             'arenas' => $arenasProject,
  125.             'firstSectionId' => $firstSectionId,
  126.             'userProfileImage' => $userProfileImage,
  127.             'user' => $user,
  128.             'emailsLabo' => $emails,
  129.             'sectionForm' => $this->createForm(SectionType::class)->createView(),
  130.             'sectionRenameForm' => $this->createForm(SectionRenameType::class)->createView(),
  131.             'sectionRevisionForm' => $this->createForm(SectionRevisionType::class)->createView(),
  132.             'workroomRevisionForm' => $this->createForm(WorkroomRevisionType::class)->createView(),
  133.             'semanticTagForm' => $this->createForm(SemanticTagType::class)->createView(),
  134.             'workroomForm' => $this->createForm(WorkroomType::class)->createView(),
  135.             'workroomRenameForm' => $this->createForm(WorkroomRenameType::class)->createView(),
  136.             'workroomArenaForm' => $this->createForm(ArenaType::class)->createView(),
  137.             'chat' => $chat,
  138.             'revisions' => $workroom->getWorkroomRevisions(),
  139.             'userRoleOnProject' => $userRoleProject,
  140.             'userRoleOnWorkroom' => $userRoleWorkroom,
  141.         ]);
  142.     }
  143.     /**
  144.      * Workroom Arena entry point.
  145.      */
  146.     #[Route(path'/arena/{uuid}'name'arena.index'methods: ['GET'], options: ['expose' => true])]
  147.     public function workroomArena(WorkroomArena $workroomArena): Response
  148.     {
  149.         // Page friendly au lieu d'un 403 stack trace si user pas membre
  150.         if (!$this->isGranted(WorkroomArenaVoter::WORKROOM_ARENA_VIEW$workroomArena)) {
  151.             $workroom $workroomArena->getWorkroom();
  152.             $project $workroom?->getProject();
  153.             return new Response(
  154.                 '<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><title>Accès refusé — Arène</title>'
  155.                 .'<style>body{font-family:-apple-system,sans-serif;max-width:680px;margin:80px auto;padding:24px;background:#fafafa;color:#333}'
  156.                 .'h1{color:#d49000}p{line-height:1.6}.btn{display:inline-block;margin-top:14px;margin-right:8px;padding:8px 14px;background:#FF6347;color:#fff;text-decoration:none;border-radius:4px}'
  157.                 .'.btn.secondary{background:#888}small{color:#888;display:block;margin-top:24px;font-size:11px}</style></head>'
  158.                 .'<body><h1>🔒 Accès refusé à cette arène</h1>'
  159.                 .'<p>Vous n\'êtes pas membre de la workroom <strong>'.htmlspecialchars($workroom?->getName() ?? '?').'</strong> à laquelle cette arène est rattachée.</p>'
  160.                 .'<p>Pour rejoindre cette arène, demandez à un membre de la workroom (ou au project manager) de vous y inviter.</p>'
  161.                 .($project '<a href="'.$this->generateUrl('project_workrooms', ['id' => $project->getId()]).'" class="btn">Voir le projet</a>' '')
  162.                 .'<a href="javascript:history.back()" class="btn secondary">← Retour</a>'
  163.                 .'<small>Arène UUID : '.htmlspecialchars($workroomArena->getUuid()).' · Workroom ID : '.($workroom?->getId() ?? '?').'</small>'
  164.                 .'</body></html>',
  165.                 403,
  166.                 ['Content-Type' => 'text/html; charset=utf-8']
  167.             );
  168.         }
  169.         $session $this->requestStack->getSession();
  170.         $session->set('workroom_arena_id', (int) $workroomArena->getId());
  171.         // Get all sections of workroom
  172.         $sections $this->workroomService->getAllArenaSections($workroomArena);
  173.         // Get first section id of workroom
  174.         $firstSectionId $this->workroomService->getFirstArenaSectionId($workroomArena);
  175.         $workroom $workroomArena->getWorkroom();
  176.         /** @var User $user */
  177.         $user $this->getUser();
  178.         $userProfileImage null;
  179.         if ($user->getUserProfile() ? $user->getUserProfile()->getPicture() : false) {
  180.             $userProfileImage $this->resourceManager->getPublicProfilePicture($user->getUserProfile()->getPicture());
  181.         }
  182.         // Get all workrooms of the current user
  183.         $workroomsProject $this->workroomService->getAllWorkroomsOfUser($user$workroom->getProject());
  184.         // Get all arenas of the current user
  185.         $arenasProject $this->workroomService->getAllArenasOfUser($user$workroom->getProject());
  186.         // Get role on project & workroom
  187.         $userRoleProject $this->sattAccountService->getUserRoleProject($user$workroom->getProject());
  188.         $userRoleWorkroomArena $this->sattAccountService->getUserRoleWorkroomArena($user$workroomArena);
  189.         $session->set('user_role_on_project'$userRoleProject);
  190.         $session->set('user_role_on_workroom_arena'$userRoleWorkroomArena);
  191.         // Get data Chat Arena
  192.         $chatArena $this->workroomService->getChatArena($workroomArena);
  193.         // Get all users email Labomega
  194.         $emails $this->sattAccountService->getEmailsLabomega($user->getEmail());
  195.         return $this->render('workroom/arena.html.twig', [
  196.             'project' => $workroom->getProject(),
  197.             'workroomArenaCurrent' => $workroomArena,
  198.             'workroomCurrent' => $workroom,
  199.             'workrooms' => $workroomsProject,
  200.             'sections' => $sections,
  201.             'arenas' => $arenasProject,
  202.             'emailsLabo' => $emails,
  203.             'user' => $user,
  204.             'firstSectionId' => $firstSectionId,
  205.             'userProfileImage' => $userProfileImage,
  206.             'sectionForm' => $this->createForm(SectionArenaType::class)->createView(),
  207.             'sectionRenameForm' => $this->createForm(SectionRenameType::class)->createView(),
  208.             'workroomForm' => $this->createForm(WorkroomType::class)->createView(),
  209.             'semanticTagForm' => $this->createForm(SemanticTagType::class)->createView(),
  210.             'workroomArenaForm' => $this->createForm(ArenaType::class)->createView(),
  211.             'chat' => $chatArena,
  212.             'userRoleOnProject' => $userRoleProject,
  213.             'userRoleOnWorkroomArena' => $userRoleWorkroomArena,
  214.         ]);
  215.     }
  216.     /**
  217.      * Revisions Update options.
  218.      */
  219.     #[Route(path'/revisions/updateOptions/workroom/{id}'name'revisions_get_select_options'methods: ['GET'], options: ['expose' => true])]
  220.     public function getRevisionsSelectOptions(Workroom $workroomRequest $request): Response
  221.     {
  222.         if ($request->isXmlHttpRequest()) {
  223.             $revisions $workroom->getWorkroomRevisions();
  224.             return $this->render('workroom/section/revisions-select-options.html.twig', [
  225.                 'revisionsList' => $revisions,
  226.             ]);
  227.         }
  228.         return new Response('Not Found'Response::HTTP_NOT_FOUND);
  229.     }
  230.     /**
  231.      * Workroom library entry point.
  232.      */
  233.     #[Route(path'/workroom/{id}/library'name'workroom.library'methods: ['GET''POST'], requirements: ['id' => '\d+'])]
  234.     public function library(Request $requestWorkroom $workroom): Response
  235.     {
  236.         $user $this->getUser();
  237.         $viewMode 'grid';
  238.         if ($request->get('viewMode')) {
  239.             $viewMode $request->get('viewMode');
  240.         }
  241.         if ($request->get('filteringBy')) {
  242.             $filter $request->get('filteringBy');
  243.         }
  244.         // Check SemanticTag form
  245.         if (!$this->sattForm->isForm(
  246.             $semanticTagForm $this->sattForm->getSemanticTagForm(
  247.                 $request$workroom
  248.             )
  249.         )) {
  250.             return $semanticTagForm;
  251.         }
  252.         // Check WorkroomFolder form
  253.         if (!$this->sattForm->isForm(
  254.             $folderCreationForm $this->sattForm->getWorkroomFolderCreationForm(
  255.                 $request$workroom$user
  256.             )
  257.         )) {
  258.             return $folderCreationForm;
  259.         }
  260.         if (!$this->sattForm->isForm(
  261.             $readingCardForm $this->sattForm->getReadingCardForm(
  262.                 $request$workroom
  263.             )
  264.         )) {
  265.             return $readingCardForm;
  266.         }
  267.         return $this->render('workroom/workroom-library.html.twig', [
  268.             'viewMode' => $viewMode,
  269.             'workroom' => $workroom,
  270.             'selected' => $request->get('filteringBy') ?? '',
  271.             // FOLDERS AND FILES
  272.             'resources' => $this->workroomService->getBaseResources($workroom$filter ?? null),
  273.             'lastResources' => $this->workroomService->getLastResources($workroom),
  274.             'deletedResources' => $this->workroomService->getDeleteResources($workroom),
  275.             // FORMS
  276.             'readingCardForm' => $readingCardForm->createView(),
  277.             'semanticTagForm' => $semanticTagForm->createView(),
  278.             'folderCreationForm' => $folderCreationForm->createView(),
  279.         ]);
  280.     }
  281.     /**
  282.      * Personal folder.
  283.      */
  284.     #[Route(path'/workroom/{id}/library/folder/{folderId}'name'workroom.folder'methods: ['GET''POST'], requirements: ['id' => '\d+'])]
  285.     public function folder(
  286.         Request $request,
  287.         Workroom $workroom,
  288.         WorkroomFolderRepository $workroomFolderRepository
  289.     ): Response {
  290.         $user $this->getUser();
  291.         $viewMode 'grid';
  292.         if ($request->get('viewMode')) {
  293.             $viewMode $request->get('viewMode');
  294.         }
  295.         if ($request->get('filteringBy')) {
  296.             $filter $request->get('filteringBy');
  297.         }
  298.         $folder $workroomFolderRepository->findOneById($request->attributes->get('folderId'));
  299.         // Check WorkroomFolder form
  300.         if (!$this->sattForm->isForm(
  301.             $folderCreationForm $this->sattForm->getWorkroomFolderCreationForm(
  302.                 $request$workroom$user$folder
  303.             )
  304.         )) {
  305.             return $folderCreationForm;
  306.         }
  307.         if (!$this->sattForm->isForm(
  308.             $readingCardForm $this->sattForm->getReadingCardForm(
  309.                 $request$workroom
  310.             )
  311.         )) {
  312.             return $readingCardForm;
  313.         }
  314.         $breadcrumbDefault = [$folder];
  315.         $breadcrumb $this->sattAccountService->constructBreadcrumb($folder$breadcrumbDefault);
  316.         return $this->render('workroom/workroom-folder.html.twig', [
  317.             'viewMode' => $viewMode,
  318.             'workroom' => $workroom,
  319.             'folder' => $folder,
  320.             'parentFolder' => $folder->getParent(),
  321.             'selected' => $request->get('filteringBy') ?? '',
  322.             'filteredViewPath' => 'account.library.filters',
  323.             'resources' => $this->workroomService->getFolderResources($workroom$folder$filter ?? null),
  324.             'lastResources' => $this->workroomService->getLastResources($workroom$folder),
  325.             'deletedResources' => $this->workroomService->getDeleteResources($workroom$folder),
  326.             'folderCreationForm' => $folderCreationForm->createView(),
  327.             'readingCardForm' => $readingCardForm->createView(),
  328.             'breadcrumb' => array_reverse($breadcrumb),
  329.         ]);
  330.     }
  331.     /**
  332.      * Load Jitsi Url.
  333.      */
  334.     #[Route(path'/jitsi/load'name'load_video_jitsi'methods: ['GET'], options: ['expose' => true])]
  335.     public function getVideoJitsi(Request $request): JsonResponse
  336.     {
  337.         $workroomUuid $request->get('uuid');
  338.         if ($request->isXmlHttpRequest()) {
  339.             return new JsonResponse(['url' => $this->workroomService->getJitsiMeetUrl($workroomUuid)], Response::HTTP_OK);
  340.         }
  341.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  342.     }
  343.     #[Route(path'/zotero/library'name'load_zotero_library'methods: ['GET''POST'], options: ['expose' => true])]
  344.     public function getZoteroItemsCollection(Request $request): JsonResponse
  345.     {
  346.         if ($request->isXmlHttpRequest()) {
  347.             $user $this->getUser();
  348.             if ($user->getZoteroUserId() && $user->getZoteroApiKey()) {
  349.                 try {
  350.                     $collectionItems $this->zoteroManager->asArray($user);
  351.                     ksort($collectionItems);
  352.                     $html $this->renderView('workroom/workroom-zotero-library.html.twig', ['datas' => $collectionItems'key' => $request->get('key')]);
  353.                 } catch (\Exception $e) {
  354.                     return new JsonResponse(['error' => true'msg' => $e->getMessage()], Response::HTTP_OK);
  355.                 }
  356.             } else {
  357.                 $html $this->renderView('workroom/workroom-zotero-library.html.twig', ['displayZoteroAccess' => true'form' => $this->createForm(ZoteroAccessType::class)->createView()]);
  358.             }
  359.             return new JsonResponse(['html' => $html], Response::HTTP_OK);
  360.         }
  361.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  362.     }
  363.     #[Route(path'/zotero/library/partial-load'name'partial_load_zotero_library'methods: ['POST'], options: ['expose' => true])]
  364.     public function getZoteroItemsCollectionPartial(Request $request): JsonResponse
  365.     {
  366.         if ($request->isXmlHttpRequest()) {
  367.             $user $this->getUser();
  368.             if ($user->getZoteroUserId() && $user->getZoteroApiKey()) {
  369.                 try {
  370.                     $collectionItems $this->zoteroManager->asArray($user$request->get('dynamic-key'));
  371.                     $html $this->renderView('workroom/_zotero_dynamic_load_row.html.twig', ['datas' => $collectionItems'key' => $request->get('key'), 'selectedItem' => false]);
  372.                 } catch (\Exception $e) {
  373.                     return new JsonResponse(['error' => true'msg' => $e->getMessage()], Response::HTTP_OK);
  374.                 }
  375.             } else {
  376.                 $html $this->renderView('workroom/workroom-zotero-library.html.twig', ['displayZoteroAccess' => true'form' => $this->createForm(ZoteroAccessType::class)->createView()]);
  377.             }
  378.             return new JsonResponse(['html' => $html], Response::HTTP_OK);
  379.         }
  380.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  381.     }
  382.     #[Route(path'/zotero/library/access'name'zotero_access'methods: ['POST'], options: ['expose' => true])]
  383.     public function zoteroAccess(Request $request): RedirectResponse|JsonResponse
  384.     {
  385.         if ($request->isXmlHttpRequest()) {
  386.             $user $this->getUser();
  387.             $user->setZoteroUserId($request->get('zotero_access')['zoteroUserId']);
  388.             $user->setZoteroApiKey($request->get('zotero_access')['zoteroApiKey']);
  389.             $this->em->persist($user);
  390.             $this->em->flush();
  391.             try {
  392.                 $this->zoteroManager->asArray($user);
  393.                 return new RedirectResponse($this->generateUrl('load_zotero_library'));
  394.             } catch (\Exception $e) {
  395.                 $user->setZoteroUserId(null);
  396.                 $user->setZoteroApiKey(null);
  397.                 $this->em->persist($user);
  398.                 $this->em->flush();
  399.                 return new JsonResponse(['error' => true'msg' => $e->getMessage()], Response::HTTP_OK);
  400.             }
  401.         }
  402.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  403.     }
  404.     #[Route(path'/zotero/library/item/details'name'zotero_library_item_details'methods: ['POST'], options: ['expose' => true])]
  405.     public function zoteroLibraryItemDetail(Request $request): JsonResponse
  406.     {
  407.         if ($request->isXmlHttpRequest()) {
  408.             $user $this->getUser();
  409.             $itemKey $request->get('key');
  410.             $hash $this->zoteroManager->getItemDetails($user$itemKey);
  411.             $html $this->renderView('workroom/_zotero_item_details.html.twig', ['hash' => $hash]);
  412.             return new JsonResponse(['html' => $html], Response::HTTP_OK);
  413.         }
  414.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  415.     }
  416.     #[Route(path'/zotero/library/item/biblio'name'zotero_library_add_biblio'methods: ['POST'], options: ['expose' => true])]
  417.     public function zoteroLibraryItemBiblio(Request $request): JsonResponse
  418.     {
  419.         if ($request->isXmlHttpRequest()) {
  420.             $user $this->getUser();
  421.             $itemKey $request->get('key');
  422.             $defaultLanguage ZoteroManager::DEFAULT_LANGUAGE;
  423.             $defaultStyle ZoteroManager::DEFAULT_STYLE;
  424.             $locales $this->zoteroManager->getLocales();
  425.             $styles $this->zoteroManager->getStyles($this->getParameter('kernel.project_dir'));
  426.             $biblio $this->zoteroManager->getItemBiblio($user$itemKey$defaultStyle$defaultLanguage);
  427.             $args = [
  428.                 'key' => $itemKey,
  429.                 'styles' => $styles,
  430.                 'locales' => $locales['language-names'],
  431.                 'biblio' => $biblio,
  432.                 'defaultStyle' => $defaultStyle,
  433.                 'defaultLanguage' => $defaultLanguage,
  434.             ];
  435.             $html $this->renderView('workroom/_zotero_item_biblio.html.twig'$args);
  436.             return new JsonResponse(['html' => $html], Response::HTTP_OK);
  437.         }
  438.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  439.     }
  440.     #[Route(path'/zotero/library/item/citation/styles'name'zotero_library_citation_styles'methods: ['POST'], options: ['expose' => true])]
  441.     public function zoteroLibraryItemCitationStyles(Request $request): JsonResponse
  442.     {
  443.         if ($request->isXmlHttpRequest()) {
  444.             $user $this->getUser();
  445.             $itemKey $request->get('key');
  446.             $locale $request->get('locale');
  447.             $style $request->get('style');
  448.             $biblio $this->zoteroManager->getItemBiblio($user$itemKey$style$locale);
  449.             return new JsonResponse(['html' => $biblio], Response::HTTP_OK);
  450.         }
  451.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  452.     }
  453.     #[Route(path'workroom/{id}/zotero/library/download'name'zotero_library_item_download'methods: ['POST'], options: ['expose' => true])]
  454.     public function zoteroLibraryItemDownload(Request $requestWorkroom $workroom): JsonResponse
  455.     {
  456.         if ($request->isXmlHttpRequest()) {
  457.             $user $this->getUser();
  458.             $dataKeys $request->get('keys');
  459.             foreach ($dataKeys as $data) {
  460.                 $response $this->zoteroManager->downloadItem($user$workroom$data['key'], $data['filename']);
  461.             }
  462.             return new JsonResponse($responseResponse::HTTP_OK);
  463.         }
  464.         return new JsonResponse('Not Found'Response::HTTP_NOT_FOUND);
  465.     }
  466. }