From e656ec566003e8b66e6c0faabbcb579ba309afc4 Mon Sep 17 00:00:00 2001 From: smarcet Date: Tue, 11 Nov 2025 13:54:47 -0300 Subject: [PATCH 1/2] chore: revamp OTEL audit log --- app/Audit/AuditContext.php | 28 +++ app/Audit/AuditEventListener.php | 48 ++++-- app/Audit/AuditLogFormatterFactory.php | 53 ++++++ app/Audit/AuditLogOtlpStrategy.php | 217 +++++++++++------------- app/Audit/AuditLogStrategy.php | 12 +- app/Audit/AuditProvider.php | 38 +++++ app/Audit/IAuditLogFormatter.php | 10 +- app/Audit/IAuditLogFormatterFactory.php | 6 + app/Audit/Interfaces/IAuditStrategy.php | 47 +++-- config/app.php | 1 + 10 files changed, 310 insertions(+), 150 deletions(-) create mode 100644 app/Audit/AuditContext.php create mode 100644 app/Audit/AuditLogFormatterFactory.php create mode 100644 app/Audit/AuditProvider.php create mode 100644 app/Audit/IAuditLogFormatterFactory.php diff --git a/app/Audit/AuditContext.php b/app/Audit/AuditContext.php new file mode 100644 index 000000000..5150f59dc --- /dev/null +++ b/app/Audit/AuditContext.php @@ -0,0 +1,28 @@ +getUnitOfWork(); // Strategy selection based on environment configuration $strategy = $this->getAuditStrategy($em); - + $ctx = $this->buildAuditContext(); if (!$strategy) { return; // No audit strategy enabled } try { foreach ($uow->getScheduledEntityInsertions() as $entity) { - $strategy->audit($entity, [], AuditLogOtlpStrategy::EVENT_ENTITY_CREATION); + $strategy->audit($entity, [], IAuditStrategy::EVENT_ENTITY_CREATION, $ctx); } foreach ($uow->getScheduledEntityUpdates() as $entity) { - $strategy->audit($entity, $uow->getEntityChangeSet($entity), AuditLogOtlpStrategy::EVENT_ENTITY_UPDATE); + $strategy->audit($entity, $uow->getEntityChangeSet($entity), IAuditStrategy::EVENT_ENTITY_UPDATE, $ctx); } foreach ($uow->getScheduledEntityDeletions() as $entity) { - $strategy->audit($entity, [], AuditLogOtlpStrategy::EVENT_ENTITY_DELETION); + $strategy->audit($entity, [], IAuditStrategy::EVENT_ENTITY_DELETION, $ctx); } foreach ($uow->getScheduledCollectionUpdates() as $col) { - $strategy->audit($col, [], AuditLogOtlpStrategy::EVENT_COLLECTION_UPDATE); + $strategy->audit($col, [], IAuditStrategy::EVENT_COLLECTION_UPDATE, $ctx); } } catch (\Exception $e) { Log::error('Audit event listener failed', [ @@ -78,9 +75,36 @@ private function getAuditStrategy($em) ]); } } - + // Use database strategy (either as default or fallback) return new AuditLogStrategy($em); + } + + private function buildAuditContext(): AuditContext + { + $resourceCtx = app(\models\oauth2\IResourceServerContext::class); + $userExternalId = $resourceCtx->getCurrentUserId(); + $member = null; + if ($userExternalId) { + $memberRepo = app(\models\main\IMemberRepository::class); + $member = $memberRepo->findOneBy(["user_external_id" => $userExternalId]); + } + + //$ui = app()->bound('ui.context') ? app('ui.context') : []; + + $req = request(); + return new AuditContext( + userId: $member?->getId(), + userEmail: $member?->getEmail(), + userFirstName: $member?->getFirstName(), + userLastName: $member?->getLastName(), + uiApp: $ui['app'] ?? null, + uiFlow: $ui['flow'] ?? null, + route: $req?->path(), + httpMethod: $req?->method(), + clientIp: $req?->ip(), + userAgent: $req?->userAgent(), + ); } } diff --git a/app/Audit/AuditLogFormatterFactory.php b/app/Audit/AuditLogFormatterFactory.php new file mode 100644 index 000000000..776f77f7e --- /dev/null +++ b/app/Audit/AuditLogFormatterFactory.php @@ -0,0 +1,53 @@ + 0) { + $child_entity = $subject[0]; + } + if (is_null($child_entity) && count($subject->getSnapshot()) > 0) { + $child_entity = $subject->getSnapshot()[0]; + } + $child_entity_formatter = $child_entity != null ? ChildEntityFormatterFactory::build($child_entity) : null; + $formatter = new EntityCollectionUpdateAuditLogFormatter($child_entity_formatter); + break; + case IAuditStrategy::EVENT_ENTITY_CREATION: + $formatter = new EntityCreationAuditLogFormatter(); + break; + case IAuditStrategy::EVENT_ENTITY_DELETION: + $child_entity_formatter = ChildEntityFormatterFactory::build($subject); + $formatter = new EntityDeletionAuditLogFormatter($child_entity_formatter); + break; + case IAuditStrategy::EVENT_ENTITY_UPDATE: + $child_entity_formatter = ChildEntityFormatterFactory::build($subject); + $formatter = new EntityUpdateAuditLogFormatter($child_entity_formatter); + break; + } + return $formatter; + } +} diff --git a/app/Audit/AuditLogOtlpStrategy.php b/app/Audit/AuditLogOtlpStrategy.php index 6b3b5038d..ca30741f8 100644 --- a/app/Audit/AuditLogOtlpStrategy.php +++ b/app/Audit/AuditLogOtlpStrategy.php @@ -1,21 +1,18 @@ -formatterFactory = $formatterFactory; $this->enabled = config('opentelemetry.enabled', false); $this->elasticIndex = config('opentelemetry.logs.elasticsearch_index', 'logs-audit'); } - public function audit($subject, array $change_set, string $event_type): void + /** + * @param $subject + * @param array $change_set + * @param string $event_type + * @param AuditContext $ctx + * @return void + */ + public function audit($subject, array $change_set, string $event_type, AuditContext $ctx): void { if (!$this->enabled) { return; } - - Log::debug("AuditLogOtlpStrategy::audit", ['subject' => $subject, 'change_set' => $change_set, 'event_type' => $event_type]); + Log::debug("AuditLogOtlpStrategy::audit", ['subject' => $subject, 'change_set' => $change_set, 'event_type' => $event_type]); try { $entity = $this->resolveAuditableEntity($subject); if (is_null($entity)) { + Log::warning("AuditLogOtlpStrategy::audit subject not found"); return; } - - Log::debug("AuditLogOtlpStrategy::audit getting current user"); - - $resource_server_ctx = App::make(\models\oauth2\IResourceServerContext::class); - $user_external_id = $resource_server_ctx->getCurrentUserId(); - $user = null; - if (!is_null($user_external_id)) { - $member_repository = App::make(IMemberRepository::class); - $user = $member_repository->findOneBy(["user_external_id" => $user_external_id]); - } - - $user_id = $user ? $user->getId() : null; - $user_email = $user ? $user->getEmail() : null; - $user_first_name = $user ? $user->getFirstName() : null; - $user_last_name = $user ? $user->getLastName() : null; - - Log::debug("AuditLogOtlpStrategy::audit current user", ["user_id" => $user_id, "user_email" => $user_email]); - $formatter = null; - switch ($event_type) { - case self::EVENT_COLLECTION_UPDATE: - $child_entity = null; - if (count($subject) > 0) { - $child_entity = $subject[0]; - } - if (is_null($child_entity) && count($subject->getSnapshot()) > 0) { - $child_entity = $subject->getSnapshot()[0]; - } - $child_entity_formatter = $child_entity != null ? ChildEntityFormatterFactory::build($child_entity) : null; - $formatter = new EntityCollectionUpdateAuditLogFormatter($child_entity_formatter); - break; - case self::EVENT_ENTITY_CREATION: - $formatter = new EntityCreationAuditLogFormatter(); - break; - case self::EVENT_ENTITY_DELETION: - $child_entity_formatter = ChildEntityFormatterFactory::build($subject); - $formatter = new EntityDeletionAuditLogFormatter($child_entity_formatter); - break; - case self::EVENT_ENTITY_UPDATE: - $child_entity_formatter = ChildEntityFormatterFactory::build($subject); - $formatter = new EntityUpdateAuditLogFormatter($child_entity_formatter); - break; + Log::debug("AuditLogOtlpStrategy::audit current user", ["user_id" => $ctx->userId, "user_email" => $ctx->userEmail]); + $formatter = $this->formatterFactory->make($subject, $event_type); + if(is_null($formatter)) { + Log::warning("AuditLogOtlpStrategy::audit formatter not found"); + return; } - - $description = null; - if ($formatter) { - $description = $formatter->format($subject, $change_set); + $description = $formatter->format($subject, $change_set); + if(is_null($description)){ + Log::warning("AuditLogOtlpStrategy::audit description is empty"); + return; } - - $auditData = $this->buildAuditLogData($entity, $subject, $change_set, $event_type, $user_id, $user_email, $user_first_name, $user_last_name); + $auditData = $this->buildAuditLogData($entity, $subject, $change_set, $event_type, $ctx); if (!empty($description)) { $auditData['audit.description'] = $description; } - Log::debug("AuditLogOtlpStrategy::audit sending entry to OTEL", ["user_id" => $user_id, "user_email" => $user_email]); + Log::debug("AuditLogOtlpStrategy::audit sending entry to OTEL", ["user_id" => $ctx->userId, "user_email" => $ctx->userEmail, 'payload' => $auditData]); EmitAuditLogJob::dispatch($this->getLogMessage($event_type), $auditData); - Log::debug("AuditLogOtlpStrategy::audit entry sent to OTEL", ["user_id" => $user_id, "user_email" => $user_email]); + Log::debug("AuditLogOtlpStrategy::audit entry sent to OTEL", ["user_id" => $ctx->userId, "user_email" => $ctx->userEmail]); } catch (\Exception $ex) { Log::error('OTEL audit logging error: ' . $ex->getMessage(), [ @@ -128,61 +81,93 @@ public function audit($subject, array $change_set, string $event_type): void private function resolveAuditableEntity($subject) { - if ($subject instanceof SummitEvent) return $subject; + // 1) special cases first - if ($subject instanceof PersistentCollection && $subject->getOwner() instanceof SummitEvent) { + // exactly a SummitEvent + if ($subject instanceof \models\summit\SummitEvent) { + return $subject; + } + + // collection that belongs to a SummitEvent + if ($subject instanceof \Doctrine\ORM\PersistentCollection + && $subject->getOwner() instanceof \models\summit\SummitEvent) { return $subject->getOwner(); } - if ($subject instanceof PresentationAction || $subject instanceof PresentationExtraQuestionAnswer) { + // presentation “child” stuff → log the presentation + if ($subject instanceof \models\summit\PresentationAction + || $subject instanceof \models\summit\PresentationExtraQuestionAnswer) { return $subject->getPresentation(); } - if ($subject instanceof SummitAttendeeBadgePrint) { + // badge print → log the badge + if ($subject instanceof \models\summit\SummitAttendeeBadgePrint) { return $subject->getBadge(); } + // 2) generic fallback + + // any collection → log the owner + if ($subject instanceof \Doctrine\ORM\PersistentCollection) { + return $subject->getOwner(); + } + + // any object → log itself + if (is_object($subject)) { + return $subject; + } + + // nothing we can do return null; } - private function buildAuditLogData($entity, $subject, array $change_set, string $event_type, ?string $user_id, ?string $user_email, ?string $user_first_name, ?string $user_last_name): array + private function buildAuditLogData($entity, $subject, array $change_set, string $event_type, AuditContext $ctx): array { - $auditData = [ + $data = [ 'audit.action' => $this->mapEventTypeToAction($event_type), 'audit.entity' => class_basename($entity), 'audit.entity_id' => (string) (method_exists($entity, 'getId') ? $entity->getId() : 'unknown'), 'audit.entity_class' => get_class($entity), 'audit.timestamp' => now()->toISOString(), 'audit.event_type' => $event_type, - 'auth.user.id' => $user_id ?? 'unknown', - 'auth.user.email' => $user_email ?? 'unknown', - 'auth.user.first_name' => $user_first_name ?? 'unknown', - 'auth.user.last_name' => $user_last_name ?? 'unknown', 'elasticsearch.index' => $this->elasticIndex, ]; + // user data + $data['auth.user.id'] = $ctx->userId ?? 'unknown'; + $data['auth.user.email'] = $ctx->userEmail ?? 'unknown'; + $data['auth.user.first_name'] = $ctx->userFirstName ?? 'unknown'; + $data['auth.user.last_name'] = $ctx->userLastName ?? 'unknown'; + + // UI / request + $data['ui.app'] = $ctx->uiApp ?? 'unknown'; + $data['ui.flow'] = $ctx->uiFlow ?? 'unknown'; + $data['http.route']= $ctx->route ?? null; + $data['http.method']= $ctx->httpMethod ?? null; + $data['client.ip'] = $ctx->clientIp ?? null; + $data['user_agent']= $ctx->userAgent ?? null; if (method_exists($entity, 'getSummitId')) { $summitId = $entity->getSummitId(); if ($summitId !== null) { - $auditData['audit.summit_id'] = (string) $summitId; + $data['audit.summit_id'] = (string) $summitId; } } switch ($event_type) { - case self::EVENT_COLLECTION_UPDATE: + case IAuditStrategy::EVENT_COLLECTION_UPDATE: if ($subject instanceof PersistentCollection) { - $auditData['audit.collection_type'] = $this->getCollectionType($subject); - $auditData['audit.collection_count'] = count($subject); + $data['audit.collection_type'] = $this->getCollectionType($subject); + $data['audit.collection_count'] = count($subject); $changes = $this->getCollectionChanges($subject, $change_set); - $auditData['audit.collection_current_count'] = $changes['current_count']; - $auditData['audit.collection_snapshot_count'] = $changes['snapshot_count']; - $auditData['audit.collection_is_dirty'] = $changes['is_dirty'] ? 'true' : 'false'; + $data['audit.collection_current_count'] = $changes['current_count']; + $data['audit.collection_snapshot_count'] = $changes['snapshot_count']; + $data['audit.collection_is_dirty'] = $changes['is_dirty'] ? 'true' : 'false'; } break; } - return $auditData; + return $data; } private function getCollectionType(PersistentCollection $collection): string @@ -191,13 +176,13 @@ private function getCollectionType(PersistentCollection $collection): string if (!method_exists($collection, 'getMapping')) { return 'unknown'; } - + $mapping = $collection->getMapping(); - + if (!isset($mapping['targetEntity']) || empty($mapping['targetEntity'])) { return 'unknown'; } - + return class_basename($mapping['targetEntity']); } catch (\Exception $ex) { return 'unknown'; @@ -216,22 +201,22 @@ private function getCollectionChanges(PersistentCollection $collection, array $c private function mapEventTypeToAction(string $event_type): string { return match($event_type) { - self::EVENT_ENTITY_CREATION => self::ACTION_CREATE, - self::EVENT_ENTITY_UPDATE => self::ACTION_UPDATE, - self::EVENT_ENTITY_DELETION => self::ACTION_DELETE, - self::EVENT_COLLECTION_UPDATE => self::ACTION_COLLECTION_UPDATE, - default => self::ACTION_UNKNOWN + IAuditStrategy::EVENT_ENTITY_CREATION => IAuditStrategy::ACTION_CREATE, + IAuditStrategy::EVENT_ENTITY_UPDATE => IAuditStrategy::ACTION_UPDATE, + IAuditStrategy::EVENT_ENTITY_DELETION => IAuditStrategy::ACTION_DELETE, + IAuditStrategy::EVENT_COLLECTION_UPDATE => IAuditStrategy::ACTION_COLLECTION_UPDATE, + default => IAuditStrategy::ACTION_UNKNOWN }; } private function getLogMessage(string $event_type): string { return match($event_type) { - self::EVENT_ENTITY_CREATION => self::LOG_MESSAGE_CREATED, - self::EVENT_ENTITY_UPDATE => self::LOG_MESSAGE_UPDATED, - self::EVENT_ENTITY_DELETION => self::LOG_MESSAGE_DELETED, - self::EVENT_COLLECTION_UPDATE => self::LOG_MESSAGE_COLLECTION_UPDATED, - default => self::LOG_MESSAGE_CHANGED + IAuditStrategy::EVENT_ENTITY_CREATION => IAuditStrategy::LOG_MESSAGE_CREATED, + IAuditStrategy::EVENT_ENTITY_UPDATE => IAuditStrategy::LOG_MESSAGE_UPDATED, + IAuditStrategy::EVENT_ENTITY_DELETION => IAuditStrategy::LOG_MESSAGE_DELETED, + IAuditStrategy::EVENT_COLLECTION_UPDATE => IAuditStrategy::LOG_MESSAGE_COLLECTION_UPDATED, + default => IAuditStrategy::LOG_MESSAGE_CHANGED }; } diff --git a/app/Audit/AuditLogStrategy.php b/app/Audit/AuditLogStrategy.php index ce684475d..e472461c2 100644 --- a/app/Audit/AuditLogStrategy.php +++ b/app/Audit/AuditLogStrategy.php @@ -1,6 +1,4 @@ -resolveAuditableEntity($subject); @@ -122,4 +122,4 @@ public function audit($subject, $change_set, $event_type) { Log::warning($ex); } } -} \ No newline at end of file +} diff --git a/app/Audit/AuditProvider.php b/app/Audit/AuditProvider.php new file mode 100644 index 000000000..2fa3af2e7 --- /dev/null +++ b/app/Audit/AuditProvider.php @@ -0,0 +1,38 @@ + [old, new]) - * @param string $event_type Type of audit event (create, update, delete, collection_update) + * @param $subject + * @param array $change_set + * @param string $event_type + * @param AuditContext $ctx * @return void */ - public function audit($subject, array $change_set, string $event_type): void; + public function audit($subject, array $change_set, string $event_type, AuditContext $ctx): void; + + + public const EVENT_COLLECTION_UPDATE = 'event_collection_update'; + public const EVENT_ENTITY_CREATION = 'event_entity_creation'; + public const EVENT_ENTITY_DELETION = 'event_entity_deletion'; + public const EVENT_ENTITY_UPDATE = 'event_entity_update'; + + public const ACTION_CREATE = 'create'; + public const ACTION_UPDATE = 'update'; + public const ACTION_DELETE = 'delete'; + public const ACTION_COLLECTION_UPDATE = 'collection_update'; + public const ACTION_UNKNOWN = 'unknown'; -} \ No newline at end of file + public const LOG_MESSAGE_CREATED = 'audit.entity.created'; + public const LOG_MESSAGE_UPDATED = 'audit.entity.updated'; + public const LOG_MESSAGE_DELETED = 'audit.entity.deleted'; + public const LOG_MESSAGE_COLLECTION_UPDATED = 'audit.collection.updated'; + public const LOG_MESSAGE_CHANGED = 'audit.entity.changed'; +} diff --git a/config/app.php b/config/app.php index ea69b5fcd..8d79b8a06 100644 --- a/config/app.php +++ b/config/app.php @@ -172,6 +172,7 @@ SimpleSoftwareIO\QrCode\QrCodeServiceProvider::class, \App\Providers\DoctrineWorkerServiceProvider::class, App\libs\Utils\Doctrine\DoctrineCacheServiceProvider::class, + App\Audit\AuditProvider::class, // Only if you want to toggle via env: ], env('OTEL_SERVICE_ENABLED', false) ? [\Keepsuit\LaravelOpenTelemetry\LaravelOpenTelemetryServiceProvider::class] : []), From 127d9a7cd9feff6b63ccc8e93d5e91ecaa00d922 Mon Sep 17 00:00:00 2001 From: smarcet Date: Tue, 11 Nov 2025 16:08:47 -0300 Subject: [PATCH 2/2] chore: add custom audit logger per entity ( SummitMemberSchedule ) --- app/Audit/AuditEventListener.php | 6 +- app/Audit/AuditLogFormatterFactory.php | 34 ++- .../SummitMemberScheduleAuditLogFormatter.php | 46 ++++ app/Models/Foundation/Main/Member.php | 221 +++++++++--------- config/audit_log.php | 10 + 5 files changed, 200 insertions(+), 117 deletions(-) create mode 100644 app/Audit/ConcreteFormatters/SummitMemberScheduleAuditLogFormatter.php create mode 100644 config/audit_log.php diff --git a/app/Audit/AuditEventListener.php b/app/Audit/AuditEventListener.php index 556ece785..9bc4d7f32 100644 --- a/app/Audit/AuditEventListener.php +++ b/app/Audit/AuditEventListener.php @@ -26,15 +26,19 @@ class AuditEventListener public function onFlush(OnFlushEventArgs $eventArgs): void { + if (app()->environment('testing')){ + return; + } $em = $eventArgs->getObjectManager(); $uow = $em->getUnitOfWork(); // Strategy selection based on environment configuration $strategy = $this->getAuditStrategy($em); - $ctx = $this->buildAuditContext(); if (!$strategy) { return; // No audit strategy enabled } + $ctx = $this->buildAuditContext(); + try { foreach ($uow->getScheduledEntityInsertions() as $entity) { $strategy->audit($entity, [], IAuditStrategy::EVENT_ENTITY_CREATION, $ctx); diff --git a/app/Audit/AuditLogFormatterFactory.php b/app/Audit/AuditLogFormatterFactory.php index 776f77f7e..ee8023a5d 100644 --- a/app/Audit/AuditLogFormatterFactory.php +++ b/app/Audit/AuditLogFormatterFactory.php @@ -21,6 +21,21 @@ class AuditLogFormatterFactory implements IAuditLogFormatterFactory { + private array $config; + + public function __construct() + { + // cache the config so we don't hit config() repeatedly + $this->config = config('audit_log', []); + } + + public function getStrategyClass(object $subject, string $event_type): ?IAuditLogFormatter + { + $class = get_class($subject); + $cls = $this->config['entities'][$class]['strategy'] ?? null; + return !is_null($cls) ? new $cls($event_type):null; + } + public function make($subject, $eventType): ?IAuditLogFormatter { $formatter = null; @@ -37,15 +52,24 @@ public function make($subject, $eventType): ?IAuditLogFormatter $formatter = new EntityCollectionUpdateAuditLogFormatter($child_entity_formatter); break; case IAuditStrategy::EVENT_ENTITY_CREATION: - $formatter = new EntityCreationAuditLogFormatter(); + $formatter = $this->getStrategyClass($subject, $eventType); + if(is_null($formatter)) { + $formatter = new EntityCreationAuditLogFormatter(); + } break; case IAuditStrategy::EVENT_ENTITY_DELETION: - $child_entity_formatter = ChildEntityFormatterFactory::build($subject); - $formatter = new EntityDeletionAuditLogFormatter($child_entity_formatter); + $formatter = $this->getStrategyClass($subject, $eventType); + if(is_null($formatter)) { + $child_entity_formatter = ChildEntityFormatterFactory::build($subject); + $formatter = new EntityDeletionAuditLogFormatter($child_entity_formatter); + } break; case IAuditStrategy::EVENT_ENTITY_UPDATE: - $child_entity_formatter = ChildEntityFormatterFactory::build($subject); - $formatter = new EntityUpdateAuditLogFormatter($child_entity_formatter); + $formatter = $this->getStrategyClass($subject, $eventType); + if(is_null($formatter)) { + $child_entity_formatter = ChildEntityFormatterFactory::build($subject); + $formatter = new EntityUpdateAuditLogFormatter($child_entity_formatter); + } break; } return $formatter; diff --git a/app/Audit/ConcreteFormatters/SummitMemberScheduleAuditLogFormatter.php b/app/Audit/ConcreteFormatters/SummitMemberScheduleAuditLogFormatter.php new file mode 100644 index 000000000..4b25bc80a --- /dev/null +++ b/app/Audit/ConcreteFormatters/SummitMemberScheduleAuditLogFormatter.php @@ -0,0 +1,46 @@ +event_type = $event_type; + } + + /** + * @param $subject + * @param array $change_set + * @return string|null + */ + public function format($subject, array $change_set): ?string + { + if(!$subject instanceof SummitMemberSchedule) return null; + $summit_event = $subject->getEvent(); + switch($this->event_type) { + case IAuditStrategy::EVENT_ENTITY_DELETION:{ + return sprintf("Activity %s (%s) was removed from user custom schedule.", $summit_event->getTitle(), $summit_event->getId()); + } + case IAuditStrategy::EVENT_ENTITY_CREATION:{ + return sprintf("Activity %s (%s) was inserted onto user custom schedule.", $summit_event->getTitle(), $summit_event->getId()); + } + } + return null; + } +} diff --git a/app/Models/Foundation/Main/Member.php b/app/Models/Foundation/Main/Member.php index 63b3d0df9..ffe91af31 100644 --- a/app/Models/Foundation/Main/Member.php +++ b/app/Models/Foundation/Main/Member.php @@ -1049,7 +1049,7 @@ public function belongsToGroup(string $code): bool try { $sql = <<getId(), $this->getId()) ); $this->schedule->removeElement($schedule); - $schedule->clearOwner(); } public function removeFromScheduleSyncInfo(ScheduleCalendarSyncInfo $sync_info) @@ -1280,9 +1279,9 @@ public function getScheduleByEvent(SummitEvent $event) { try { - $query = $this->createQuery("SELECT s from models\main\SummitMemberSchedule s - JOIN s.member a - JOIN s.event e + $query = $this->createQuery("SELECT s from models\main\SummitMemberSchedule s + JOIN s.member a + JOIN s.event e WHERE a.id = :member_id and e.id = :event_id "); return $query @@ -1325,9 +1324,9 @@ public function getScheduleSyncInfoByEvent($summit_event_id, CalendarSyncInfo $c public function getFavoriteByEvent(SummitEvent $event) { try { - $query = $this->createQuery("SELECT f from models\main\SummitMemberFavorite f - JOIN f.member a - JOIN f.event e + $query = $this->createQuery("SELECT f from models\main\SummitMemberFavorite f + JOIN f.member a + JOIN f.event e WHERE a.id = :member_id and e.id = :event_id "); return $query @@ -1349,8 +1348,8 @@ public function getFavoriteByEvent(SummitEvent $event) public function getScheduledEventsIds(Summit $summit) { $sql = <<createQuery("SELECT s from models\main\SummitMemberSchedule s JOIN s.member m - JOIN s.event e + JOIN s.event e JOIN e.summit su WHERE su.id = :summit_id and m.id = :member_id and e.published = 1 "); return $query @@ -1452,7 +1451,7 @@ public function getFavoritesSummitEventsBySummit(Summit $summit) { $query = $this->createQuery("SELECT f from models\main\SummitMemberFavorite f JOIN f.member m - JOIN f.event e + JOIN f.event e JOIN e.summit su WHERE su.id = :summit_id and m.id = :member_id and e.published = 1 "); return $query @@ -1669,10 +1668,10 @@ public function getReservations() public function getReservationsCountBySummit(Summit $summit): int { $query = $this->createQuery("SELECT count(rv.id) from models\summit\SummitRoomReservation rv - JOIN rv.owner o - JOIN rv.room r - JOIN r.venue v - JOIN v.summit s + JOIN rv.owner o + JOIN rv.room r + JOIN r.venue v + JOIN v.summit s WHERE s.id = :summit_id AND o.id = :owner_id and rv.status not in (:status)"); return $query ->setParameter('summit_id', $summit->getId()) @@ -1694,10 +1693,10 @@ public function getReservationsCountBySummit(Summit $summit): int public function getReservationsBySummit(Summit $summit) { $query = $this->createQuery("SELECT rv from models\summit\SummitRoomReservation rv - JOIN rv.owner o - JOIN rv.room r - JOIN r.venue v - JOIN v.summit s + JOIN rv.owner o + JOIN rv.room r + JOIN r.venue v + JOIN v.summit s WHERE s.id = :summit_id AND o.id = :owner_id"); return $query ->setParameter('summit_id', $summit->getId()) @@ -1834,10 +1833,10 @@ public function getLastNSponsorMemberships($last_n = 2) public function getActiveSummitsSponsorMemberships() { $dql = <<= :now ORDER BY s.begin_date ASC @@ -1879,8 +1878,8 @@ public function hasSponsorMembershipsFor(Summit $summit, Sponsor $sponsor = null SELECT COUNT(Sponsor_Users.SponsorID) FROM Sponsor_Users INNER JOIN Sponsor ON Sponsor.ID = Sponsor_Users.SponsorID -WHERE - MemberID = :member_id +WHERE + MemberID = :member_id AND Sponsor.SummitID = :summit_id SQL; @@ -2105,12 +2104,12 @@ public function hasPermissionForOnGroup(Summit $summit, string $groupSlug): bool if (!SummitAdministratorPermissionGroup::isValidGroup($groupSlug)) return false; $sql = << 0) return $res; $sql = <<prepareRawSQL($sql, [ @@ -2787,7 +2786,7 @@ public function getAcceptedPresentations if($filter->hasFilter("has_media_upload_with_type")) { $extraWhere .= " AND EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -2797,7 +2796,7 @@ public function getAcceptedPresentations if($filter->hasFilter("has_not_media_upload_with_type")) { $extraWhere .= " AND NOT EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -2809,63 +2808,63 @@ public function getAcceptedPresentations if($value) $extraWhere .= 'AND ( EXISTS ( - SELECT __p61.id FROM models\summit\Presentation __p61 - JOIN __p61.created_by __c61 WITH __c61 = :member_id - JOIN __p61.speakers __pspk61 - JOIN __pspk61.speaker __spk61 WITH __spk61.member = :member_id + SELECT __p61.id FROM models\summit\Presentation __p61 + JOIN __p61.created_by __c61 WITH __c61 = :member_id + JOIN __p61.speakers __pspk61 + JOIN __pspk61.speaker __spk61 WITH __spk61.member = :member_id WHERE __p61.summit = :summit_id - ) - OR + ) + OR EXISTS ( - SELECT __p62.id FROM models\summit\Presentation __p62 + SELECT __p62.id FROM models\summit\Presentation __p62 JOIN __p62.created_by __c62 WITH __c62 = :member_id - JOIN __p62.moderator __md62 WITH __md62.member = :member_id + JOIN __p62.moderator __md62 WITH __md62.member = :member_id WHERE __p62.summit = :summit_id ))'; else $extraWhere .= ' AND ( NOT EXISTS ( - SELECT __p61.id FROM models\summit\Presentation __p61 - JOIN __p61.created_by __c61 WITH __c61 = :member_id + SELECT __p61.id FROM models\summit\Presentation __p61 + JOIN __p61.created_by __c61 WITH __c61 = :member_id JOIN __p61.speakers __pspk61 - JOIN __pspk61.speaker __spk61 WITH __spk61.member = :member_id + JOIN __pspk61.speaker __spk61 WITH __spk61.member = :member_id WHERE __p61.summit = :summit_id - ) - AND + ) + AND NOT EXISTS ( - SELECT __p62.id FROM models\summit\Presentation __p62 - JOIN __p62.created_by __c62 WITH __c62 = :member_id - JOIN __p62.moderator __md62 WITH __md62.member = :member_id + SELECT __p62.id FROM models\summit\Presentation __p62 + JOIN __p62.created_by __c62 WITH __c62 = :member_id + JOIN __p62.moderator __md62 WITH __md62.member = :member_id WHERE __p62.summit = :summit_id ))'; } } $query = $this->createQuery(sprintf(" - SELECT DISTINCT p from models\summit\Presentation p - JOIN p.summit s - LEFT JOIN p.speakers a_spk + SELECT DISTINCT p from models\summit\Presentation p + JOIN p.summit s + LEFT JOIN p.speakers a_spk LEFT JOIN p.moderator mod - LEFT JOIN a_spk.speaker spk - JOIN p.created_by cb - LEFT JOIN p.selection_plan sel_p - LEFT JOIN p.materials m - LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id + LEFT JOIN a_spk.speaker spk + JOIN p.created_by cb + LEFT JOIN p.selection_plan sel_p + LEFT JOIN p.materials m + LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id LEFT JOIN pmu.media_upload_type mut JOIN p.type t - JOIN p.category cat - LEFT JOIN p.selected_presentations ssp - LEFT JOIN ssp.list sspl - WHERE s.id = :summit_id + JOIN p.category cat + LEFT JOIN p.selected_presentations ssp + LEFT JOIN ssp.list sspl + WHERE s.id = :summit_id AND cb = :submitter_id - AND + AND ( - ( + ( ssp.order is not null AND ssp.order <= cat.session_count AND ssp.collection = '%s' AND sspl.list_type = '%s' AND sspl.list_class = '%s' ) - OR p.published = 1 + OR p.published = 1 ) " . $extraWhere, SummitSelectedPresentation::CollectionSelected, @@ -3022,7 +3021,7 @@ public function getAlternatePresentations if($filter->hasFilter("has_media_upload_with_type")) { $extraWhere .= " AND EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -3032,7 +3031,7 @@ public function getAlternatePresentations if($filter->hasFilter("has_not_media_upload_with_type")) { $extraWhere .= " AND NOT EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -3049,19 +3048,19 @@ public function getAlternatePresentations } $query = $this->createQuery(" - SELECT DISTINCT p from models\summit\Presentation p - JOIN p.summit s + SELECT DISTINCT p from models\summit\Presentation p + JOIN p.summit s LEFT JOIN p.moderator mod - LEFT JOIN p.speakers a_spk - LEFT JOIN a_spk.speaker spk - JOIN p.created_by cb - JOIN p.selection_plan sel_p - LEFT JOIN p.materials m - LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id - LEFT JOIN pmu.media_upload_type mut - JOIN p.type t - JOIN p.category cat - WHERE s.id = :summit_id + LEFT JOIN p.speakers a_spk + LEFT JOIN a_spk.speaker spk + JOIN p.created_by cb + JOIN p.selection_plan sel_p + LEFT JOIN p.materials m + LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id + LEFT JOIN pmu.media_upload_type mut + JOIN p.type t + JOIN p.category cat + WHERE s.id = :summit_id AND cb.id = :submitter_id" . $extraWhere); $query @@ -3191,7 +3190,7 @@ public function getRejectedPresentations if($filter->hasFilter("has_media_upload_with_type")) { $extraWhere .= " AND EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -3201,7 +3200,7 @@ public function getRejectedPresentations if($filter->hasFilter("has_not_media_upload_with_type")) { $extraWhere .= " AND NOT EXISTS ( - SELECT pmu_12.id + SELECT pmu_12.id FROM models\summit\PresentationMediaUpload pmu_12 JOIN pmu_12.media_upload_type mut_12 JOIN pmu_12.presentation p__12 @@ -3217,20 +3216,20 @@ public function getRejectedPresentations } } - $query = $this->createQuery("SELECT DISTINCT p from models\summit\Presentation p + $query = $this->createQuery("SELECT DISTINCT p from models\summit\Presentation p JOIN p.summit s - LEFT JOIN p.moderator mod - LEFT JOIN p.speakers a_spk - LEFT JOIN a_spk.speaker spk - LEFT JOIN p.materials m - LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id - LEFT JOIN pmu.media_upload_type mut - LEFT JOIN p.selection_plan sel_p - JOIN p.type t - JOIN p.category cat - JOIN p.created_by cb - WHERE s.id = :summit_id - AND p.published = 0 + LEFT JOIN p.moderator mod + LEFT JOIN p.speakers a_spk + LEFT JOIN a_spk.speaker spk + LEFT JOIN p.materials m + LEFT JOIN models\summit\PresentationMediaUpload pmu WITH pmu.id = m.id + LEFT JOIN pmu.media_upload_type mut + LEFT JOIN p.selection_plan sel_p + JOIN p.type t + JOIN p.category cat + JOIN p.created_by cb + WHERE s.id = :summit_id + AND p.published = 0 AND cb.id = :submitter_id " . $extraWhere); $query @@ -3424,4 +3423,4 @@ public function removeSponsorMembership(Sponsor $sponsor):void if(!$this->sponsor_memberships->contains($sponsor)) return; $this->sponsor_memberships->removeElement($sponsor); } -} \ No newline at end of file +} diff --git a/config/audit_log.php b/config/audit_log.php new file mode 100644 index 000000000..bb26f9521 --- /dev/null +++ b/config/audit_log.php @@ -0,0 +1,10 @@ + [ + \models\main\SummitMemberSchedule::class => [ + 'enabled' => true, + 'strategy' => \App\Audit\ConcreteFormatters\SummitMemberScheduleAuditLogFormatter::class, + ] + ] +];