mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2026-03-13 01:17:45 +00:00
Refactor subject conversion handling and enhance audit logging
- Replaced `Subject|array` return types with `SubjectBag` in `SubjectConverterInterface` for more robust handling. - Updated `getSubjectsForEntity` to include an optional `$includeAssociated` parameter for finer control over associated subject resolution. - Refactored `AuditEvent` to differentiate `mainSubject` from associated subjects, improving clarity in audit logging. - Introduced database schema changes to add `main_subject` and `subjects` columns in the `chill_main_audit_trail` table. - Added `SubjectBag` class for grouped subject management and implemented deduplication logic. - Updated all converters and test cases to use the new `SubjectBag` approach, ensuring compatibility. - Improved event dispatching in controllers to utilize the updated `AuditEvent` structure and refined metadata handling.
This commit is contained in:
@@ -17,6 +17,7 @@ readonly class AuditEvent
|
||||
{
|
||||
public function __construct(
|
||||
public string $action,
|
||||
public object $mainSubject,
|
||||
/**
|
||||
* @var list<object>
|
||||
*/
|
||||
|
||||
@@ -34,13 +34,10 @@ final readonly class AuditEvent2Trail implements AuditEvent2TrailInterface
|
||||
$event->description->trans($this->translator)
|
||||
: $event->description;
|
||||
|
||||
$subjects = array_reduce(
|
||||
$event->subjects,
|
||||
function (array $carry, mixed $item): array {
|
||||
return array_merge($carry, $this->subjectConverterManager->getSubjectsForEntity($item));
|
||||
},
|
||||
[]
|
||||
);
|
||||
$subjectBag = $this->subjectConverterManager->getSubjectsForEntity($event->mainSubject, true);
|
||||
foreach ($event->subjects as $target) {
|
||||
$subjectBag->append($this->subjectConverterManager->getSubjectsForEntity($target, false));
|
||||
}
|
||||
|
||||
$user = $this->security->getUser();
|
||||
|
||||
@@ -50,7 +47,8 @@ final readonly class AuditEvent2Trail implements AuditEvent2TrailInterface
|
||||
$this->clock->now(),
|
||||
$user instanceof User ? $user : null,
|
||||
$description,
|
||||
array_map(fn (Subject $subject) => $subject->asArray(), $subjects),
|
||||
$subjectBag->subject->asArray(),
|
||||
array_map(fn (Subject $subject) => $subject->asArray(), $subjectBag->associatedSubjects),
|
||||
$event->metadata,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,4 +25,26 @@ class Subject
|
||||
{
|
||||
return [...$this->identifiers, 't' => $this->type];
|
||||
}
|
||||
|
||||
public function isEqual(Subject $subject): bool
|
||||
{
|
||||
if ($subject->type !== $this->type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->identifiers as $k => $identifierValue) {
|
||||
if (!array_key_exists($k, $subject->identifiers)) {
|
||||
return false;
|
||||
}
|
||||
if ($subject->identifiers[$k] !== $identifierValue) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($this->identifiers) !== count($subject->identifiers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
39
src/Bundle/ChillMainBundle/Audit/SubjectBag.php
Normal file
39
src/Bundle/ChillMainBundle/Audit/SubjectBag.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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;
|
||||
|
||||
final class SubjectBag
|
||||
{
|
||||
public function __construct(
|
||||
public Subject $subject,
|
||||
/**
|
||||
* @var list<Subject>
|
||||
*/
|
||||
public array $associatedSubjects = [],
|
||||
) {}
|
||||
|
||||
public function append(SubjectBag $subjectBag): void
|
||||
{
|
||||
$toAppend = [$subjectBag->subject, ...$subjectBag->associatedSubjects];
|
||||
foreach ($toAppend as $subject) {
|
||||
if ($subject->isEqual($this->subject)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->associatedSubjects as $associatedSubject) {
|
||||
if ($subject->isEqual($associatedSubject)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
$this->associatedSubjects[] = $subject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,8 @@ interface SubjectConverterInterface
|
||||
{
|
||||
/**
|
||||
* @param T $subject
|
||||
*
|
||||
* @return Subject|list<Subject>
|
||||
*/
|
||||
public function convert(mixed $subject): Subject|array;
|
||||
public function convert(mixed $subject, bool $includeAssociated = false): SubjectBag;
|
||||
|
||||
/**
|
||||
* Determines whether the given subject is supported for conversion.
|
||||
|
||||
@@ -22,7 +22,7 @@ final readonly class SubjectConverterManager implements SubjectConverterManagerI
|
||||
private iterable $converters,
|
||||
) {}
|
||||
|
||||
public function getSubjectsForEntity(mixed $subject): array
|
||||
public function getSubjectsForEntity(mixed $subject, bool $includeAssociated = false): SubjectBag
|
||||
{
|
||||
foreach ($this->converters as $converter) {
|
||||
if ($converter instanceof SubjectConverterManagerAwareInterface) {
|
||||
@@ -30,9 +30,7 @@ final readonly class SubjectConverterManager implements SubjectConverterManagerI
|
||||
}
|
||||
|
||||
if ($converter->supportsConvert($subject)) {
|
||||
$subjects = $converter->convert($subject);
|
||||
|
||||
return $subjects instanceof Subject ? [$subjects] : $subjects;
|
||||
return $converter->convert($subject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,5 @@ namespace Chill\MainBundle\Audit;
|
||||
|
||||
interface SubjectConverterManagerInterface
|
||||
{
|
||||
/**
|
||||
* @return list<Subject>
|
||||
*/
|
||||
public function getSubjectsForEntity(mixed $subject): array;
|
||||
public function getSubjectsForEntity(object $subject, bool $includeAssociated = false): SubjectBag;
|
||||
}
|
||||
|
||||
@@ -26,26 +26,28 @@ class AuditTrail
|
||||
|
||||
public function __construct(
|
||||
#[ORM\Id]
|
||||
#[ORM\Column(type: 'uuid', unique: true, name: 'id')]
|
||||
#[ORM\Column(name: 'id', type: 'uuid', unique: true)]
|
||||
private UuidInterface $id,
|
||||
#[ORM\Column(type: Types::STRING, length: 255, name: 'action')]
|
||||
#[ORM\Column(name: 'action', type: Types::STRING, length: 255)]
|
||||
private string $action,
|
||||
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, name: 'occurredat')]
|
||||
#[ORM\Column(name: 'occurredat', type: Types::DATETIMETZ_IMMUTABLE)]
|
||||
private \DateTimeImmutable $occurredAt,
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true, name: 'user_id', referencedColumnName: 'id')]
|
||||
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', nullable: true)]
|
||||
private ?User $user = null,
|
||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: false, options: ['default' => ''], name: 'description')]
|
||||
#[ORM\Column(name: 'description', type: Types::STRING, nullable: false, options: ['default' => ''])]
|
||||
private string $description = '',
|
||||
#[ORM\Column(name: 'main_subject', type: Types::JSON, options: ['jsonb' => true, 'default' => "'[]'::jsonb"])]
|
||||
private array $mainSubject = [],
|
||||
/**
|
||||
* @var list<array<string, mixed>>
|
||||
*/
|
||||
#[ORM\Column(type: Types::JSON, options: ['jsonb' => true, 'default' => "'[]'::jsonb"], name: 'targets')]
|
||||
private array $targets = [],
|
||||
#[ORM\Column(name: 'subjects', type: Types::JSON, options: ['jsonb' => true, 'default' => "'[]'::jsonb"])]
|
||||
private array $subjects = [],
|
||||
/**
|
||||
* @var array<array-key, mixed>
|
||||
*/
|
||||
#[ORM\Column(type: Types::JSON, options: ['jsonb' => true, 'default' => "'[]'::jsonb"], name: 'metadata')]
|
||||
#[ORM\Column(name: 'metadata', type: Types::JSON, options: ['jsonb' => true, 'default' => "'[]'::jsonb"])]
|
||||
private array $metadata = [],
|
||||
) {}
|
||||
|
||||
@@ -77,9 +79,17 @@ class AuditTrail
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function getTargets(): array
|
||||
public function getSubjects(): array
|
||||
{
|
||||
return $this->targets;
|
||||
return $this->subjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getMainSubject(): array
|
||||
{
|
||||
return $this->mainSubject;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,8 @@ class AuditTrailRepository extends ServiceEntityRepository
|
||||
'occurredat' => ':occured_at',
|
||||
'user_id' => ':user_id',
|
||||
'description' => ':description',
|
||||
'targets' => ':targets',
|
||||
'main_subject' => ':main_subject',
|
||||
'subjects' => ':subjects',
|
||||
'metadata' => ':metadata',
|
||||
])
|
||||
->setParameter('id', $auditTrail->getId())
|
||||
@@ -50,9 +51,24 @@ class AuditTrailRepository extends ServiceEntityRepository
|
||||
->setParameter('occured_at', $auditTrail->getOccurredAt(), Types::DATETIMETZ_IMMUTABLE)
|
||||
->setParameter('user_id', $auditTrail->getUser()?->getId(), Types::INTEGER)
|
||||
->setParameter('description', $auditTrail->getDescription())
|
||||
->setParameter('targets', $auditTrail->getTargets(), Types::JSON)
|
||||
->setParameter('main_subject', $auditTrail->getMainSubject(), Types::JSON)
|
||||
->setParameter('subjects', $auditTrail->getSubjects(), Types::JSON)
|
||||
->setParameter('metadata', $auditTrail->getMetadata(), Types::JSON);
|
||||
|
||||
$sql->executeQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{} $criteria
|
||||
*
|
||||
* @return array<AuditTrail>
|
||||
*/
|
||||
public function findByCriteria(array $criteria, int $offset = 0, int $limit = 100): array
|
||||
{
|
||||
return $this->createQueryBuilder('audit')
|
||||
->orderBy('audit.occurredAt', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset)
|
||||
->getQuery()->getResult();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Chill\MainBundle\Tests\Audit;
|
||||
use Chill\MainBundle\Audit\AuditEvent;
|
||||
use Chill\MainBundle\Audit\AuditEvent2Trail;
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectBag;
|
||||
use Chill\MainBundle\Audit\SubjectConverterManagerInterface;
|
||||
use Chill\MainBundle\Entity\AuditTrail;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -44,15 +45,22 @@ class AuditEvent2TrailTest extends TestCase
|
||||
|
||||
$event = new AuditEvent(
|
||||
action: 'test_action',
|
||||
subjects: [$subject = new \stdClass()],
|
||||
mainSubject: $subject = new \stdClass(),
|
||||
description: $description->reveal(),
|
||||
metadata: ['foo' => 'bar']
|
||||
);
|
||||
|
||||
$security->getUser()->willReturn(null);
|
||||
|
||||
$subjectConverterManager->getSubjectsForEntity($subject)
|
||||
->willReturn([new Subject('stdClass', ['id' => '1'])]);
|
||||
$subjectConverterManager->getSubjectsForEntity($subject, true)
|
||||
->willReturn(
|
||||
new SubjectBag(
|
||||
new Subject('stdClass', ['id' => '1']),
|
||||
[
|
||||
new Subject('stdClass2', ['id' => '2']),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$service = new AuditEvent2Trail(
|
||||
$translator->reveal(),
|
||||
@@ -66,7 +74,8 @@ class AuditEvent2TrailTest extends TestCase
|
||||
$this->assertInstanceOf(AuditTrail::class, $trail);
|
||||
$this->assertSame('test_action', $trail->getAction());
|
||||
$this->assertSame('translated description', $trail->getDescription());
|
||||
$this->assertSame([['id' => '1', 't' => 'stdClass']], $trail->getTargets());
|
||||
$this->assertSame(['id' => '1', 't' => 'stdClass'], $trail->getMainSubject());
|
||||
$this->assertSame([['id' => '2', 't' => 'stdClass2']], $trail->getSubjects());
|
||||
$this->assertSame(['foo' => 'bar'], $trail->getMetadata());
|
||||
$this->assertEquals($clock->now(), $trail->getOccurredAt());
|
||||
}
|
||||
@@ -80,11 +89,22 @@ class AuditEvent2TrailTest extends TestCase
|
||||
|
||||
$event = new AuditEvent(
|
||||
action: 'test_action',
|
||||
mainSubject: $subject = new \stdClass(),
|
||||
subjects: [],
|
||||
description: 'string description',
|
||||
metadata: []
|
||||
);
|
||||
|
||||
$subjectConverterManager->getSubjectsForEntity($subject, true)
|
||||
->willReturn(
|
||||
new SubjectBag(
|
||||
new Subject('stdClass', ['id' => '1']),
|
||||
[
|
||||
new Subject('stdClass2', ['id' => 2]),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
$security->getUser()->willReturn(null);
|
||||
|
||||
$service = new AuditEvent2Trail(
|
||||
|
||||
78
src/Bundle/ChillMainBundle/Tests/Audit/SubjectBagTest.php
Normal file
78
src/Bundle/ChillMainBundle/Tests/Audit/SubjectBagTest.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?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\Tests\Audit;
|
||||
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectBag;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class SubjectBagTest extends TestCase
|
||||
{
|
||||
public function testAppend(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$bag = new SubjectBag($subject1);
|
||||
|
||||
$subject2 = new Subject('type2', ['id' => '2']);
|
||||
$associated1 = new Subject('type3', ['id' => '3']);
|
||||
$bag2 = new SubjectBag($subject2, [$associated1]);
|
||||
|
||||
$bag->append($bag2);
|
||||
|
||||
$this->assertCount(2, $bag->associatedSubjects);
|
||||
$this->assertSame($subject2, $bag->associatedSubjects[0]);
|
||||
$this->assertSame($associated1, $bag->associatedSubjects[1]);
|
||||
}
|
||||
|
||||
public function testAppendAvoidDuplicates(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$associated1 = new Subject('type2', ['id' => '2']);
|
||||
$bag = new SubjectBag($subject1, [$associated1]);
|
||||
|
||||
// Duplicate of associated1
|
||||
$subject2 = new Subject('type2', ['id' => '2']);
|
||||
$bag2 = new SubjectBag($subject2);
|
||||
|
||||
$bag->append($bag2);
|
||||
|
||||
$this->assertCount(1, $bag->associatedSubjects, 'Should not add duplicate of already present associated subject');
|
||||
|
||||
// Add another one
|
||||
$subject3 = new Subject('type3', ['id' => '3']);
|
||||
$bag3 = new SubjectBag($subject3, [$subject2]); // subject2 is duplicate of associated1
|
||||
|
||||
$bag->append($bag3);
|
||||
$this->assertCount(2, $bag->associatedSubjects);
|
||||
$this->assertSame($subject3, $bag->associatedSubjects[1]);
|
||||
}
|
||||
|
||||
public function testAppendAvoidDuplicateWithMainSubject(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$associated1 = new Subject('type2', ['id' => '2']);
|
||||
$bag = new SubjectBag($subject1, [$associated1]);
|
||||
|
||||
// Duplicate of associated1
|
||||
$subject2 = new Subject('type1', ['id' => '1']);
|
||||
$bag2 = new SubjectBag($subject2);
|
||||
|
||||
$bag->append($bag2);
|
||||
|
||||
$this->assertCount(1, $bag->associatedSubjects, 'Should not add duplicate of already present associated subject');
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ namespace Chill\MainBundle\Tests\Audit;
|
||||
|
||||
use Chill\MainBundle\Audit\Exception\ConvertSubjectException;
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectBag;
|
||||
use Chill\MainBundle\Audit\SubjectConverterInterface;
|
||||
use Chill\MainBundle\Audit\SubjectConverterManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -38,7 +39,7 @@ class SubjectConverterManagerTest extends TestCase
|
||||
public function testReturnsSingleSubjectWhenConverterReturnsSubject(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$auditSubject = new Subject('type', ['id' => '123']);
|
||||
$auditSubject = new SubjectBag(new Subject('type', ['id' => '123']));
|
||||
|
||||
$converter = $this->prophesize(SubjectConverterInterface::class);
|
||||
$converter->supportsConvert($subject)->willReturn(true);
|
||||
@@ -48,33 +49,13 @@ class SubjectConverterManagerTest extends TestCase
|
||||
|
||||
$result = $manager->getSubjectsForEntity($subject);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertSame(['id' => '123', 't' => 'type'], $result[0]->asArray());
|
||||
}
|
||||
|
||||
public function testReturnsListOfSubjectsWhenConverterReturnsArray(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$auditSubject1 = new Subject('type1', ['id' => '123']);
|
||||
$auditSubject2 = new Subject('type2', ['id' => '456']);
|
||||
|
||||
$converter = $this->prophesize(SubjectConverterInterface::class);
|
||||
$converter->supportsConvert($subject)->willReturn(true);
|
||||
$converter->convert($subject)->willReturn([$auditSubject1, $auditSubject2]);
|
||||
|
||||
$manager = new SubjectConverterManager([$converter->reveal()]);
|
||||
|
||||
$result = $manager->getSubjectsForEntity($subject);
|
||||
|
||||
$this->assertCount(2, $result);
|
||||
$this->assertSame(['id' => '123', 't' => 'type1'], $result[0]->asArray());
|
||||
$this->assertSame(['id' => '456', 't' => 'type2'], $result[1]->asArray());
|
||||
$this->assertSame($auditSubject, $result);
|
||||
}
|
||||
|
||||
public function testSkipsConverterThatDoesNotSupportSubject(): void
|
||||
{
|
||||
$subject = new \stdClass();
|
||||
$auditSubject = new Subject('type', ['id' => '123']);
|
||||
$auditSubject = new SubjectBag(new Subject('type', ['id' => '123']));
|
||||
|
||||
$converter1 = $this->prophesize(SubjectConverterInterface::class);
|
||||
$converter1->supportsConvert($subject)->willReturn(false);
|
||||
@@ -88,7 +69,6 @@ class SubjectConverterManagerTest extends TestCase
|
||||
|
||||
$result = $manager->getSubjectsForEntity($subject);
|
||||
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertSame(['id' => '123', 't' => 'type'], $result[0]->asArray());
|
||||
$this->assertSame($auditSubject, $result);
|
||||
}
|
||||
}
|
||||
|
||||
74
src/Bundle/ChillMainBundle/Tests/Audit/SubjectTest.php
Normal file
74
src/Bundle/ChillMainBundle/Tests/Audit/SubjectTest.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?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\Tests\Audit;
|
||||
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @covers \Chill\MainBundle\Audit\Subject
|
||||
*/
|
||||
class SubjectTest extends TestCase
|
||||
{
|
||||
public function testIsEqual(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1', 'name' => 'test']);
|
||||
$subject2 = new Subject('type1', ['id' => '1', 'name' => 'test']);
|
||||
|
||||
$this->assertTrue($subject1->isEqual($subject2));
|
||||
}
|
||||
|
||||
public function testIsEqualDifferentType(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$subject2 = new Subject('type2', ['id' => '1']);
|
||||
|
||||
$this->assertFalse($subject1->isEqual($subject2));
|
||||
}
|
||||
|
||||
public function testIsEqualDifferentIdentifierValue(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$subject2 = new Subject('type1', ['id' => '2']);
|
||||
|
||||
$this->assertFalse($subject1->isEqual($subject2));
|
||||
}
|
||||
|
||||
public function testIsEqualDifferentIdentifierKey(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1']);
|
||||
$subject2 = new Subject('type1', ['other' => '1']);
|
||||
|
||||
$this->assertFalse($subject1->isEqual($subject2));
|
||||
}
|
||||
|
||||
public function testIsEqualDifferentIdentifierCount(): void
|
||||
{
|
||||
$subject1 = new Subject('type1', ['id' => '1', 'name' => 'test']);
|
||||
$subject2 = new Subject('type1', ['id' => '1']);
|
||||
|
||||
$this->assertFalse($subject1->isEqual($subject2));
|
||||
|
||||
$subject3 = new Subject('type1', ['id' => '1']);
|
||||
$subject4 = new Subject('type1', ['id' => '1', 'name' => 'test']);
|
||||
|
||||
$this->assertFalse($subject3->isEqual($subject4));
|
||||
}
|
||||
|
||||
public function testAsArray(): void
|
||||
{
|
||||
$subject = new Subject('type1', ['id' => '1', 'name' => 'test']);
|
||||
$this->assertSame(['id' => '1', 'name' => 'test', 't' => 'type1'], $subject->asArray());
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,20 @@ final class Version20260123162433 extends AbstractMigration
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql("CREATE TABLE chill_main_audit_trail (id UUID NOT NULL, user_id INT DEFAULT NULL, action VARCHAR(255) NOT NULL, occurredAt TIMESTAMP(0) WITH TIME ZONE NOT NULL, description VARCHAR(255) DEFAULT '' NOT NULL, targets JSONB DEFAULT '[]'::jsonb NOT NULL, metadata JSONB DEFAULT '[]'::jsonb NOT NULL, PRIMARY KEY(id))");
|
||||
$this->addSql(
|
||||
<<<'SQL'
|
||||
CREATE TABLE chill_main_audit_trail (
|
||||
id UUID NOT NULL,
|
||||
user_id INT DEFAULT NULL,
|
||||
action VARCHAR(255) NOT NULL,
|
||||
occurredAt TIMESTAMP(0) WITH TIME ZONE NOT NULL,
|
||||
description TEXT DEFAULT '' NOT NULL,
|
||||
main_subject JSONB DEFAULT '[]'::jsonb NOT NULL,
|
||||
subjects JSONB DEFAULT '[]'::jsonb NOT NULL,
|
||||
metadata JSONB DEFAULT '[]'::jsonb NOT NULL,
|
||||
PRIMARY KEY(id))
|
||||
SQL
|
||||
);
|
||||
$this->addSql('CREATE INDEX IDX_331A47F4A76ED395 ON chill_main_audit_trail (user_id)');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_audit_trail.id IS \'(DC2Type:uuid)\'');
|
||||
$this->addSql('COMMENT ON COLUMN chill_main_audit_trail.occurredAt IS \'(DC2Type:datetimetz_immutable)\'');
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\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;
|
||||
@@ -24,15 +25,15 @@ class AccompanyingPeriodSubjectConverter implements SubjectConverterInterface, S
|
||||
{
|
||||
use SubjectConverterManagerAwareTrait;
|
||||
|
||||
public function convert(mixed $subject): Subject|array
|
||||
public function convert(mixed $subject, bool $includeAssociated = false): SubjectBag
|
||||
{
|
||||
$data = [new Subject('accompanying_period', ['id' => $subject->getId()])];
|
||||
$main = new SubjectBag(new Subject('accompanying_period', ['id' => $subject->getId()]));
|
||||
|
||||
foreach ($subject->getCurrentParticipations() as $participation) {
|
||||
array_push($data, ...$this->subjectConverterManager->getSubjectsForEntity($participation->getPerson()));
|
||||
$main->append($this->subjectConverterManager->getSubjectsForEntity($participation->getPerson(), false));
|
||||
}
|
||||
|
||||
return $data;
|
||||
return $main;
|
||||
}
|
||||
|
||||
public function supportsConvert(mixed $subject): bool
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\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;
|
||||
@@ -24,12 +25,12 @@ class AccompanyingPeriodWorkSubjectConverter implements SubjectConverterInterfac
|
||||
{
|
||||
use SubjectConverterManagerAwareTrait;
|
||||
|
||||
public function convert(mixed $subject): Subject|array
|
||||
public function convert(mixed $subject, bool $includeAssociated = false): SubjectBag
|
||||
{
|
||||
$data = [new Subject('accompanying_period_work', ['id' => $subject->getId()])];
|
||||
$data = new SubjectBag(new Subject('accompanying_period_work', ['id' => $subject->getId()]));
|
||||
|
||||
foreach ($subject->getPersons() as $person) {
|
||||
array_push($data, ...$this->subjectConverterManager->getSubjectsForEntity($person));
|
||||
$data->append($this->subjectConverterManager->getSubjectsForEntity($person));
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Audit\SubjectConverter;
|
||||
|
||||
use Chill\MainBundle\Audit\Subject;
|
||||
use Chill\MainBundle\Audit\SubjectBag;
|
||||
use Chill\MainBundle\Audit\SubjectConverterInterface;
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
|
||||
@@ -20,9 +21,9 @@ use Chill\PersonBundle\Entity\Person;
|
||||
*/
|
||||
class PersonSubjectConverter implements SubjectConverterInterface
|
||||
{
|
||||
public function convert(mixed $subject): Subject|array
|
||||
public function convert(mixed $subject, bool $includeAssociated = false): SubjectBag
|
||||
{
|
||||
return new Subject('person', ['id' => $subject->getId()]);
|
||||
return new SubjectBag(new Subject('person', ['id' => $subject->getId()]));
|
||||
}
|
||||
|
||||
public function supportsConvert(mixed $subject): bool
|
||||
|
||||
@@ -80,7 +80,8 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AuditEvent(
|
||||
AuditTrail::AUDIT_UPDATE,
|
||||
[$accompanyingCourse],
|
||||
$accompanyingCourse,
|
||||
[],
|
||||
new TranslatableMessage('accompanying_period.audit.close'),
|
||||
['action' => 'close']
|
||||
)
|
||||
@@ -133,7 +134,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
$em->remove($accompanyingCourse);
|
||||
$em->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_DELETE, [$accompanyingCourse]));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_DELETE, $accompanyingCourse));
|
||||
|
||||
$this->addFlash('success', $this->translator
|
||||
->trans('The accompanying course has been successfully removed.'));
|
||||
@@ -175,9 +176,9 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(
|
||||
AuditTrail::AUDIT_VIEW,
|
||||
[$accompanyingCourse],
|
||||
new TranslatableMessage('accompanying_period.audit.show_edit_page'),
|
||||
['action' => 'show_edit_page']
|
||||
$accompanyingCourse,
|
||||
description: new TranslatableMessage('accompanying_period.audit.show_edit_page'),
|
||||
metadata: ['action' => 'show_edit_page']
|
||||
));
|
||||
|
||||
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
|
||||
@@ -212,7 +213,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
{
|
||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_VIEW, [$accompanyingCourse]));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_VIEW, $accompanyingCourse));
|
||||
|
||||
// compute some warnings
|
||||
// get persons without household
|
||||
@@ -283,7 +284,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
$em->persist($period);
|
||||
$em->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_CREATE, [$period]));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_CREATE, $period));
|
||||
|
||||
return $this->redirectToRoute('chill_person_accompanying_course_edit', [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
@@ -322,7 +323,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
$em->persist($period);
|
||||
$em->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_CREATE, [$period]));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_CREATE, $period));
|
||||
|
||||
return $this->redirectToRoute('chill_person_accompanying_course_edit', [
|
||||
'accompanying_period_id' => $period->getId(),
|
||||
@@ -348,7 +349,7 @@ final class AccompanyingCourseController extends \Symfony\Bundle\FrameworkBundle
|
||||
$accompanyingCourse->reOpen();
|
||||
$this->managerRegistry->getManager()->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_UPDATE, [$accompanyingCourse], new TranslatableMessage('accompanying_period.audit.reopen'), ['action' => 'reopen']));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_UPDATE, $accompanyingCourse, description: new TranslatableMessage('accompanying_period.audit.reopen'), metadata: ['action' => 'reopen']));
|
||||
|
||||
return $this->redirectToRoute('chill_person_accompanying_course_index', [
|
||||
'accompanying_period_id' => $accompanyingCourse->getId(),
|
||||
|
||||
@@ -162,9 +162,9 @@ final class AccompanyingCourseWorkController extends AbstractController
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AuditEvent(
|
||||
AuditTrail::AUDIT_VIEW,
|
||||
[$period],
|
||||
new TranslatableMessage('accompanying_period.audit.show_list_work'),
|
||||
['action' => 'show_list_work']
|
||||
$period,
|
||||
description: new TranslatableMessage('accompanying_period.audit.show_list_work'),
|
||||
metadata: ['action' => 'show_list_work']
|
||||
)
|
||||
);
|
||||
|
||||
@@ -188,7 +188,7 @@ final class AccompanyingCourseWorkController extends AbstractController
|
||||
$this->eventDispatcher->dispatch(
|
||||
new AuditEvent(
|
||||
AuditTrail::AUDIT_VIEW,
|
||||
[$work],
|
||||
$work,
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ final class PersonController extends AbstractController
|
||||
'You are not allowed to see this person.'
|
||||
);
|
||||
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_VIEW, [$person]));
|
||||
$this->eventDispatcher->dispatch(new AuditEvent(AuditTrail::AUDIT_VIEW, $person));
|
||||
|
||||
return $this->render(
|
||||
'@ChillPerson/Person/view.html.twig',
|
||||
|
||||
Reference in New Issue
Block a user