mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-03 04:29:40 +00:00
Add audit functionality for SingleTask and integrate subject converter and displayer
- Introduced `SingleTaskSubjectConverter` for audit conversion logic and `SingleTaskSubjectDisplayer` for display logic, including a Twig template. - Integrated `TriggerAuditInterface` into `SingleTaskController` and added audit triggers for create, view, update, delete, and list actions with translatable descriptions. - Registered new audit-related services in `services.yaml` and loaded `services.yaml` in the extension. - Created unit tests to validate the behavior of the subject converter and related functionality. - Added French translations for audit messages related to `SingleTask`.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TaskBundle\Audit\Displayer;
|
||||
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectDisplayerInterface;
|
||||
use Chill\TaskBundle\Repository\SingleTaskRepository;
|
||||
use Twig\Environment;
|
||||
|
||||
final readonly class SingleTaskSubjectDisplayer implements SubjectDisplayerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private SingleTaskRepository $singleTaskRepository,
|
||||
private Environment $twig,
|
||||
) {}
|
||||
|
||||
public function supportsDisplay(Subject $subject, array $options = []): bool
|
||||
{
|
||||
return 'single_task' === $subject->type;
|
||||
}
|
||||
|
||||
public function display(Subject $subject, string $format = 'html', array $options = []): string
|
||||
{
|
||||
$task = $this->singleTaskRepository->find($subject->identifiers['id']);
|
||||
|
||||
if ('html' === $format) {
|
||||
return $this->twig->render('@ChillTask/Audit/single_task.html.twig', [
|
||||
'id' => $subject->identifiers['id'],
|
||||
'task' => $task,
|
||||
]);
|
||||
}
|
||||
|
||||
return $task ? sprintf('%s (#%d)', $task->getTitle(), $task->getId()) : sprintf('Single task #%d', $subject->identifiers['id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TaskBundle\Audit\SubjectConverter;
|
||||
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectBag;
|
||||
use Chill\MainBundle\Audit\SubjectConverterInterface;
|
||||
use Chill\MainBundle\Audit\SubjectConverterManagerAwareInterface;
|
||||
use Chill\MainBundle\Audit\SubjectConverterManagerAwareTrait;
|
||||
use Chill\TaskBundle\Entity\SingleTask;
|
||||
|
||||
/**
|
||||
* @implements SubjectConverterInterface<SingleTask>
|
||||
*/
|
||||
final class SingleTaskSubjectConverter implements SubjectConverterInterface, SubjectConverterManagerAwareInterface
|
||||
{
|
||||
use SubjectConverterManagerAwareTrait;
|
||||
|
||||
public function convert(mixed $subject, bool $includeAssociated = false): SubjectBag
|
||||
{
|
||||
$mainSubject = new Subject('single_task', ['id' => $subject->getId()]);
|
||||
$main = new SubjectBag($mainSubject);
|
||||
|
||||
if ($includeAssociated) {
|
||||
if ($subject->getPerson()) {
|
||||
$main->append($this->subjectConverterManager->getSubjectsForEntity($subject->getPerson(), false));
|
||||
} elseif ($subject->getCourse()) {
|
||||
$main->append($this->subjectConverterManager->getSubjectsForEntity($subject->getCourse(), false));
|
||||
}
|
||||
}
|
||||
|
||||
return $main;
|
||||
}
|
||||
|
||||
public function supportsConvert(mixed $subject): bool
|
||||
{
|
||||
return $subject instanceof SingleTask;
|
||||
}
|
||||
|
||||
public static function getDefaultPriority(): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\TaskBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Audit\TriggerAuditInterface;
|
||||
use Chill\MainBundle\Entity\AuditTrail;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
|
||||
@@ -60,6 +62,7 @@ final class SingleTaskController extends AbstractController
|
||||
private readonly SingleTaskRepository $singleTaskRepository,
|
||||
private readonly Security $security,
|
||||
private readonly \Doctrine\Persistence\ManagerRegistry $managerRegistry,
|
||||
private readonly TriggerAuditInterface $triggerAudit,
|
||||
) {}
|
||||
|
||||
#[Route(path: '/{_locale}/task/single-task/{id}/delete', name: 'chill_task_single_task_delete', methods: ['GET', 'POST', 'DELETE'])]
|
||||
@@ -113,6 +116,8 @@ final class SingleTaskController extends AbstractController
|
||||
if (Request::METHOD_POST === $request->getMethod()) {
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
($this->triggerAudit)(AuditTrail::AUDIT_DELETE, $task);
|
||||
|
||||
$this->logger->notice('A task has been removed', [
|
||||
'by_user' => $this->getUser()->getUsername(),
|
||||
'task_id' => $task->getId(),
|
||||
@@ -180,6 +185,8 @@ final class SingleTaskController extends AbstractController
|
||||
|
||||
$em->flush();
|
||||
|
||||
($this->triggerAudit)(AuditTrail::AUDIT_UPDATE, $task);
|
||||
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans('The task has been updated'));
|
||||
|
||||
@@ -309,6 +316,12 @@ final class SingleTaskController extends AbstractController
|
||||
): Response {
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $course);
|
||||
|
||||
($this->triggerAudit)(
|
||||
AuditTrail::AUDIT_LIST,
|
||||
$course,
|
||||
description: new \Symfony\Component\Translation\TranslatableMessage('audit.single_task.list_for_course')
|
||||
);
|
||||
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
@@ -354,6 +367,8 @@ final class SingleTaskController extends AbstractController
|
||||
): Response {
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $person);
|
||||
|
||||
($this->triggerAudit)(AuditTrail::AUDIT_LIST, $person, description: new \Symfony\Component\Translation\TranslatableMessage('audit.single_task.list_for_person'));
|
||||
|
||||
$filterOrder = $this->buildFilterOrder();
|
||||
$flags = \array_merge(
|
||||
$filterOrder->getCheckboxData('status'),
|
||||
@@ -527,6 +542,8 @@ final class SingleTaskController extends AbstractController
|
||||
|
||||
$em->flush();
|
||||
|
||||
($this->triggerAudit)(AuditTrail::AUDIT_CREATE, $task);
|
||||
|
||||
$this->addFlash('success', $this->translator->trans('The task is created'));
|
||||
|
||||
if ($request->query->has('returnPath')) {
|
||||
@@ -569,6 +586,8 @@ final class SingleTaskController extends AbstractController
|
||||
{
|
||||
$this->denyAccessUnlessGranted(TaskVoter::SHOW, $task);
|
||||
|
||||
($this->triggerAudit)(AuditTrail::AUDIT_VIEW, $task);
|
||||
|
||||
if ($task->getContext() instanceof Person) {
|
||||
$event = new PrivacyEvent($task->getContext(), [
|
||||
'element_class' => SingleTask::class,
|
||||
|
||||
@@ -42,6 +42,7 @@ class ChillTaskExtension extends Extension implements PrependExtensionInterface
|
||||
$loader->load('services/timeline.yaml');
|
||||
$loader->load('services/fixtures.yaml');
|
||||
$loader->load('services/form.yaml');
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
|
||||
public function prepend(ContainerBuilder $container)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<span>
|
||||
{% if task is not null %}
|
||||
<span>
|
||||
{{ 'audit.single_task.display'|trans({'{id}': id} ) }} -
|
||||
{{ task.title }}
|
||||
<a href="{{ path('chill_task_single_task_show', {'id': id}) }}">
|
||||
<i class="bi bi-link"></i>
|
||||
</a>
|
||||
</span>
|
||||
{% else %}
|
||||
{{ 'audit.single_task.not_found_with_id'|trans({'{id}': id}, 'messages') }}
|
||||
{% endif %}
|
||||
</span>
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TaskBundle\Tests\Audit\SubjectConverter;
|
||||
|
||||
use Chill\MainBundle\Audit\SubjectConverterManagerInterface;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TaskBundle\Audit\SubjectConverter\SingleTaskSubjectConverter;
|
||||
use Chill\TaskBundle\Entity\SingleTask;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Prophecy\PhpUnit\ProphecyTrait;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
final class SingleTaskSubjectConverterTest extends TestCase
|
||||
{
|
||||
use ProphecyTrait;
|
||||
|
||||
public function testSupportsConvert(): void
|
||||
{
|
||||
$converter = new SingleTaskSubjectConverter();
|
||||
|
||||
$this->assertTrue($converter->supportsConvert($this->createMock(SingleTask::class)));
|
||||
$this->assertFalse($converter->supportsConvert(new \stdClass()));
|
||||
}
|
||||
|
||||
public function testConvert(): void
|
||||
{
|
||||
$converter = new SingleTaskSubjectConverter();
|
||||
$manager = $this->prophesize(SubjectConverterManagerInterface::class);
|
||||
$converter->setSubjectConverterManager($manager->reveal());
|
||||
|
||||
$task = $this->prophesize(SingleTask::class);
|
||||
$task->getId()->willReturn(123);
|
||||
$task->getPerson()->willReturn(null);
|
||||
$task->getCourse()->willReturn(null);
|
||||
|
||||
$bag = $converter->convert($task->reveal(), true);
|
||||
|
||||
$this->assertSame('single_task', $bag->subject->type);
|
||||
$this->assertSame(['id' => 123], $bag->subject->identifiers);
|
||||
$this->assertCount(0, $bag->associatedSubjects);
|
||||
}
|
||||
|
||||
public function testConvertWithPerson(): void
|
||||
{
|
||||
$converter = new SingleTaskSubjectConverter();
|
||||
$manager = $this->prophesize(SubjectConverterManagerInterface::class);
|
||||
$converter->setSubjectConverterManager($manager->reveal());
|
||||
|
||||
$person = $this->prophesize(Person::class)->reveal();
|
||||
$task = $this->prophesize(SingleTask::class);
|
||||
$task->getId()->willReturn(123);
|
||||
$task->getPerson()->willReturn($person);
|
||||
|
||||
$personBag = new \Chill\MainBundle\Audit\SubjectBag(new \Chill\MainBundle\Audit\Subject('person', ['id' => 456]));
|
||||
$manager->getSubjectsForEntity($person, false)->willReturn($personBag);
|
||||
|
||||
$bag = $converter->convert($task->reveal(), true);
|
||||
|
||||
$this->assertSame('single_task', $bag->subject->type);
|
||||
$this->assertCount(1, $bag->associatedSubjects);
|
||||
$this->assertSame('person', $bag->associatedSubjects[0]->type);
|
||||
}
|
||||
|
||||
public function testConvertWithCourse(): void
|
||||
{
|
||||
$converter = new SingleTaskSubjectConverter();
|
||||
$manager = $this->prophesize(SubjectConverterManagerInterface::class);
|
||||
$converter->setSubjectConverterManager($manager->reveal());
|
||||
|
||||
$course = $this->prophesize(AccompanyingPeriod::class)->reveal();
|
||||
$task = $this->prophesize(SingleTask::class);
|
||||
$task->getId()->willReturn(123);
|
||||
$task->getPerson()->willReturn(null);
|
||||
$task->getCourse()->willReturn($course);
|
||||
|
||||
$courseBag = new \Chill\MainBundle\Audit\SubjectBag(new \Chill\MainBundle\Audit\Subject('accompanying_period', ['id' => 789]));
|
||||
$manager->getSubjectsForEntity($course, false)->willReturn($courseBag);
|
||||
|
||||
$bag = $converter->convert($task->reveal(), true);
|
||||
|
||||
$this->assertSame('single_task', $bag->subject->type);
|
||||
$this->assertCount(1, $bag->associatedSubjects);
|
||||
$this->assertSame('accompanying_period', $bag->associatedSubjects[0]->type);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,7 @@
|
||||
services:
|
||||
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
autowire: true
|
||||
|
||||
Chill\TaskBundle\Audit\:
|
||||
resource: '../Audit/*'
|
||||
|
||||
@@ -116,3 +116,9 @@ CHILL_TASK_TASK_UPDATE: Modifier une tâche
|
||||
CHILL_TASK_TASK_CREATE_FOR_COURSE: Créer une tâche pour un parcours
|
||||
CHILL_TASK_TASK_CREATE_FOR_PERSON: Créer une tâche pour un usager
|
||||
|
||||
audit:
|
||||
single_task:
|
||||
list_for_course: Liste des tâches pour un parcours
|
||||
list_for_person: Liste des tâches pour un usager
|
||||
not_found_with_id: Tâche non trouvée n°{id}
|
||||
display: Tâche n°{id}
|
||||
|
||||
Reference in New Issue
Block a user