<?php
namespace App\Controller\Workroom;
use App\Entity\User;
use App\Entity\Workroom;
use App\Entity\WorkroomArena;
use App\Entity\WorkroomFolder;
use App\Form\Project\WorkroomType;
use App\Form\Workroom\ArenaType;
use App\Form\Workroom\SectionArenaType;
use App\Form\Workroom\SectionRenameType;
use App\Form\Workroom\SectionRevisionType;
use App\Form\Workroom\SectionType;
use App\Form\Workroom\SemanticTagType;
use App\Form\Workroom\WorkroomRenameType;
use App\Form\Workroom\WorkroomRevisionType;
use App\Manager\HistoryManager;
use App\Manager\ResourceManager;
use App\Manager\ZoteroManager;
use App\Repository\WorkroomFolderRepository;
use App\Security\Voter\WorkroomArenaVoter;
use App\Security\Voter\WorkroomVoter;
use App\Service\Account\SattAccountService;
use App\Service\Common\SattFormService;
use App\Service\Workroom\SattWorkroomService;
use App\Form\Zotero\ZoteroAccessType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage;
use Symfony\Component\Routing\Annotation\Route;
use App\Service\JwtService;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Class WorkroomController.
*/
class WorkroomController extends AbstractController
{
private SattFormService $sattForm;
private EntityManagerInterface $em;
private SattWorkroomService $workroomService;
private ResourceManager $resourceManager;
private HistoryManager $historyManager;
private SattAccountService $sattAccountService;
private ZoteroManager $zoteroManager;
private JwtService $jwtService;
private TokenStorageInterface $tokenStorage;
private ParameterBagInterface $parameterBag;
private RequestStack $requestStack;
/**
* WorkroomController constructor.
*/
public function __construct(
SattFormService $sattForm,
SattWorkroomService $workroomService,
SattAccountService $sattAccountService,
ResourceManager $resourceManager,
HistoryManager $historyManager,
ZoteroManager $zoteroManager,
EntityManagerInterface $em,
JwtService $jwtService,
TokenStorageInterface $tokenStorage,
ParameterBagInterface $parameterBag,
RequestStack $requestStack
) {
$this->sattForm = $sattForm;
$this->workroomService = $workroomService;
$this->sattAccountService = $sattAccountService;
$this->resourceManager = $resourceManager;
$this->historyManager = $historyManager;
$this->zoteroManager = $zoteroManager;
$this->em = $em;
$this->jwtService = $jwtService;
$this->tokenStorage = $tokenStorage;
$this->parameterBag = $parameterBag;
$this->requestStack = $requestStack;
}
/**
* Workroom entry point.
*/
#[Route(path: '/workroom/{uuid}', name: 'workroom_index', methods: ['GET', 'POST'], options: ['expose' => true])]
public function index(Request $request, Workroom $workroom): Response
{
$this->denyAccessUnlessGranted(WorkroomVoter::WORKROOM_VIEW, $workroom);
$session = $this->requestStack->getSession();
$session->set('workroom_id', (int) $workroom->getId());
$session->set('project_id', (int) $workroom->getProject()->getId());
// Get all sections of workroom
$sections = $this->workroomService->getAllSections($workroom);
// Get first section id of workroom
$firstSectionId = $this->workroomService->getFirstSectionId($workroom);
/** @var User $user */
$user = $this->getUser();
// Add event history
$this->historyManager->setEventEnterWorkroom($user, $workroom);
$userProfileImage = null;
if ($user->getUserProfile() ? $user->getUserProfile()->getPicture() : false) {
$userProfileImage = $this->resourceManager->getPublicProfilePicture($user->getUserProfile()->getPicture());
}
// Get all workrooms of the current user
$workroomsProject = $this->workroomService->getAllWorkroomsOfUser($user, $workroom->getProject());
// Get all arenas of the current user
$arenasProject = $this->workroomService->getAllArenasOfUser($user, $workroom->getProject());
// Get role on project & workroom
$userRoleProject = $this->sattAccountService->getUserRoleProject($user, $workroom->getProject());
$userRoleWorkroom = $this->sattAccountService->getUserRoleWorkroom($user, $workroom);
$session->set('user_role_on_project', $userRoleProject);
$session->set('user_role_on_workroom', $userRoleWorkroom);
// Get data Chat
$chat = $this->workroomService->getChat($workroom);
// Get all users email Labomega
$emails = $this->sattAccountService->getEmailsLabomega($user->getEmail());
return $this->render('workroom/index.html.twig', [
'project' => $workroom->getProject(),
'workroomCurrent' => $workroom,
'workrooms' => $workroomsProject,
'sections' => $sections,
'arenas' => $arenasProject,
'firstSectionId' => $firstSectionId,
'userProfileImage' => $userProfileImage,
'user' => $user,
'emailsLabo' => $emails,
'sectionForm' => $this->createForm(SectionType::class)->createView(),
'sectionRenameForm' => $this->createForm(SectionRenameType::class)->createView(),
'sectionRevisionForm' => $this->createForm(SectionRevisionType::class)->createView(),
'workroomRevisionForm' => $this->createForm(WorkroomRevisionType::class)->createView(),
'semanticTagForm' => $this->createForm(SemanticTagType::class)->createView(),
'workroomForm' => $this->createForm(WorkroomType::class)->createView(),
'workroomRenameForm' => $this->createForm(WorkroomRenameType::class)->createView(),
'workroomArenaForm' => $this->createForm(ArenaType::class)->createView(),
'chat' => $chat,
'revisions' => $workroom->getWorkroomRevisions(),
'userRoleOnProject' => $userRoleProject,
'userRoleOnWorkroom' => $userRoleWorkroom,
]);
}
/**
* Workroom Arena entry point.
*/
#[Route(path: '/arena/{uuid}', name: 'arena.index', methods: ['GET'], options: ['expose' => true])]
public function workroomArena(WorkroomArena $workroomArena): Response
{
// Page friendly au lieu d'un 403 stack trace si user pas membre
if (!$this->isGranted(WorkroomArenaVoter::WORKROOM_ARENA_VIEW, $workroomArena)) {
$workroom = $workroomArena->getWorkroom();
$project = $workroom?->getProject();
return new Response(
'<!DOCTYPE html><html lang="fr"><head><meta charset="utf-8"><title>Accès refusé — Arène</title>'
.'<style>body{font-family:-apple-system,sans-serif;max-width:680px;margin:80px auto;padding:24px;background:#fafafa;color:#333}'
.'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}'
.'.btn.secondary{background:#888}small{color:#888;display:block;margin-top:24px;font-size:11px}</style></head>'
.'<body><h1>🔒 Accès refusé à cette arène</h1>'
.'<p>Vous n\'êtes pas membre de la workroom <strong>'.htmlspecialchars($workroom?->getName() ?? '?').'</strong> à laquelle cette arène est rattachée.</p>'
.'<p>Pour rejoindre cette arène, demandez à un membre de la workroom (ou au project manager) de vous y inviter.</p>'
.($project ? '<a href="'.$this->generateUrl('project_workrooms', ['id' => $project->getId()]).'" class="btn">Voir le projet</a>' : '')
.'<a href="javascript:history.back()" class="btn secondary">← Retour</a>'
.'<small>Arène UUID : '.htmlspecialchars($workroomArena->getUuid()).' · Workroom ID : '.($workroom?->getId() ?? '?').'</small>'
.'</body></html>',
403,
['Content-Type' => 'text/html; charset=utf-8']
);
}
$session = $this->requestStack->getSession();
$session->set('workroom_arena_id', (int) $workroomArena->getId());
// Get all sections of workroom
$sections = $this->workroomService->getAllArenaSections($workroomArena);
// Get first section id of workroom
$firstSectionId = $this->workroomService->getFirstArenaSectionId($workroomArena);
$workroom = $workroomArena->getWorkroom();
/** @var User $user */
$user = $this->getUser();
$userProfileImage = null;
if ($user->getUserProfile() ? $user->getUserProfile()->getPicture() : false) {
$userProfileImage = $this->resourceManager->getPublicProfilePicture($user->getUserProfile()->getPicture());
}
// Get all workrooms of the current user
$workroomsProject = $this->workroomService->getAllWorkroomsOfUser($user, $workroom->getProject());
// Get all arenas of the current user
$arenasProject = $this->workroomService->getAllArenasOfUser($user, $workroom->getProject());
// Get role on project & workroom
$userRoleProject = $this->sattAccountService->getUserRoleProject($user, $workroom->getProject());
$userRoleWorkroomArena = $this->sattAccountService->getUserRoleWorkroomArena($user, $workroomArena);
$session->set('user_role_on_project', $userRoleProject);
$session->set('user_role_on_workroom_arena', $userRoleWorkroomArena);
// Get data Chat Arena
$chatArena = $this->workroomService->getChatArena($workroomArena);
// Get all users email Labomega
$emails = $this->sattAccountService->getEmailsLabomega($user->getEmail());
return $this->render('workroom/arena.html.twig', [
'project' => $workroom->getProject(),
'workroomArenaCurrent' => $workroomArena,
'workroomCurrent' => $workroom,
'workrooms' => $workroomsProject,
'sections' => $sections,
'arenas' => $arenasProject,
'emailsLabo' => $emails,
'user' => $user,
'firstSectionId' => $firstSectionId,
'userProfileImage' => $userProfileImage,
'sectionForm' => $this->createForm(SectionArenaType::class)->createView(),
'sectionRenameForm' => $this->createForm(SectionRenameType::class)->createView(),
'workroomForm' => $this->createForm(WorkroomType::class)->createView(),
'semanticTagForm' => $this->createForm(SemanticTagType::class)->createView(),
'workroomArenaForm' => $this->createForm(ArenaType::class)->createView(),
'chat' => $chatArena,
'userRoleOnProject' => $userRoleProject,
'userRoleOnWorkroomArena' => $userRoleWorkroomArena,
]);
}
/**
* Revisions Update options.
*/
#[Route(path: '/revisions/updateOptions/workroom/{id}', name: 'revisions_get_select_options', methods: ['GET'], options: ['expose' => true])]
public function getRevisionsSelectOptions(Workroom $workroom, Request $request): Response
{
if ($request->isXmlHttpRequest()) {
$revisions = $workroom->getWorkroomRevisions();
return $this->render('workroom/section/revisions-select-options.html.twig', [
'revisionsList' => $revisions,
]);
}
return new Response('Not Found', Response::HTTP_NOT_FOUND);
}
/**
* Workroom library entry point.
*/
#[Route(path: '/workroom/{id}/library', name: 'workroom.library', methods: ['GET', 'POST'], requirements: ['id' => '\d+'])]
public function library(Request $request, Workroom $workroom): Response
{
$user = $this->getUser();
$viewMode = 'grid';
if ($request->get('viewMode')) {
$viewMode = $request->get('viewMode');
}
if ($request->get('filteringBy')) {
$filter = $request->get('filteringBy');
}
// Check SemanticTag form
if (!$this->sattForm->isForm(
$semanticTagForm = $this->sattForm->getSemanticTagForm(
$request, $workroom
)
)) {
return $semanticTagForm;
}
// Check WorkroomFolder form
if (!$this->sattForm->isForm(
$folderCreationForm = $this->sattForm->getWorkroomFolderCreationForm(
$request, $workroom, $user
)
)) {
return $folderCreationForm;
}
if (!$this->sattForm->isForm(
$readingCardForm = $this->sattForm->getReadingCardForm(
$request, $workroom
)
)) {
return $readingCardForm;
}
return $this->render('workroom/workroom-library.html.twig', [
'viewMode' => $viewMode,
'workroom' => $workroom,
'selected' => $request->get('filteringBy') ?? '',
// FOLDERS AND FILES
'resources' => $this->workroomService->getBaseResources($workroom, $filter ?? null),
'lastResources' => $this->workroomService->getLastResources($workroom),
'deletedResources' => $this->workroomService->getDeleteResources($workroom),
// FORMS
'readingCardForm' => $readingCardForm->createView(),
'semanticTagForm' => $semanticTagForm->createView(),
'folderCreationForm' => $folderCreationForm->createView(),
]);
}
/**
* Personal folder.
*/
#[Route(path: '/workroom/{id}/library/folder/{folderId}', name: 'workroom.folder', methods: ['GET', 'POST'], requirements: ['id' => '\d+'])]
public function folder(
Request $request,
Workroom $workroom,
WorkroomFolderRepository $workroomFolderRepository
): Response {
$user = $this->getUser();
$viewMode = 'grid';
if ($request->get('viewMode')) {
$viewMode = $request->get('viewMode');
}
if ($request->get('filteringBy')) {
$filter = $request->get('filteringBy');
}
$folder = $workroomFolderRepository->findOneById($request->attributes->get('folderId'));
// Check WorkroomFolder form
if (!$this->sattForm->isForm(
$folderCreationForm = $this->sattForm->getWorkroomFolderCreationForm(
$request, $workroom, $user, $folder
)
)) {
return $folderCreationForm;
}
if (!$this->sattForm->isForm(
$readingCardForm = $this->sattForm->getReadingCardForm(
$request, $workroom
)
)) {
return $readingCardForm;
}
$breadcrumbDefault = [$folder];
$breadcrumb = $this->sattAccountService->constructBreadcrumb($folder, $breadcrumbDefault);
return $this->render('workroom/workroom-folder.html.twig', [
'viewMode' => $viewMode,
'workroom' => $workroom,
'folder' => $folder,
'parentFolder' => $folder->getParent(),
'selected' => $request->get('filteringBy') ?? '',
'filteredViewPath' => 'account.library.filters',
'resources' => $this->workroomService->getFolderResources($workroom, $folder, $filter ?? null),
'lastResources' => $this->workroomService->getLastResources($workroom, $folder),
'deletedResources' => $this->workroomService->getDeleteResources($workroom, $folder),
'folderCreationForm' => $folderCreationForm->createView(),
'readingCardForm' => $readingCardForm->createView(),
'breadcrumb' => array_reverse($breadcrumb),
]);
}
/**
* Load Jitsi Url.
*/
#[Route(path: '/jitsi/load', name: 'load_video_jitsi', methods: ['GET'], options: ['expose' => true])]
public function getVideoJitsi(Request $request): JsonResponse
{
$workroomUuid = $request->get('uuid');
if ($request->isXmlHttpRequest()) {
return new JsonResponse(['url' => $this->workroomService->getJitsiMeetUrl($workroomUuid)], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library', name: 'load_zotero_library', methods: ['GET', 'POST'], options: ['expose' => true])]
public function getZoteroItemsCollection(Request $request): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
if ($user->getZoteroUserId() && $user->getZoteroApiKey()) {
try {
$collectionItems = $this->zoteroManager->asArray($user);
ksort($collectionItems);
$html = $this->renderView('workroom/workroom-zotero-library.html.twig', ['datas' => $collectionItems, 'key' => $request->get('key')]);
} catch (\Exception $e) {
return new JsonResponse(['error' => true, 'msg' => $e->getMessage()], Response::HTTP_OK);
}
} else {
$html = $this->renderView('workroom/workroom-zotero-library.html.twig', ['displayZoteroAccess' => true, 'form' => $this->createForm(ZoteroAccessType::class)->createView()]);
}
return new JsonResponse(['html' => $html], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library/partial-load', name: 'partial_load_zotero_library', methods: ['POST'], options: ['expose' => true])]
public function getZoteroItemsCollectionPartial(Request $request): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
if ($user->getZoteroUserId() && $user->getZoteroApiKey()) {
try {
$collectionItems = $this->zoteroManager->asArray($user, $request->get('dynamic-key'));
$html = $this->renderView('workroom/_zotero_dynamic_load_row.html.twig', ['datas' => $collectionItems, 'key' => $request->get('key'), 'selectedItem' => false]);
} catch (\Exception $e) {
return new JsonResponse(['error' => true, 'msg' => $e->getMessage()], Response::HTTP_OK);
}
} else {
$html = $this->renderView('workroom/workroom-zotero-library.html.twig', ['displayZoteroAccess' => true, 'form' => $this->createForm(ZoteroAccessType::class)->createView()]);
}
return new JsonResponse(['html' => $html], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library/access', name: 'zotero_access', methods: ['POST'], options: ['expose' => true])]
public function zoteroAccess(Request $request): RedirectResponse|JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
$user->setZoteroUserId($request->get('zotero_access')['zoteroUserId']);
$user->setZoteroApiKey($request->get('zotero_access')['zoteroApiKey']);
$this->em->persist($user);
$this->em->flush();
try {
$this->zoteroManager->asArray($user);
return new RedirectResponse($this->generateUrl('load_zotero_library'));
} catch (\Exception $e) {
$user->setZoteroUserId(null);
$user->setZoteroApiKey(null);
$this->em->persist($user);
$this->em->flush();
return new JsonResponse(['error' => true, 'msg' => $e->getMessage()], Response::HTTP_OK);
}
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library/item/details', name: 'zotero_library_item_details', methods: ['POST'], options: ['expose' => true])]
public function zoteroLibraryItemDetail(Request $request): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
$itemKey = $request->get('key');
$hash = $this->zoteroManager->getItemDetails($user, $itemKey);
$html = $this->renderView('workroom/_zotero_item_details.html.twig', ['hash' => $hash]);
return new JsonResponse(['html' => $html], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library/item/biblio', name: 'zotero_library_add_biblio', methods: ['POST'], options: ['expose' => true])]
public function zoteroLibraryItemBiblio(Request $request): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
$itemKey = $request->get('key');
$defaultLanguage = ZoteroManager::DEFAULT_LANGUAGE;
$defaultStyle = ZoteroManager::DEFAULT_STYLE;
$locales = $this->zoteroManager->getLocales();
$styles = $this->zoteroManager->getStyles($this->getParameter('kernel.project_dir'));
$biblio = $this->zoteroManager->getItemBiblio($user, $itemKey, $defaultStyle, $defaultLanguage);
$args = [
'key' => $itemKey,
'styles' => $styles,
'locales' => $locales['language-names'],
'biblio' => $biblio,
'defaultStyle' => $defaultStyle,
'defaultLanguage' => $defaultLanguage,
];
$html = $this->renderView('workroom/_zotero_item_biblio.html.twig', $args);
return new JsonResponse(['html' => $html], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: '/zotero/library/item/citation/styles', name: 'zotero_library_citation_styles', methods: ['POST'], options: ['expose' => true])]
public function zoteroLibraryItemCitationStyles(Request $request): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
$itemKey = $request->get('key');
$locale = $request->get('locale');
$style = $request->get('style');
$biblio = $this->zoteroManager->getItemBiblio($user, $itemKey, $style, $locale);
return new JsonResponse(['html' => $biblio], Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
#[Route(path: 'workroom/{id}/zotero/library/download', name: 'zotero_library_item_download', methods: ['POST'], options: ['expose' => true])]
public function zoteroLibraryItemDownload(Request $request, Workroom $workroom): JsonResponse
{
if ($request->isXmlHttpRequest()) {
$user = $this->getUser();
$dataKeys = $request->get('keys');
foreach ($dataKeys as $data) {
$response = $this->zoteroManager->downloadItem($user, $workroom, $data['key'], $data['filename']);
}
return new JsonResponse($response, Response::HTTP_OK);
}
return new JsonResponse('Not Found', Response::HTTP_NOT_FOUND);
}
}