diff --git a/src/Command/ShowEventLogCommand.php b/src/Command/ShowEventLogCommand.php new file mode 100644 index 00000000..e9cc82f0 --- /dev/null +++ b/src/Command/ShowEventLogCommand.php @@ -0,0 +1,160 @@ +entityManager = $entityManager; + $this->translator = $translator; + $this->elementTypeNameGenerator = $elementTypeNameGenerator; + $this->formatter = $formatter; + + $this->repo = $this->entityManager->getRepository(AbstractLogEntry::class); + parent::__construct(); + } + + protected function configure() + { + $this + ->setDescription('List the last event log entries.') + ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50 ) + ->addOption('oldest_first', null, InputOption::VALUE_NONE,'Show older entries first.') + ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Which page should be shown?', 1) + ->addOption('onePage', null, InputOption::VALUE_NONE, 'Show only one page (dont ask to go to next).') + ->addOption('showExtra', 'x', InputOption::VALUE_NONE, 'Show a column with the extra data.'); + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $onePage = $input->getOption('onePage'); + + $desc = $input->getOption('oldest_first'); + $limit = $input->getOption('count'); + $page = $input->getOption('page'); + $showExtra = $input->getOption('showExtra'); + + $total_count = $this->repo->count([]); + $max_page = ceil($total_count / $limit); + + if ($page > $max_page) { + $io->error("There is no page $page! The maximum page is $max_page."); + return 1; + } + + $io->note("There are a total of $total_count log entries in the DB."); + + $continue = true; + while ($continue && $page <= $max_page) { + $this->showPage($output, $desc, $limit, $page, $max_page, $showExtra); + + if ($onePage) { + return 0; + } + + $continue = $io->confirm('Do you want to show the next page?'); + $page++; + } + + return 0; + } + + protected function showPage(OutputInterface $output, bool $desc, int $limit, int $page, int $max_page, bool $showExtra): void + { + $sorting = $desc ? 'ASC' : 'DESC'; + $offset = ($page - 1) * $limit; + + /** @var AbstractLogEntry[] $entries */ + $entries = $this->repo->getLogsOrderedByTimestamp($sorting, $limit, $offset); + + $table = new Table($output); + $table->setHeaderTitle("Page $page / $max_page"); + $headers = ['ID', 'Timestamp', 'Type', 'User', 'Target Type', 'Target']; + if ($showExtra) { + $headers[] = 'Extra data'; + } + $table->setHeaders($headers); + + foreach ($entries as $entry) { + $this->addTableRow($table, $entry, $showExtra); + } + + $table->render(); + } + + protected function addTableRow(Table $table, AbstractLogEntry $entry, bool $showExtra): void + { + $target = $this->repo->getTargetElement($entry); + $target_name = ""; + if ($target instanceof NamedDBElement) { + $target_name = $target->getName() . ' (' . $target->getID() . ')'; + } elseif ($entry->getTargetID()) { + $target_name = '(' . $entry->getTargetID() . ')'; + } + + $target_class = ""; + if ($entry->getTargetClass() !== null) { + $target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass()); + } + + $row = [ + $entry->getID(), + $entry->getTimestamp()->format('Y-m-d H:i:s'), + $entry->getType(), + $entry->getUser()->getFullName(true), + $target_class, + $target_name + ]; + + if ($showExtra) { + $row[] = $this->formatter->formatConsole($entry); + } + + $table->addRow($row); + } +} \ No newline at end of file diff --git a/src/DataTables/Column/LogEntryExtraColumn.php b/src/DataTables/Column/LogEntryExtraColumn.php index 42cf7d49..e3d9d5c9 100644 --- a/src/DataTables/Column/LogEntryExtraColumn.php +++ b/src/DataTables/Column/LogEntryExtraColumn.php @@ -31,16 +31,19 @@ use App\Entity\LogSystem\InstockChangedLogEntry; use App\Entity\LogSystem\UserLoginLogEntry; use App\Entity\LogSystem\UserLogoutLogEntry; use App\Entity\LogSystem\UserNotAllowedLogEntry; +use App\Services\LogSystem\LogEntryExtraFormatter; use Omines\DataTablesBundle\Column\AbstractColumn; use Symfony\Contracts\Translation\TranslatorInterface; class LogEntryExtraColumn extends AbstractColumn { protected $translator; + protected $formatter; - public function __construct(TranslatorInterface $translator) + public function __construct(TranslatorInterface $translator, LogEntryExtraFormatter $formatter) { $this->translator = $translator; + $this->formatter = $formatter; } /** @@ -53,69 +56,6 @@ class LogEntryExtraColumn extends AbstractColumn public function render($value, $context) { - if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry) { - return sprintf( - "%s: %s", - $this->translator->trans('log.user_login.ip'), - htmlspecialchars($context->getIPAddress()) - ); - } - - if ($context instanceof ExceptionLogEntry) { - return sprintf( - '%s %s:%d : %s', - htmlspecialchars($context->getExceptionClass()), - htmlspecialchars($context->getFile()), - $context->getLine(), - htmlspecialchars($context->getMessage()) - ); - } - - if ($context instanceof DatabaseUpdatedLogEntry) { - return sprintf( - '%s %s %s', - $this->translator->trans($context->isSuccessful() ? 'log.database_updated.success' : 'log.database_updated.failure'), - $context->getOldVersion(), - $context->getNewVersion() - ); - } - - if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { - return sprintf( - '%s: %s', - $this->translator->trans('log.element_created.original_instock'), - $context->getCreationInstockValue() - ); - } - - if ($context instanceof ElementDeletedLogEntry) { - return sprintf( - '%s: %s', - $this->translator->trans('log.element_deleted.old_name'), - $context->getOldName() - ); - } - - if ($context instanceof ElementEditedLogEntry && !empty($context->getMessage())) { - return htmlspecialchars($context->getMessage()); - } - - if ($context instanceof InstockChangedLogEntry) { - return sprintf( - '%s; %s %s (%s); %s: %s', - $this->translator->trans($context->isWithdrawal() ? 'log.instock_changed.withdrawal' : 'log.instock_changed.added'), - $context->getOldInstock(), - $context->getNewInstock(), - (!$context->isWithdrawal() ? '+' : '-') . $context->getDifference(true), - $this->translator->trans('log.instock_changed.comment'), - htmlspecialchars($context->getComment()) - ); - } - - if ($context instanceof UserNotAllowedLogEntry) { - return htmlspecialchars($context->getMessage()); - } - - return ""; + return $this->formatter->format($context); } } \ No newline at end of file diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index e81ae101..e4742131 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -55,6 +55,18 @@ class LogEntryRepository extends EntityRepository return $this->findBy(['element' => $element], ['timestamp' => $order], $limit, $offset); } + /** + * Gets the last log entries ordered by timestamp + * @param string $order + * @param null $limit + * @param null $offset + * @return array + */ + public function getLogsOrderedByTimestamp($order = 'DESC', $limit = null, $offset = null) + { + return $this->findBy([], ['timestamp' => $order], $limit, $offset); + } + /** * Gets the target element associated with the logentry. * @param AbstractLogEntry $logEntry diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php new file mode 100644 index 00000000..5934d9e2 --- /dev/null +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -0,0 +1,138 @@ +translator = $translator; + } + + /** + * Return an user viewable representation of the extra data in a log entry, styled for console output. + * @param AbstractLogEntry $logEntry + * @return string + */ + public function formatConsole(AbstractLogEntry $logEntry): string + { + $tmp = $this->format($logEntry); + + //Just a simple tweak to make the console output more pretty. + $search = ['', '', '', '', ' ']; + $replace = ['', '', '', '', '→']; + + return str_replace($search, $replace, $tmp); + } + + /** + * Return a HTML formatted string containing a user viewable form of the Extra data + * @param AbstractLogEntry $context + * @return string + */ + public function format(AbstractLogEntry $context): string + { + if ($context instanceof UserLoginLogEntry || $context instanceof UserLogoutLogEntry) { + return sprintf( + "%s: %s", + $this->translator->trans('log.user_login.ip'), + htmlspecialchars($context->getIPAddress()) + ); + } + + if ($context instanceof ExceptionLogEntry) { + return sprintf( + '%s %s:%d : %s', + htmlspecialchars($context->getExceptionClass()), + htmlspecialchars($context->getFile()), + $context->getLine(), + htmlspecialchars($context->getMessage()) + ); + } + + if ($context instanceof DatabaseUpdatedLogEntry) { + return sprintf( + '%s %s %s', + $this->translator->trans($context->isSuccessful() ? 'log.database_updated.success' : 'log.database_updated.failure'), + $context->getOldVersion(), + $context->getNewVersion() + ); + } + + if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { + return sprintf( + '%s: %s', + $this->translator->trans('log.element_created.original_instock'), + $context->getCreationInstockValue() + ); + } + + if ($context instanceof ElementDeletedLogEntry) { + return sprintf( + '%s: %s', + $this->translator->trans('log.element_deleted.old_name'), + $context->getOldName() + ); + } + + if ($context instanceof ElementEditedLogEntry && !empty($context->getMessage())) { + return htmlspecialchars($context->getMessage()); + } + + if ($context instanceof InstockChangedLogEntry) { + return sprintf( + '%s; %s %s (%s); %s: %s', + $this->translator->trans($context->isWithdrawal() ? 'log.instock_changed.withdrawal' : 'log.instock_changed.added'), + $context->getOldInstock(), + $context->getNewInstock(), + (!$context->isWithdrawal() ? '+' : '-') . $context->getDifference(true), + $this->translator->trans('log.instock_changed.comment'), + htmlspecialchars($context->getComment()) + ); + } + + if ($context instanceof UserNotAllowedLogEntry) { + return htmlspecialchars($context->getMessage()); + } + + return ""; + } +} \ No newline at end of file