. */ namespace App\Services\LogSystem; use App\Entity\LogSystem\AbstractLogEntry; use App\Services\ElementTypeNameGenerator; use Doctrine\ORM\EntityManagerInterface; use Symfony\Contracts\Translation\TranslatorInterface; class LogDataFormatter { private const STRING_MAX_LENGTH = 1024; public function __construct(private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager, private readonly ElementTypeNameGenerator $elementTypeNameGenerator) { } /** * Formats the given data of a log entry as HTML */ public function formatData(mixed $data, AbstractLogEntry $logEntry, string $fieldName): string { if (is_string($data)) { $tmp = '"' . mb_strimwidth(htmlspecialchars($data), 0, self::STRING_MAX_LENGTH, ) . '"'; //Show special characters and line breaks $tmp = preg_replace('/\n/', '\\n
', $tmp); $tmp = preg_replace('/\r/', '\\r', $tmp); return preg_replace('/\t/', '\\t', $tmp); } if (is_bool($data)) { return $this->formatBool($data); } if (is_int($data)) { return (string) $data; } if (is_float($data)) { return (string) $data; } if (is_null($data)) { return 'null'; } if (is_array($data)) { //If the array contains only one element with the key @id, it is a reference to another entity (foreign key) if (isset($data['@id'])) { return $this->formatForeignKey($data, $logEntry, $fieldName); } //If the array contains a "date", "timezone_type" and "timezone" key, it is a DateTime object if (isset($data['date'], $data['timezone_type'], $data['timezone'])) { return $this->formatDateTime($data); } return $this->formatJSON($data); } throw new \RuntimeException('Type of $data not supported (' . gettype($data) . ')'); } private function formatJSON(array $data): string { $json = htmlspecialchars(json_encode($data, JSON_PRETTY_PRINT), ENT_QUOTES | ENT_SUBSTITUTE); return sprintf( '
', $json ); } private function formatForeignKey(array $data, AbstractLogEntry $logEntry, string $fieldName): string { //Extract the id from the @id key $id = $data['@id']; try { //Retrieve the class type from the logEntry and retrieve the doctrine metadata $classMetadata = $this->entityManager->getClassMetadata($logEntry->getTargetClass()); $fkTargetClass = $classMetadata->getAssociationTargetClass($fieldName); //Try to retrieve the entity from the database $entity = $this->entityManager->getRepository($fkTargetClass)->find($id); //If the entity was found, return a label for this entity if ($entity) { return $this->elementTypeNameGenerator->formatLabelHTMLForEntity($entity, true); } else { //Otherwise the entity was deleted, so return the id return $this->elementTypeNameGenerator->formatElementDeletedHTML($fkTargetClass, $id); } } catch (\InvalidArgumentException|\ReflectionException) { return 'unknown target class: ' . $id; } } private function formatDateTime(array $data): string { if (!isset($data['date'], $data['timezone_type'], $data['timezone'])) { return 'unknown DateTime format'; } $date = $data['date']; $timezoneType = $data['timezone_type']; $timezone = $data['timezone']; if (!is_string($date) || !is_int($timezoneType) || !is_string($timezone)) { return 'unknown DateTime format'; } try { $dateTime = new \DateTime($date, new \DateTimeZone($timezone)); } catch (\Exception) { return 'unknown DateTime format'; } //Format it to the users locale $formatter = new \IntlDateFormatter(null, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::MEDIUM); return $formatter->format($dateTime); } private function formatBool(bool $data): string { return $data ? $this->translator->trans('true') : $this->translator->trans('false'); } }