2020-01-24 22:57:04 +01:00
|
|
|
<?php
|
2020-02-22 18:14:36 +01:00
|
|
|
/**
|
|
|
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
|
|
|
*
|
2022-11-29 22:28:53 +01:00
|
|
|
* Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics)
|
2020-02-22 18:14:36 +01:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2020-02-01 16:17:20 +01:00
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-01-24 22:57:04 +01:00
|
|
|
namespace App\DataTables;
|
|
|
|
|
2023-06-18 18:31:39 +02:00
|
|
|
use App\DataTables\Column\EnumColumn;
|
|
|
|
use App\Entity\LogSystem\LogTargetType;
|
2023-06-11 14:55:06 +02:00
|
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
2020-02-22 20:04:43 +01:00
|
|
|
use App\DataTables\Column\IconLinkColumn;
|
2020-01-24 22:57:04 +01:00
|
|
|
use App\DataTables\Column\LocaleDateTimeColumn;
|
2020-01-25 22:52:34 +01:00
|
|
|
use App\DataTables\Column\LogEntryExtraColumn;
|
2020-01-24 22:57:04 +01:00
|
|
|
use App\DataTables\Column\LogEntryTargetColumn;
|
2020-03-01 19:46:48 +01:00
|
|
|
use App\DataTables\Column\RevertLogColumn;
|
2022-12-17 01:19:52 +01:00
|
|
|
use App\DataTables\Column\RowClassColumn;
|
2022-09-11 18:45:31 +02:00
|
|
|
use App\DataTables\Filters\LogFilter;
|
2020-02-22 20:04:43 +01:00
|
|
|
use App\Entity\Base\AbstractDBElement;
|
|
|
|
use App\Entity\Contracts\TimeTravelInterface;
|
2020-01-24 22:57:04 +01:00
|
|
|
use App\Entity\LogSystem\AbstractLogEntry;
|
2020-02-29 22:53:53 +01:00
|
|
|
use App\Entity\LogSystem\CollectionElementDeleted;
|
2020-03-07 17:15:16 +01:00
|
|
|
use App\Entity\LogSystem\ElementCreatedLogEntry;
|
|
|
|
use App\Entity\LogSystem\ElementDeletedLogEntry;
|
|
|
|
use App\Entity\LogSystem\ElementEditedLogEntry;
|
2023-01-08 01:41:04 +01:00
|
|
|
use App\Entity\LogSystem\PartStockChangedLogEntry;
|
2020-03-07 22:30:28 +01:00
|
|
|
use App\Entity\UserSystem\User;
|
2020-02-22 20:04:43 +01:00
|
|
|
use App\Exceptions\EntityNotSupportedException;
|
2022-09-18 22:59:31 +02:00
|
|
|
use App\Repository\LogEntryRepository;
|
2020-01-24 22:57:04 +01:00
|
|
|
use App\Services\ElementTypeNameGenerator;
|
2020-02-22 20:04:43 +01:00
|
|
|
use App\Services\EntityURLGenerator;
|
2023-04-10 00:30:23 +02:00
|
|
|
use App\Services\LogSystem\LogLevelHelper;
|
2023-01-23 23:01:57 +01:00
|
|
|
use App\Services\UserSystem\UserAvatarHelper;
|
2020-02-22 20:04:43 +01:00
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2020-01-24 22:57:04 +01:00
|
|
|
use Doctrine\ORM\QueryBuilder;
|
2022-09-11 18:45:31 +02:00
|
|
|
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
|
2020-01-24 22:57:04 +01:00
|
|
|
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
|
|
|
|
use Omines\DataTablesBundle\Column\TextColumn;
|
|
|
|
use Omines\DataTablesBundle\DataTable;
|
|
|
|
use Omines\DataTablesBundle\DataTableTypeInterface;
|
2020-04-04 15:45:14 +02:00
|
|
|
use Symfony\Component\OptionsResolver\Options;
|
2020-02-22 20:04:43 +01:00
|
|
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
2020-01-25 20:28:00 +01:00
|
|
|
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
2020-01-24 22:57:04 +01:00
|
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
|
|
|
|
class LogDataTable implements DataTableTypeInterface
|
|
|
|
{
|
2022-09-18 22:59:31 +02:00
|
|
|
protected LogEntryRepository $logRepo;
|
2020-01-24 22:57:04 +01:00
|
|
|
|
2023-06-11 14:15:46 +02:00
|
|
|
public function __construct(protected ElementTypeNameGenerator $elementTypeNameGenerator, protected TranslatorInterface $translator,
|
|
|
|
protected UrlGeneratorInterface $urlGenerator, protected EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager,
|
2023-06-11 14:55:06 +02:00
|
|
|
protected Security $security, protected UserAvatarHelper $userAvatarHelper, protected LogLevelHelper $logLevelHelper)
|
2020-01-24 22:57:04 +01:00
|
|
|
{
|
2020-02-22 20:04:43 +01:00
|
|
|
$this->logRepo = $entityManager->getRepository(AbstractLogEntry::class);
|
|
|
|
}
|
|
|
|
|
2020-03-15 13:56:31 +01:00
|
|
|
public function configureOptions(OptionsResolver $optionsResolver): void
|
2020-02-22 20:04:43 +01:00
|
|
|
{
|
|
|
|
$optionsResolver->setDefaults([
|
2020-03-15 13:56:31 +01:00
|
|
|
'mode' => 'system_log',
|
|
|
|
'filter_elements' => [],
|
2022-09-11 18:45:31 +02:00
|
|
|
'filter' => null,
|
2020-03-15 13:56:31 +01:00
|
|
|
]);
|
2020-02-22 20:04:43 +01:00
|
|
|
|
2020-04-04 15:45:14 +02:00
|
|
|
$optionsResolver->setAllowedTypes('filter_elements', ['array', 'object']);
|
|
|
|
$optionsResolver->setAllowedTypes('mode', 'string');
|
2022-09-11 19:04:05 +02:00
|
|
|
$optionsResolver->setAllowedTypes('filter', ['null', LogFilter::class]);
|
2020-04-04 15:45:14 +02:00
|
|
|
|
2020-08-21 22:43:37 +02:00
|
|
|
$optionsResolver->setNormalizer('filter_elements', static function (Options $options, $value) {
|
2020-08-21 21:36:22 +02:00
|
|
|
if (!is_array($value)) {
|
2020-04-04 15:45:14 +02:00
|
|
|
return [$value];
|
|
|
|
}
|
2020-04-10 13:05:08 +02:00
|
|
|
|
2020-04-04 15:45:14 +02:00
|
|
|
return $value;
|
|
|
|
});
|
|
|
|
|
2020-03-07 17:15:16 +01:00
|
|
|
$optionsResolver->setAllowedValues('mode', ['system_log', 'element_history', 'last_activity']);
|
2020-01-24 22:57:04 +01:00
|
|
|
}
|
|
|
|
|
2020-02-01 16:17:20 +01:00
|
|
|
public function configure(DataTable $dataTable, array $options): void
|
2020-01-24 22:57:04 +01:00
|
|
|
{
|
2020-02-22 20:04:43 +01:00
|
|
|
$resolver = new OptionsResolver();
|
|
|
|
$this->configureOptions($resolver);
|
|
|
|
$options = $resolver->resolve($options);
|
|
|
|
|
2022-12-17 01:03:22 +01:00
|
|
|
//This special $$rowClass column is used to set the row class depending on the log level. The class gets set by the frontend controller
|
2022-12-17 01:19:52 +01:00
|
|
|
$dataTable->add('dont_matter', RowClassColumn::class, [
|
2023-06-11 14:15:46 +02:00
|
|
|
'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()),
|
2022-12-17 01:03:22 +01:00
|
|
|
]);
|
|
|
|
|
2020-01-25 20:55:30 +01:00
|
|
|
$dataTable->add('symbol', TextColumn::class, [
|
|
|
|
'label' => '',
|
2022-09-11 00:46:12 +02:00
|
|
|
'className' => 'no-colvis',
|
2023-06-11 14:15:46 +02:00
|
|
|
'render' => fn($value, AbstractLogEntry $context): string => sprintf(
|
|
|
|
'<i class="fas fa-fw %s" title="%s"></i>',
|
|
|
|
$this->logLevelHelper->logLevelToIconClass($context->getLevelString()),
|
|
|
|
$context->getLevelString()
|
|
|
|
),
|
2020-01-25 20:55:30 +01:00
|
|
|
]);
|
|
|
|
|
2020-01-24 22:57:04 +01:00
|
|
|
$dataTable->add('id', TextColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.id',
|
2020-01-24 22:57:04 +01:00
|
|
|
'visible' => false,
|
|
|
|
]);
|
|
|
|
|
|
|
|
$dataTable->add('timestamp', LocaleDateTimeColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.timestamp',
|
2020-02-01 16:17:20 +01:00
|
|
|
'timeFormat' => 'medium',
|
2023-06-11 14:15:46 +02:00
|
|
|
'render' => fn(string $value, AbstractLogEntry $context): string => sprintf('<a href="%s">%s</a>',
|
2023-06-18 00:00:58 +02:00
|
|
|
$this->urlGenerator->generate('log_details', ['id' => $context->getID()]),
|
2023-06-11 14:15:46 +02:00
|
|
|
$value
|
|
|
|
)
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
|
|
|
$dataTable->add('type', TextColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.type',
|
2020-01-24 22:57:04 +01:00
|
|
|
'propertyPath' => 'type',
|
|
|
|
'render' => function (string $value, AbstractLogEntry $context) {
|
2023-01-08 01:41:04 +01:00
|
|
|
$text = $this->translator->trans('log.type.'.$value);
|
|
|
|
|
|
|
|
if ($context instanceof PartStockChangedLogEntry) {
|
|
|
|
$text .= sprintf(
|
2023-01-08 01:45:34 +01:00
|
|
|
' (<i>%s</i>)',
|
2023-11-25 19:16:54 +01:00
|
|
|
$this->translator->trans($context->getInstockChangeType()->toTranslationKey())
|
2023-01-08 01:41:04 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $text;
|
2020-02-01 16:17:20 +01:00
|
|
|
},
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
|
|
|
$dataTable->add('level', TextColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.level',
|
2020-03-15 13:56:31 +01:00
|
|
|
'visible' => 'system_log' === $options['mode'],
|
2020-01-24 22:57:04 +01:00
|
|
|
'propertyPath' => 'levelString',
|
2023-06-11 14:15:46 +02:00
|
|
|
'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value),
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
|
|
|
$dataTable->add('user', TextColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.user',
|
2024-06-17 21:38:16 +02:00
|
|
|
'orderField' => 'NATSORT(user.name)',
|
2023-06-11 14:15:46 +02:00
|
|
|
'render' => function ($value, AbstractLogEntry $context): string {
|
2020-01-25 20:28:00 +01:00
|
|
|
$user = $context->getUser();
|
2020-02-01 16:17:20 +01:00
|
|
|
|
2022-12-17 00:25:54 +01:00
|
|
|
//If user was deleted, show the info from the username field
|
2023-06-11 14:55:06 +02:00
|
|
|
if (!$user instanceof User) {
|
2023-04-08 01:13:13 +02:00
|
|
|
if ($context->isCLIEntry()) {
|
2023-04-07 22:44:59 +02:00
|
|
|
return sprintf('%s [%s]',
|
2024-06-22 00:31:43 +02:00
|
|
|
htmlentities((string) $context->getCLIUsername()),
|
2023-04-07 22:44:59 +02:00
|
|
|
$this->translator->trans('log.cli_user')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
//Else we just deal with a deleted user
|
2022-12-17 00:25:54 +01:00
|
|
|
return sprintf(
|
|
|
|
'@%s [%s]',
|
|
|
|
htmlentities($context->getUsername()),
|
|
|
|
$this->translator->trans('log.target_deleted'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-23 23:01:57 +01:00
|
|
|
$img_url = $this->userAvatarHelper->getAvatarSmURL($user);
|
|
|
|
|
2020-01-25 20:28:00 +01:00
|
|
|
return sprintf(
|
2023-02-05 20:06:53 +01:00
|
|
|
'<img src="%s" data-thumbnail="%s" class="avatar-xs" data-controller="elements--hoverpic"> <a href="%s">%s</a>',
|
2023-01-23 23:01:57 +01:00
|
|
|
$img_url,
|
2023-01-25 00:16:10 +01:00
|
|
|
$this->userAvatarHelper->getAvatarMdURL($user),
|
2020-01-25 20:28:00 +01:00
|
|
|
$this->urlGenerator->generate('user_info', ['id' => $user->getID()]),
|
2022-12-17 00:25:54 +01:00
|
|
|
htmlentities($user->getFullName(true))
|
2020-01-25 20:28:00 +01:00
|
|
|
);
|
2020-02-01 16:17:20 +01:00
|
|
|
},
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
2023-06-18 18:31:39 +02:00
|
|
|
$dataTable->add('target_type', EnumColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.target_type',
|
2020-01-24 22:57:04 +01:00
|
|
|
'visible' => false,
|
2023-06-18 18:31:39 +02:00
|
|
|
'class' => LogTargetType::class,
|
|
|
|
'render' => function (LogTargetType $value, AbstractLogEntry $context) {
|
|
|
|
$class = $value->toClass();
|
2020-02-01 16:17:20 +01:00
|
|
|
if (null !== $class) {
|
2020-01-24 22:57:04 +01:00
|
|
|
return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class);
|
|
|
|
}
|
2020-02-01 16:17:20 +01:00
|
|
|
|
2020-01-24 22:57:04 +01:00
|
|
|
return '';
|
2020-02-01 16:17:20 +01:00
|
|
|
},
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
|
|
|
$dataTable->add('target', LogEntryTargetColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.target',
|
2020-03-29 22:16:06 +02:00
|
|
|
'show_associated' => 'element_history' !== $options['mode'],
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
|
2020-01-25 22:52:34 +01:00
|
|
|
$dataTable->add('extra', LogEntryExtraColumn::class, [
|
2023-02-11 23:39:11 +01:00
|
|
|
'label' => 'log.extra',
|
2020-01-25 22:52:34 +01:00
|
|
|
]);
|
|
|
|
|
2020-03-15 13:56:31 +01:00
|
|
|
$dataTable->add('timeTravel', IconLinkColumn::class, [
|
2020-02-22 20:04:43 +01:00
|
|
|
'label' => '',
|
|
|
|
'icon' => 'fas fa-fw fa-eye',
|
|
|
|
'href' => function ($value, AbstractLogEntry $context) {
|
|
|
|
if (
|
2020-03-07 22:30:28 +01:00
|
|
|
($context instanceof TimeTravelInterface
|
2023-05-14 21:41:00 +02:00
|
|
|
&& $context->hasOldDataInformation())
|
2020-03-07 22:30:28 +01:00
|
|
|
|| $context instanceof CollectionElementDeleted
|
2020-02-22 20:04:43 +01:00
|
|
|
) {
|
|
|
|
try {
|
|
|
|
$target = $this->logRepo->getTargetElement($context);
|
2023-06-11 14:55:06 +02:00
|
|
|
if ($target instanceof AbstractDBElement) {
|
2020-03-15 13:56:31 +01:00
|
|
|
return $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp());
|
2020-03-01 19:46:48 +01:00
|
|
|
}
|
2023-06-11 14:15:46 +02:00
|
|
|
} catch (EntityNotSupportedException) {
|
2020-02-22 20:04:43 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
2020-03-15 13:56:31 +01:00
|
|
|
|
2020-02-22 20:04:43 +01:00
|
|
|
return null;
|
2020-03-07 20:49:52 +01:00
|
|
|
},
|
2023-06-11 14:15:46 +02:00
|
|
|
'disabled' => fn($value, AbstractLogEntry $context) => !$this->security->isGranted('show_history', $context->getTargetClass()),
|
2020-02-22 20:04:43 +01:00
|
|
|
]);
|
|
|
|
|
2020-03-01 19:46:48 +01:00
|
|
|
$dataTable->add('actionRevert', RevertLogColumn::class, [
|
2020-03-15 13:56:31 +01:00
|
|
|
'label' => '',
|
2020-03-01 19:46:48 +01:00
|
|
|
]);
|
|
|
|
|
2020-01-24 22:57:04 +01:00
|
|
|
$dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
|
|
|
|
|
|
|
|
$dataTable->createAdapter(ORMAdapter::class, [
|
|
|
|
'entity' => AbstractLogEntry::class,
|
2020-02-22 20:04:43 +01:00
|
|
|
'query' => function (QueryBuilder $builder) use ($options): void {
|
|
|
|
$this->getQuery($builder, $options);
|
2020-01-24 22:57:04 +01:00
|
|
|
},
|
2022-09-11 18:45:31 +02:00
|
|
|
'criteria' => [
|
|
|
|
function (QueryBuilder $builder) use ($options): void {
|
|
|
|
$this->buildCriteria($builder, $options);
|
|
|
|
},
|
|
|
|
new SearchCriteriaProvider(),
|
|
|
|
],
|
2020-01-24 22:57:04 +01:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2022-09-11 18:45:31 +02:00
|
|
|
private function buildCriteria(QueryBuilder $builder, array $options): void
|
|
|
|
{
|
|
|
|
if (!empty($options['filter'])) {
|
|
|
|
$filter = $options['filter'];
|
|
|
|
$filter->apply($builder);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-02-22 20:04:43 +01:00
|
|
|
protected function getQuery(QueryBuilder $builder, array $options): void
|
2020-01-24 22:57:04 +01:00
|
|
|
{
|
2022-09-25 14:43:15 +02:00
|
|
|
$builder->select('log')
|
2020-01-24 22:57:04 +01:00
|
|
|
->addSelect('user')
|
|
|
|
->from(AbstractLogEntry::class, 'log')
|
|
|
|
->leftJoin('log.user', 'user');
|
2020-02-22 20:04:43 +01:00
|
|
|
|
2022-09-11 18:45:31 +02:00
|
|
|
/* Do this here as we don't want to show up the global count of all log entries in the footer line, with these modes */
|
2020-03-15 13:56:31 +01:00
|
|
|
if ('last_activity' === $options['mode']) {
|
|
|
|
$builder->where('log INSTANCE OF '.ElementCreatedLogEntry::class)
|
|
|
|
->orWhere('log INSTANCE OF '.ElementDeletedLogEntry::class)
|
|
|
|
->orWhere('log INSTANCE OF '.ElementEditedLogEntry::class)
|
|
|
|
->orWhere('log INSTANCE OF '.CollectionElementDeleted::class)
|
|
|
|
->andWhere('log.target_type NOT IN (:disallowed)');
|
2020-03-07 22:30:28 +01:00
|
|
|
|
|
|
|
$builder->setParameter('disallowed', [
|
2023-06-18 18:31:39 +02:00
|
|
|
LogTargetType::USER,
|
|
|
|
LogTargetType::GROUP,
|
2020-03-07 22:30:28 +01:00
|
|
|
]);
|
2020-03-07 17:15:16 +01:00
|
|
|
}
|
|
|
|
|
2020-08-21 21:36:22 +02:00
|
|
|
if (!empty($options['filter_elements'])) {
|
2020-02-22 20:04:43 +01:00
|
|
|
foreach ($options['filter_elements'] as $element) {
|
|
|
|
/** @var AbstractDBElement $element */
|
|
|
|
|
2023-06-18 18:31:39 +02:00
|
|
|
$target_type = LogTargetType::fromElementClass($element);
|
2020-02-22 20:04:43 +01:00
|
|
|
$target_id = $element->getID();
|
2023-06-18 18:31:39 +02:00
|
|
|
|
2023-07-19 00:01:42 +02:00
|
|
|
//We have to create unique parameter names for each element
|
|
|
|
$target_type_var = 'filter_target_type_' . uniqid('', false);
|
|
|
|
$target_id_var = 'filter_target_id_' . uniqid('', false);
|
|
|
|
|
|
|
|
$builder->orWhere("log.target_type = :$target_type_var AND log.target_id = :$target_id_var");
|
|
|
|
$builder->setParameter($target_type_var, $target_type);
|
|
|
|
$builder->setParameter($target_id_var, $target_id);
|
2020-02-22 20:04:43 +01:00
|
|
|
}
|
|
|
|
}
|
2020-01-24 22:57:04 +01:00
|
|
|
}
|
2020-02-01 16:17:20 +01:00
|
|
|
}
|