Skip to content
69 changes: 67 additions & 2 deletions app/Audit/AbstractAuditLogFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ protected function formatChangeValue($value): string
}


protected function buildChangeDetails(array $change_set): string
private function buildChangeDetails(array $change_set): ?string
{
$changed_fields = [];
$ignored_fields = $this->getIgnoredFields();
Expand All @@ -178,21 +178,86 @@ protected function buildChangeDetails(array $change_set): string
}

if (empty($changed_fields)) {
return 'properties without changes registered';
return null;
}

$fields_summary = count($changed_fields) . ' field(s) modified: ';
return $fields_summary . implode(' | ', $changed_fields);
}

/**
* The only way to consume buildChangeDetails() for an EVENT_ENTITY_UPDATE message.
* Returns null (suppressing the audit entry) whenever nothing meaningful changed,
* otherwise hands the non-null details string to $messageBuilder to assemble the
* final message. buildChangeDetails() is private specifically so a formatter cannot
* bypass this null-check.
*/
final protected function formatUpdateMessage(array $change_set, \Closure $messageBuilder): ?string
{
$details = $this->buildChangeDetails($change_set);
if ($details === null) {
return null;
}
return $messageBuilder($details);
}


protected function formatFieldChange(string $prop_name, $old_value, $new_value): ?string
{
if ($this->valuesAreEffectivelyEqual($old_value, $new_value)) {
return null;
}

$old_display = $this->formatChangeValue($old_value);
$new_display = $this->formatChangeValue($new_value);

return sprintf("Property \"%s\" has changed from \"%s\" to \"%s\"", $prop_name, $old_display, $new_display);
}

protected function valuesAreEffectivelyEqual($old_value, $new_value): bool
{
if ($old_value === $new_value) {
return true;
}

if ($old_value instanceof \DateTimeInterface && $new_value instanceof \DateTimeInterface) {
return $old_value->getTimestamp() === $new_value->getTimestamp();
}

// A boolean column hydrated as false/true and re-written by a factory as 0/1 or "0"/"1"
// (e.g. PresentationFactory::setAttendingMedia(0)) is reported by Doctrine as a change
// (false !== 0) although the stored value is the same. Only the exact 0/1 forms are
// accepted; anything else next to a boolean is a real change.
if (is_bool($old_value) || is_bool($new_value)) {
$old_flag = $this->asBooleanFlag($old_value);
$new_flag = $this->asBooleanFlag($new_value);
return $old_flag !== null && $new_flag !== null && $old_flag === $new_flag;
}

if ((is_scalar($old_value) || is_null($old_value)) && (is_scalar($new_value) || is_null($new_value))) {
return $this->formatChangeValue($old_value) === $this->formatChangeValue($new_value);
}

return false;
}

/**
* Maps true/1/"1" to true and false/0/"0" to false; any other value yields null.
*/
private function asBooleanFlag($value): ?bool
{
if (is_bool($value)) {
return $value;
}
if ($value === 0 || $value === "0") {
return false;
}
if ($value === 1 || $value === "1") {
return true;
}
return null;
}

