From 471a571144ad7a3725b0cfd1137c2968cfeccffc Mon Sep 17 00:00:00 2001 From: Ambroise Maupate Date: Wed, 28 Jun 2023 18:58:01 +0200 Subject: [PATCH] feat(Log): Added Twig Log extension to generate link to entities edit pages --- .../src/Logger/DoctrineHandler.php | 148 +++++++++++------- .../src/Repository/LogRepository.php | 6 +- .../src/TwigExtension/LogExtension.php | 119 ++++++++++++++ .../AjaxControllers/AjaxNodesController.php | 28 +++- .../NodeTypes/NodeTypesController.php | 8 +- .../src/Controllers/Nodes/NodesController.php | 2 +- .../Resources/translations/messages.en.xlf | 4 + .../Resources/translations/messages.fr.xlf | 4 + .../src/Resources/translations/messages.xlf | 1 + .../Resources/views/dashboard/index.html.twig | 8 +- .../views/modules/history-item.html.twig | 16 +- 11 files changed, 259 insertions(+), 85 deletions(-) create mode 100644 lib/RoadizCoreBundle/src/TwigExtension/LogExtension.php diff --git a/lib/RoadizCoreBundle/src/Logger/DoctrineHandler.php b/lib/RoadizCoreBundle/src/Logger/DoctrineHandler.php index 94a2f40c..924d289b 100644 --- a/lib/RoadizCoreBundle/src/Logger/DoctrineHandler.php +++ b/lib/RoadizCoreBundle/src/Logger/DoctrineHandler.php @@ -9,8 +9,10 @@ use Monolog\Logger; use RZ\Roadiz\Core\AbstractEntities\PersistableInterface; use RZ\Roadiz\CoreBundle\Entity\Log; +use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use RZ\Roadiz\CoreBundle\Entity\User; +use RZ\Roadiz\Documents\Models\DocumentInterface; use RZ\Roadiz\Documents\UrlGenerators\DocumentUrlGeneratorInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -22,9 +24,9 @@ */ final class DoctrineHandler extends AbstractProcessingHandler { - protected ManagerRegistry $managerRegistry; - protected TokenStorageInterface $tokenStorage; - protected RequestStack $requestStack; + private ManagerRegistry $managerRegistry; + private TokenStorageInterface $tokenStorage; + private RequestStack $requestStack; private DocumentUrlGeneratorInterface $documentUrlGenerator; public function __construct( @@ -35,30 +37,85 @@ public function __construct( $level = Logger::INFO, $bubble = true ) { + parent::__construct($level, $bubble); $this->tokenStorage = $tokenStorage; $this->requestStack = $requestStack; $this->managerRegistry = $managerRegistry; - - parent::__construct($level, $bubble); $this->documentUrlGenerator = $documentUrlGenerator; } - /** - * @return TokenStorageInterface - */ - public function getTokenStorage(): TokenStorageInterface + protected function getThumbnailSourcePath(?DocumentInterface $thumbnail): ?string { - return $this->tokenStorage; + if (null === $thumbnail) { + return null; + } + return $this->documentUrlGenerator + ->setDocument($thumbnail) + ->setOptions([ + "fit" => "150x150", + "quality" => 70, + ]) + ->getUrl(); } - /** - * @param TokenStorageInterface $tokenStorage - * - * @return $this - */ - public function setTokenStorage(TokenStorageInterface $tokenStorage): DoctrineHandler + + protected function populateForNode(Node $value, Log $log, array &$data): void { - $this->tokenStorage = $tokenStorage; - return $this; + $log->setEntityClass(Node::class); + $log->setEntityId($value->getId()); + $data = array_merge( + $data, + [ + 'node_id' => $value->getId(), + 'entity_title' => $value->getNodeName(), + ] + ); + $nodeSource = $value->getNodeSources()->first() ?: null; + if (null !== $nodeSource) { + $data = array_merge( + $data, + [ + 'node_source_id' => $nodeSource->getId(), + 'translation_id' => $nodeSource->getTranslation()->getId(), + 'entity_title' => $nodeSource->getTitle() ?? $value->getNodeName(), + ] + ); + } + + $thumbnailSrc = $this->getThumbnailSourcePath($nodeSource?->getOneDisplayableDocument()); + if (null !== $thumbnailSrc) { + $data = array_merge( + $data, + [ + 'entity_thumbnail_src' => $thumbnailSrc, + ] + ); + } + } + + protected function populateForNodesSources(NodesSources $value, Log $log, array &$data): void + { + $log->setEntityClass(NodesSources::class); + $log->setEntityId($value->getId()); + $data = array_merge( + $data, + [ + 'node_source_id' => $value->getId(), + 'node_id' => $value->getNode()->getId(), + 'translation_id' => $value->getTranslation()->getId(), + 'entity_title' => $value->getTitle(), + ] + ); + + $thumbnail = $value->getOneDisplayableDocument(); + $thumbnailSrc = $this->getThumbnailSourcePath($thumbnail); + if (null !== $thumbnailSrc) { + $data = array_merge( + $data, + [ + 'entity_thumbnail_src' => $thumbnailSrc, + ] + ); + } } /** @@ -83,45 +140,33 @@ public function write(array $record): void if (\is_array($context)) { foreach ($context as $key => $value) { - if ($value instanceof NodesSources) { - $log->setEntityClass(NodesSources::class); - $log->setEntityId($value->getId()); - $data = array_merge( - $data, - [ - 'node_source_id' => $value->getId(), - 'node_id' => $value->getNode()->getId(), - 'translation_id' => $value->getTranslation()->getId(), - 'entity_title' => $value->getTitle(), - ] - ); - - $thumbnail = $value->getOneDisplayableDocument(); - if (null !== $thumbnail) { - $thumbnailSrc = $this->documentUrlGenerator - ->setDocument($thumbnail) - ->setOptions([ - "fit" => "150x150", - "quality" => 70, - ]) - ->getUrl(); - - $data = array_merge( - $data, - [ - 'entity_thumbnail_src' => $thumbnailSrc, - ] - ); - } + if ($value instanceof Node) { + $this->populateForNode($value, $log, $data); + } elseif ($value instanceof NodesSources) { + $this->populateForNodesSources($value, $log, $data); } elseif ($key === 'entity' && $value instanceof PersistableInterface) { $log->setEntityClass(get_class($value)); $log->setEntityId($value->getId()); + + $texteable = ['getTitle', 'getName', '__toString']; + foreach ($texteable as $method) { + if (method_exists($value, $method)) { + $data = array_merge( + $data, + [ + 'entity_title' => $value->{$method}() + ] + ); + break; + } + } } if ($value instanceof \Exception) { $data = array_merge( $data, [ - get_class($value) => $value->getMessage() + 'exception_class' => get_class($value), + 'message' => $value->getMessage() ] ); } @@ -155,10 +200,7 @@ public function write(array $record): void /* * Use available securityAuthorizationChecker to provide a valid user */ - if ( - null !== $this->getTokenStorage() && - null !== $token = $this->getTokenStorage()->getToken() - ) { + if (null !== $token = $this->tokenStorage->getToken()) { $user = $token->getUser(); if ($user instanceof UserInterface) { if ($user instanceof User) { diff --git a/lib/RoadizCoreBundle/src/Repository/LogRepository.php b/lib/RoadizCoreBundle/src/Repository/LogRepository.php index 264ccbbe..c076eaac 100644 --- a/lib/RoadizCoreBundle/src/Repository/LogRepository.php +++ b/lib/RoadizCoreBundle/src/Repository/LogRepository.php @@ -8,6 +8,7 @@ use Doctrine\ORM\Tools\Pagination\Paginator; use Doctrine\Persistence\ManagerRegistry; use RZ\Roadiz\CoreBundle\Entity\Log; +use RZ\Roadiz\CoreBundle\Entity\Node; use RZ\Roadiz\CoreBundle\Entity\NodesSources; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -31,17 +32,16 @@ public function __construct( */ public function findLatestByNodesSources(int $maxResult = 5): Paginator { - $subQb = $this->createQueryBuilder('slog'); $subQb->select($subQb->expr()->max('slog.id')) - ->andWhere($subQb->expr()->eq('slog.entityClass', ':entityClass')) + ->andWhere($subQb->expr()->in('slog.entityClass', ':entityClass')) ->addGroupBy('slog.entityId'); $qb = $this->createQueryBuilder('log'); $qb->select('log.id as id') ->andWhere($qb->expr()->in('log.id', $subQb->getQuery()->getDQL())) ->orderBy('log.datetime', 'DESC') - ->setParameter(':entityClass', NodesSources::class) + ->setParameter(':entityClass', [NodesSources::class, Node::class]) ->setMaxResults($maxResult) ; $ids = $qb->getQuery() diff --git a/lib/RoadizCoreBundle/src/TwigExtension/LogExtension.php b/lib/RoadizCoreBundle/src/TwigExtension/LogExtension.php new file mode 100644 index 00000000..abafb765 --- /dev/null +++ b/lib/RoadizCoreBundle/src/TwigExtension/LogExtension.php @@ -0,0 +1,119 @@ +security = $security; + $this->urlGenerator = $urlGenerator; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('log_entity_edit_path', [$this, 'getEditPath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]), + ]; + } + + public function getEditPath(?object $log): ?string + { + if (!($log instanceof Log)) { + return null; + } + + switch ($log->getEntityClass()) { + case Node::class: + case NodesSources::class: + if ( + $this->security->isGranted('ROLE_ACCESS_NODES') && + isset($log->getAdditionalData()['node_id']) && + isset($log->getAdditionalData()['translation_id']) + ) { + return $this->urlGenerator->generate('nodesEditSourcePage', [ + 'nodeId' => $log->getAdditionalData()['node_id'], + 'translationId' => $log->getAdditionalData()['translation_id'], + ]); + } + break; + case Tag::class: + if ( + $this->security->isGranted('ROLE_ACCESS_TAGS') + ) { + return $this->urlGenerator->generate('tagsEditPage', [ + 'tagId' => $log->getEntityId(), + ]); + } + break; + case Document::class: + if ( + $this->security->isGranted('ROLE_ACCESS_DOCUMENTS') + ) { + return $this->urlGenerator->generate('documentsEditPage', [ + 'documentId' => $log->getEntityId(), + ]); + } + break; + case User::class: + if ( + $this->security->isGranted('ROLE_ACCESS_USERS') + ) { + return $this->urlGenerator->generate('usersEditPage', [ + 'userId' => $log->getEntityId(), + ]); + } + break; + case CustomForm::class: + if ( + $this->security->isGranted('ROLE_ACCESS_CUSTOMFORMS') + ) { + return $this->urlGenerator->generate('customFormsEditPage', [ + 'id' => $log->getEntityId(), + ]); + } + break; + case Translation::class: + if ( + $this->security->isGranted('ROLE_ACCESS_TRANSLATIONS') + ) { + return $this->urlGenerator->generate('translationsEditPage', [ + 'translationId' => $log->getEntityId(), + ]); + } + break; + case Setting::class: + if ( + $this->security->isGranted('ROLE_ACCESS_SETTINGS') + ) { + return $this->urlGenerator->generate('settingsEditPage', [ + 'settingId' => $log->getEntityId(), + ]); + } + break; + + } + + return null; + } +} diff --git a/lib/Rozier/src/AjaxControllers/AjaxNodesController.php b/lib/Rozier/src/AjaxControllers/AjaxNodesController.php index ea22f6d8..54e7495d 100644 --- a/lib/Rozier/src/AjaxControllers/AjaxNodesController.php +++ b/lib/Rozier/src/AjaxControllers/AjaxNodesController.php @@ -58,7 +58,7 @@ public function __construct( * @param int $nodeId * @return JsonResponse */ - public function getTagsAction(Request $request, int $nodeId) + public function getTagsAction(Request $request, int $nodeId): JsonResponse { $this->denyAccessUnlessGranted('ROLE_ACCESS_NODES'); $tags = []; @@ -80,11 +80,11 @@ public function getTagsAction(Request $request, int $nodeId) * such as coming from node-tree widgets. * * @param Request $request - * @param int $nodeId + * @param int|string $nodeId * * @return Response JSON response */ - public function editAction(Request $request, $nodeId) + public function editAction(Request $request, int|string $nodeId): Response { /* * Validate @@ -104,6 +104,13 @@ public function editAction(Request $request, $nodeId) switch ($request->get('_action')) { case 'updatePosition': $this->updatePosition($request->request->all(), $node); + $responseArray = [ + 'statusCode' => '200', + 'status' => 'success', + 'responseText' => $this->getTranslator()->trans('node.%name%.was_moved', [ + '%name%' => $node->getNodeName(), + ]), + ]; break; case 'duplicate': $duplicator = new NodeDuplicator( @@ -121,7 +128,7 @@ public function editAction(Request $request, $nodeId) $msg = $this->getTranslator()->trans('duplicated.node.%name%', [ '%name%' => $node->getNodeName(), ]); - $this->logger->info($msg, ['source' => $newNode->getNodeSources()->first()]); + $this->logger->info($msg, ['entity' => $newNode->getNodeSources()->first()]); $responseArray = [ 'statusCode' => '200', @@ -156,7 +163,7 @@ public function editAction(Request $request, $nodeId) * @param array $parameters * @param Node $node */ - protected function updatePosition($parameters, Node $node): void + protected function updatePosition(array $parameters, Node $node): void { if ($node->isLocked()) { throw new BadRequestHttpException('Locked node cannot be moved.'); @@ -195,6 +202,11 @@ protected function updatePosition($parameters, Node $node): void $this->dispatchEvent(new NodesSourcesUpdatedEvent($nodeSource)); } + $msg = $this->getTranslator()->trans('node.%name%.was_moved', [ + '%name%' => $node->getNodeName(), + ]); + $this->logger->info($msg, ['entity' => $node->getNodeSources()->first() ?: $node]); + $this->em()->flush(); } @@ -306,14 +318,14 @@ public function statusesAction(Request $request): JsonResponse '%name%' => $node->getNodeName(), '%visible%' => $node->isVisible() ? $this->getTranslator()->trans('visible') : $this->getTranslator()->trans('invisible'), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); $this->dispatchEvent(new NodeVisibilityChangedEvent($node)); } else { $msg = $this->getTranslator()->trans('node.%name%.%field%.updated', [ '%name%' => $node->getNodeName(), '%field%' => $request->get('statusName'), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); } $this->dispatchEvent(new NodeUpdatedEvent($node)); $this->em()->flush(); @@ -357,7 +369,7 @@ protected function changeNodeStatus(Node $node, string $transition): JsonRespons '%name%' => $node->getNodeName(), '%status%' => $this->getTranslator()->trans(Node::getStatusLabel($node->getStatus())), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); return new JsonResponse( [ diff --git a/lib/Rozier/src/Controllers/NodeTypes/NodeTypesController.php b/lib/Rozier/src/Controllers/NodeTypes/NodeTypesController.php index 372bb2e8..cb8eacea 100644 --- a/lib/Rozier/src/Controllers/NodeTypes/NodeTypesController.php +++ b/lib/Rozier/src/Controllers/NodeTypes/NodeTypesController.php @@ -17,6 +17,7 @@ use Themes\Rozier\Forms\NodeTypeType; use Themes\Rozier\RozierApp; use Themes\Rozier\Utils\SessionListFilters; +use Twig\Error\RuntimeError; /** * @package Themes\Rozier\Controllers\NodeTypes @@ -61,9 +62,10 @@ public function indexAction(Request $request): Response * Return an edition form for requested node-type. * * @param Request $request - * @param int $nodeTypeId + * @param int $nodeTypeId * * @return Response + * @throws RuntimeError */ public function editAction(Request $request, int $nodeTypeId): Response { @@ -108,6 +110,7 @@ public function editAction(Request $request, int $nodeTypeId): Response * @param Request $request * * @return Response + * @throws RuntimeError */ public function addAction(Request $request): Response { @@ -143,9 +146,10 @@ public function addAction(Request $request): Response /** * @param Request $request - * @param int $nodeTypeId + * @param int $nodeTypeId * * @return Response + * @throws RuntimeError */ public function deleteAction(Request $request, int $nodeTypeId): Response { diff --git a/lib/Rozier/src/Controllers/Nodes/NodesController.php b/lib/Rozier/src/Controllers/Nodes/NodesController.php index bacdb43a..cf75a595 100644 --- a/lib/Rozier/src/Controllers/Nodes/NodesController.php +++ b/lib/Rozier/src/Controllers/Nodes/NodesController.php @@ -254,7 +254,7 @@ public function editAction(Request $request, int $nodeId, ?int $translationId = $msg = $this->getTranslator()->trans('node.%name%.updated', [ '%name%' => $node->getNodeName(), ]); - $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: null); + $this->publishConfirmMessage($request, $msg, $node->getNodeSources()->first() ?: $node); return $this->redirectToRoute( 'nodesEditPage', ['nodeId' => $node->getId()] diff --git a/lib/Rozier/src/Resources/translations/messages.en.xlf b/lib/Rozier/src/Resources/translations/messages.en.xlf index bc33be1b..4e6b94b3 100644 --- a/lib/Rozier/src/Resources/translations/messages.en.xlf +++ b/lib/Rozier/src/Resources/translations/messages.en.xlf @@ -6,6 +6,10 @@ node.%name%.updated Node "%name%" has been updated. + + node.%name%.was_moved + Node "%name%" has been moved. + node.%nodeId%.not_exists Node %nodeId% does not exists. diff --git a/lib/Rozier/src/Resources/translations/messages.fr.xlf b/lib/Rozier/src/Resources/translations/messages.fr.xlf index d8ba5e4d..4e969475 100644 --- a/lib/Rozier/src/Resources/translations/messages.fr.xlf +++ b/lib/Rozier/src/Resources/translations/messages.fr.xlf @@ -6,6 +6,10 @@ node.%name%.updated Le nœud %name% a été mis à jour. + + node.%name%.was_moved + Le nœud %name% a été déplacé. + node.%nodeId%.not_exists Le nœud %nodeId% n’existe pas. diff --git a/lib/Rozier/src/Resources/translations/messages.xlf b/lib/Rozier/src/Resources/translations/messages.xlf index 55b1b2ac..2f4ce0c4 100644 --- a/lib/Rozier/src/Resources/translations/messages.xlf +++ b/lib/Rozier/src/Resources/translations/messages.xlf @@ -3,6 +3,7 @@ node.%name%.updated + node.%name%.was_moved node.%nodeId%.not_exists node.%name%.status.%field%.updated node.has_no.field.%field% diff --git a/lib/Rozier/src/Resources/views/dashboard/index.html.twig b/lib/Rozier/src/Resources/views/dashboard/index.html.twig index d38be5e0..6ad3a8c2 100644 --- a/lib/Rozier/src/Resources/views/dashboard/index.html.twig +++ b/lib/Rozier/src/Resources/views/dashboard/index.html.twig @@ -27,13 +27,7 @@ {% apply spaceless %} {% for log in latestLogs %}
- {% if is_granted('ROLE_ACCESS_NODES') and log.additionalData.node_id and log.additionalData.translation_id %} - {% set entityPath = path("nodesEditSourcePage", { - "nodeId": log.additionalData.node_id, - "translationId": log.additionalData.translation_id - }) %} - {% endif %} - + {% set entityPath = log_entity_edit_path(log) %} {% if entityPath %} {% endif %} diff --git a/lib/Rozier/src/Resources/views/modules/history-item.html.twig b/lib/Rozier/src/Resources/views/modules/history-item.html.twig index 79869173..e1ed2e7a 100644 --- a/lib/Rozier/src/Resources/views/modules/history-item.html.twig +++ b/lib/Rozier/src/Resources/views/modules/history-item.html.twig @@ -44,18 +44,10 @@