WIP: render entities

This commit is contained in:
2026-02-13 19:22:08 +01:00
parent 32c5f21438
commit 22ace0c66e
20 changed files with 337 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
<?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\MainBundle\Audit\Exception;
class DisplaySubjectException extends \LogicException
{
public function __construct(array $subject)
{
parent::__construct(sprintf('Subject "%s" could not be displayed', json_encode($subject['subject'], JSON_THROW_ON_ERROR, 5)));
}
}

View File

@@ -26,6 +26,15 @@ class Subject
return [...$this->identifiers, 't' => $this->type];
}
public static function fromArray(array $subject): self
{
return new self($subject['t'], array_filter(
$subject,
static fn (string $key) => 't' !== $key,
ARRAY_FILTER_USE_KEY
));
}
public function isEqual(Subject $subject): bool
{
if ($subject->type !== $this->type) {

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\Audit;
use Chill\MainBundle\Audit\Exception\ConvertSubjectException;
use Chill\MainBundle\Audit\Exception\DisplaySubjectException;
final readonly class SubjectConverterManager implements SubjectConverterManagerInterface
{
@@ -20,6 +21,11 @@ final readonly class SubjectConverterManager implements SubjectConverterManagerI
* @var iterable<SubjectConverterInterface>
*/
private iterable $converters,
/**
* @var iterable<SubjectDisplayerInterface>
*/
private iterable $displayers,
) {}
public function getSubjectsForEntity(mixed $subject, bool $includeAssociated = false): SubjectBag
@@ -36,4 +42,15 @@ final readonly class SubjectConverterManager implements SubjectConverterManagerI
throw new ConvertSubjectException($subject);
}
public function display(Subject $subject, array $options = []): string
{
foreach ($this->displayers as $displayer) {
if ($displayer->supportsDisplay($subject, $options = [])) {
return $displayer->display($subject, $options = []);
}
}
throw new DisplaySubjectException($subject->asArray());
}
}

View File

@@ -14,4 +14,6 @@ namespace Chill\MainBundle\Audit;
interface SubjectConverterManagerInterface
{
public function getSubjectsForEntity(object $subject, bool $includeAssociated = false): SubjectBag;
public function display(Subject $subject): string;
}

View File

@@ -0,0 +1,27 @@
<?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\MainBundle\Audit;
/**
* Interface for displaying a given Subject instance as html with optional configurations.
*/
interface SubjectDisplayerInterface
{
public function supportsDisplay(Subject $subject, array $optons = []): bool;
/**
* Render a Subject as an HTML string.
*
* @return string an html string
*/
public function display(Subject $subject, array $options = []): string;
}

View File

@@ -0,0 +1,29 @@
<?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\MainBundle\Audit\Twig;
use Chill\MainBundle\Audit\Subject;
use Chill\MainBundle\Audit\SubjectConverterManagerInterface;
use Twig\Extension\RuntimeExtensionInterface;
final readonly class SubjectRenderRuntimeTwig implements RuntimeExtensionInterface
{
public function __construct(private SubjectConverterManagerInterface $subjectConverterManager) {}
public function renderAuditSubject(array|Subject $subject, array $options = []): string
{
return $this->subjectConverterManager->display(
$subject instanceof Subject ? $subject : Subject::fromArray($subject),
$options
);
}
}

View File

@@ -0,0 +1,25 @@
<?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\MainBundle\Audit\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
final class SubjectRenderTwig extends AbstractExtension
{
public function getFilters(): array
{
return [
new TwigFilter('chill_render_audit_subject', [SubjectRenderRuntimeTwig::class, 'renderAuditSubject'], ['is_safe' => ['html']]),
];
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle;
use Chill\MainBundle\Audit\SubjectConverterInterface;
use Chill\MainBundle\Audit\SubjectDisplayerInterface;
use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass;
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
@@ -67,6 +68,8 @@ class ChillMainBundle extends Bundle
->addTag('chill_main.notification_flag_provider');
$container->registerForAutoconfiguration(SubjectConverterInterface::class)
->addTag('chill_main.audit_subject_converter');
$container->registerForAutoconfiguration(SubjectDisplayerInterface::class)
->addTag('chill_main.audit_subject_displayer');
$container->addCompilerPass(new SearchableServicesCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new ConfigConsistencyCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);

View File

@@ -0,0 +1,40 @@
<?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\MainBundle\Controller;
use Chill\MainBundle\Repository\AuditTrailRepository;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Twig\Environment;
final class AuditTrailSearchController
{
public function __construct(
private Security $security,
private AuditTrailRepository $auditTrailRepository,
private FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
private Environment $twig,
) {}
#[Route('/{_locale}/main/audit-trail/list', name: 'chill_main_audit_trail_list')]
public function list(Request $request): Response
{
$auditTrails = $this->auditTrailRepository->findByCriteria([]);
return new Response($this->twig->render('@ChillMain/AuditTrail/list.html.twig', [
'auditTrails' => $auditTrails,
]));
}
}

View File

@@ -0,0 +1,21 @@
<div class="item-bloc">
<div class="item-row">
<div class="item-two-col-grid">
<div class="title">
<div>{{ audit.mainSubject|chill_render_audit_subject }} | {{ ('audit_trail.action.'~audit.action)|trans }}{% if audit.description is not empty %} | {{ audit.description }}{% endif %}</div>
{% if audit.user %}<div><span class="badge-user">{{ audit.user|chill_entity_render_box({'at_date': audit.occurredAt}) }}</span></div>{% endif %}
</div>
<div class="aside text-md-end">
{{ audit.occurredAt|format_datetime('medium', 'medium') }}
</div>
</div>
</div>
{% if audit.subjects|length > 0 %}
<div class="item-row">
<span>{{ 'audit_trail.list.associated_subject'|trans }}:&nbsp;</span>
{% for s in audit.subjects %}
{{ s|chill_render_audit_subject }}{% if not loop.last %}, {% endif %}
{% endfor %}
</div>
{% endif %}
</div>

View File

@@ -0,0 +1,13 @@
{% extends '@ChillMain/layout.html.twig' %}
{% block title 'audit_trail.list.title'|trans %}
{% block content %}
<h1>{{ block('title') }}</h1>
<div class="audit-trail list flex-table">
{% for audit in auditTrails %}
{{ include('@ChillMain/AuditTrail/_list_item.html.twig') }}
{% endfor %}
</div>
{% endblock %}

View File

@@ -10,9 +10,12 @@ services:
- '../../Audit/AuditEvent.php'
- '../../Audit/SubjectConverterManagerAwareInterface.php'
- '../../Audit/SubjectConverterManagerAwareTrait.php'
- '../../Audit/SubjectConverterInterface.php'
- '../../Audit/SubjectDisplayerInterface.php'
- '../../Audit/Exception/'
- '../../Audit/Exception/*'
Chill\MainBundle\Audit\SubjectConverterManager:
arguments:
$converters: !tagged_iterator chill_main.audit_subject_converter
$displayers: !tagged_iterator chill_main.audit_subject_displayer

View File

@@ -1011,3 +1011,13 @@ editor:
login_page:
logo_alt: "Logo de Chill"
audit_trail:
action:
view: Visualisation
update: Mise à jour
create: Création
delete: Suppression
list:
title: Liste des événements
associated_subject: Entités associées