/**
* Format detailed message for many-to-many collection changes
*/
Expand Down
5 changes: 2 additions & 3 deletions app/Audit/AuditLogOtlpStrategy.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function audit($subject, array $change_set, string $event_type, AuditCon
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)) {
Expand All @@ -67,7 +67,7 @@ public function audit($subject, array $change_set, string $event_type, AuditCon
}
$description = $formatter->format($subject, $change_set);
if(is_null($description)){
Log::warning("AuditLogOtlpStrategy::audit description is empty");
Log::debug("AuditLogOtlpStrategy::audit description is empty");
return;
}
$auditData = $this->buildAuditLogData($entity, $subject, $change_set, $event_type, $ctx);
Expand All @@ -79,7 +79,6 @@ public function audit($subject, array $change_set, string $event_type, AuditCon
job: $job,
);
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(), [
'exception' => $ex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public function format($subject, array $change_set): ?string
return sprintf("Affiliation (%s) for '%s' (%s) created by user %s", $id, $owner_name, $job_title, $this->getUserInfo());

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$details = $this->buildChangeDetails($change_set);
return sprintf("Affiliation (%s) for '%s' (%s) updated: %s by user %s", $id, $owner_name, $job_title, $details, $this->getUserInfo());
return $this->formatUpdateMessage($change_set, fn($details) => sprintf("Affiliation (%s) for '%s' (%s) updated: %s by user %s", $id, $owner_name, $job_title, $details, $this->getUserInfo()));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf("Affiliation (%s) for '%s' (%s) deleted by user %s", $id, $owner_name, $job_title, $this->getUserInfo());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,13 @@ public function format($subject, array $change_set): ?string
case IAuditStrategy::EVENT_ENTITY_UPDATE:
$question_type = $subject->getQuestionType();
$question_label = $question_type ? ($question_type->getLabel() ?? 'Unknown Question') : 'Unknown Question';
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Assigned Selection Plan Extra Question (%s) '%s' updated: %s by user %s",
$id,
$question_label,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
$question_type = $subject->getQuestionType();
Expand Down
5 changes: 2 additions & 3 deletions app/Audit/ConcreteFormatters/CompanyAuditLogFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,13 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Company '%s' (%d) updated: %s by user %s",
$name,
$id,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ public function format($subject, array $change_set): ?string
case IAuditStrategy::EVENT_ENTITY_CREATION:
return sprintf("Extra Question Value '%s' (%s) for Question '%s' created by user %s", $label, $id, $question_label, $this->getUserInfo());
case IAuditStrategy::EVENT_ENTITY_UPDATE:
$details = $this->buildChangeDetails($change_set);
return sprintf("Extra Question Value '%s' (%s) for Question '%s' updated: %s by user %s", $label, $id, $question_label, $details, $this->getUserInfo());
return $this->formatUpdateMessage($change_set, fn($details) => sprintf("Extra Question Value '%s' (%s) for Question '%s' updated: %s by user %s", $label, $id, $question_label, $details, $this->getUserInfo()));
case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf("Extra Question Value '%s' (%s) for Question '%s' deleted by user %s", $label, $id, $question_label, $this->getUserInfo());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,13 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Featured speaker '%s' (%s) updated: %s by user %s",
$speaker_name,
$speaker_id,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
5 changes: 2 additions & 3 deletions app/Audit/ConcreteFormatters/FileAuditLogFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"File '%s' (%s) (%d) updated: %s by user %s",
$name,
$filename,
$id,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,15 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Pre-Paid Discount Code '%s' (%d) for Summit '%s' updated: %s (current: %s) by user %s",
$code,
$id,
$summit_name,
$change_details,
$discount_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ public function format($subject, array $change_set): ?string
case IAuditStrategy::EVENT_ENTITY_CREATION:
return sprintf("Presentation Attendee Vote (%s) for '%s' created by user %s", $id, $title, $this->getUserInfo());
case IAuditStrategy::EVENT_ENTITY_UPDATE:
$details = $this->buildChangeDetails($change_set);
return sprintf("Presentation Attendee Vote (%s) for '%s' updated: %s by user %s", $id, $title, $details, $this->getUserInfo());
return $this->formatUpdateMessage($change_set, fn($details) => sprintf("Presentation Attendee Vote (%s) for '%s' updated: %s by user %s", $id, $title, $details, $this->getUserInfo()));
case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf("Presentation Attendee Vote (%s) for '%s' deleted by user %s", $id, $title, $this->getUserInfo());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,15 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation Category '%s' (%s) (%d) for Summit '%s' updated: %s by user %s",
$title,
$code,
$id,
$summit_name,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Track Group (PresentationCategoryGroup) '%s' (%s) for Summit '%s' updated: %s by user %s",
$name,
$id,
$summit_name,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function format(mixed $subject, array $change_set): ?string

abstract protected function formatCreation(array $data): string;

abstract protected function formatUpdate(array $data, array $change_set): string;
abstract protected function formatUpdate(array $data, array $change_set): ?string;

abstract protected function formatDeletion(array $data): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation Action Type '%s' (%d) for Summit '%s' updated: %s by user %s",
$label,
$id,
$summit_name,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ protected function formatCreation(array $data): string
);
}

protected function formatUpdate(array $data, array $change_set): string
protected function formatUpdate(array $data, array $change_set): ?string
{
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation '%s' (%s) updated: %s by user %s",
$data['title'],
$data['id'],
$this->buildChangeDetails($change_set),
$change_details,
$this->getUserInfo()
);
));
}

protected function formatDeletion(array $data): string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation Link '%s' (%d) for presentation '%s' updated: %s by user %s",
$title,
$id,
$presentation_title,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation Media Upload '%s' (%d) for presentation '%s' updated: %s by user %s",
$title,
$id,
$presentation_title,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,14 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Presentation Slide '%s' (%d) for presentation '%s' updated: %s by user %s",
$title,
$id,
$presentation_title,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ public function format($subject, array $change_set): ?string
);

case IAuditStrategy::EVENT_ENTITY_UPDATE:
$change_details = $this->buildChangeDetails($change_set);
return sprintf(
return $this->formatUpdateMessage($change_set, fn($change_details) => sprintf(
"Speaker '%s' (%s) updated: %s by user %s",
$full_name,
$speaker_id,
$change_details,
$this->getUserInfo()
);
));

case IAuditStrategy::EVENT_ENTITY_DELETION:
return sprintf(
Expand Down
Loading
Loading