mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-23 18:28:49 +02:00
Allow to undo a change from event log.
This commit is contained in:
parent
15d25cf2b2
commit
5a5d7b24be
24 changed files with 659 additions and 30 deletions
|
@ -43,9 +43,20 @@ declare(strict_types=1);
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\DataTables\LogDataTable;
|
use App\DataTables\LogDataTable;
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Entity\LogSystem\CollectionElementDeleted;
|
||||||
|
use App\Entity\LogSystem\ElementCreatedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementDeletedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementEditedLogEntry;
|
||||||
|
use App\Services\LogSystem\EventUndoHelper;
|
||||||
|
use App\Services\LogSystem\TimeTravel;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Omines\DataTablesBundle\DataTableFactory;
|
use Omines\DataTablesBundle\DataTableFactory;
|
||||||
|
use phpDocumentor\Reflection\Element;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
@ -55,6 +66,18 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||||
*/
|
*/
|
||||||
class LogController extends AbstractController
|
class LogController extends AbstractController
|
||||||
{
|
{
|
||||||
|
protected $entityManager;
|
||||||
|
protected $timeTravel;
|
||||||
|
protected $dbRepository;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(EntityManagerInterface $entityManager, TimeTravel $timeTravel)
|
||||||
|
{
|
||||||
|
$this->entityManager = $entityManager;
|
||||||
|
$this->timeTravel = $timeTravel;
|
||||||
|
$this->dbRepository = $entityManager->getRepository(AbstractDBElement::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/", name="log_view")
|
* @Route("/", name="log_view")
|
||||||
*
|
*
|
||||||
|
@ -77,4 +100,63 @@ class LogController extends AbstractController
|
||||||
'datatable' => $table,
|
'datatable' => $table,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/undo", name="log_undo", methods={"POST"})
|
||||||
|
* @param Request $request
|
||||||
|
*/
|
||||||
|
public function undoLog(Request $request, EventUndoHelper $eventUndoHelper)
|
||||||
|
{
|
||||||
|
$id = $request->request->get('undo');
|
||||||
|
$log_element = $this->entityManager->find(AbstractLogEntry::class, $id);
|
||||||
|
|
||||||
|
$eventUndoHelper->setMode(EventUndoHelper::MODE_UNDO);
|
||||||
|
$eventUndoHelper->setUndoneEvent($log_element);
|
||||||
|
|
||||||
|
if ($log_element instanceof ElementDeletedLogEntry || $log_element instanceof CollectionElementDeleted) {
|
||||||
|
if ($log_element instanceof ElementDeletedLogEntry) {
|
||||||
|
$element_class = $log_element->getTargetClass();
|
||||||
|
$element_id = $log_element->getTargetID();
|
||||||
|
} else {
|
||||||
|
$element_class = $log_element->getDeletedElementClass();
|
||||||
|
$element_id = $log_element->getDeletedElementID();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the element we want to undelete already exits
|
||||||
|
if ($this->entityManager->find($element_class, $element_id) == null) {
|
||||||
|
$undeleted_element = $this->timeTravel->undeleteEntity($element_class, $element_id);
|
||||||
|
$this->entityManager->persist($undeleted_element);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->dbRepository->changeID($undeleted_element, $element_id);
|
||||||
|
$this->addFlash('success', 'log.undo.element_undelete_success');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'log.undo.element_element_already_undeleted');
|
||||||
|
}
|
||||||
|
} elseif ($log_element instanceof ElementCreatedLogEntry) {
|
||||||
|
$element = $this->entityManager->find($log_element->getTargetClass(), $log_element->getTargetID());
|
||||||
|
if ($element !== null) {
|
||||||
|
$this->entityManager->remove($element);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->addFlash('success', 'log.undo.element_delete_success');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('warning', 'log.undo.element.element_already_delted');
|
||||||
|
}
|
||||||
|
} elseif ($log_element instanceof ElementEditedLogEntry) {
|
||||||
|
$element = $this->entityManager->find($log_element->getTargetClass(), $log_element->getTargetID());
|
||||||
|
if ($element instanceof AbstractDBElement) {
|
||||||
|
$this->timeTravel->applyEntry($element, $log_element);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
$this->addFlash('success', 'log.undo.element_change_undone');
|
||||||
|
} else {
|
||||||
|
$this->addFlash('error', 'log.undo.do_undelete_before');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->addFlash('error', 'log.undo.log_type_invalid');
|
||||||
|
}
|
||||||
|
|
||||||
|
$eventUndoHelper->clearUndoneEvent();
|
||||||
|
|
||||||
|
$redirect = $request->request->get('redirect_back');
|
||||||
|
return $this->redirect($redirect);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ class IconLinkColumn extends AbstractColumn
|
||||||
|
|
||||||
if ($href !== null) {
|
if ($href !== null) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<a href="%s" title="%s"><i class="%s"></i></a>',
|
'<a class="btn btn-primary btn-sm" href="%s" title="%s"><i class="%s"></i></a>',
|
||||||
$href,
|
$href,
|
||||||
$title,
|
$title,
|
||||||
$icon
|
$icon
|
||||||
|
|
69
src/DataTables/Column/RevertLogColumn.php
Normal file
69
src/DataTables/Column/RevertLogColumn.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\DataTables\Column;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
use App\Entity\LogSystem\CollectionElementDeleted;
|
||||||
|
use App\Entity\LogSystem\ElementCreatedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementDeletedLogEntry;
|
||||||
|
use App\Entity\LogSystem\ElementEditedLogEntry;
|
||||||
|
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||||
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||||
|
|
||||||
|
class RevertLogColumn extends AbstractColumn
|
||||||
|
{
|
||||||
|
protected $translator;
|
||||||
|
|
||||||
|
public function __construct(TranslatorInterface $translator)
|
||||||
|
{
|
||||||
|
$this->translator = $translator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function normalize($value)
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render($value, $context)
|
||||||
|
{
|
||||||
|
if ($context instanceof ElementDeletedLogEntry || $context instanceof CollectionElementDeleted) {
|
||||||
|
$icon = 'fa-trash-restore';
|
||||||
|
$title = $this->translator->trans('log.undo.undelete');
|
||||||
|
} elseif ($context instanceof ElementEditedLogEntry || $context instanceof ElementCreatedLogEntry) {
|
||||||
|
$icon = 'fa-undo';
|
||||||
|
$title = $this->translator->trans('log.undo.undo');
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<button type="submit" class="btn btn-outline-secondary btn-sm" name="undo" value="%d"><i class="fas fa-fw %s" title="%s"></i></button>',
|
||||||
|
$context->getID(),
|
||||||
|
$icon,
|
||||||
|
$title
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,6 +46,7 @@ use App\DataTables\Column\IconLinkColumn;
|
||||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||||
use App\DataTables\Column\LogEntryExtraColumn;
|
use App\DataTables\Column\LogEntryExtraColumn;
|
||||||
use App\DataTables\Column\LogEntryTargetColumn;
|
use App\DataTables\Column\LogEntryTargetColumn;
|
||||||
|
use App\DataTables\Column\RevertLogColumn;
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Contracts\TimeTravelInterface;
|
use App\Entity\Contracts\TimeTravelInterface;
|
||||||
use App\Entity\LogSystem\AbstractLogEntry;
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
@ -218,8 +219,10 @@ class LogDataTable implements DataTableTypeInterface
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
$target = $this->logRepo->getTargetElement($context);
|
$target = $this->logRepo->getTargetElement($context);
|
||||||
$str = $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp());
|
if($target !== null) {
|
||||||
return $str;
|
$str = $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp());
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
} catch (EntityNotSupportedException $exception) {
|
} catch (EntityNotSupportedException $exception) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -228,6 +231,10 @@ class LogDataTable implements DataTableTypeInterface
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$dataTable->add('actionRevert', RevertLogColumn::class, [
|
||||||
|
'label' => ''
|
||||||
|
]);
|
||||||
|
|
||||||
$dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
|
$dataTable->addOrderBy('timestamp', DataTable::SORT_DESCENDING);
|
||||||
|
|
||||||
$dataTable->createAdapter(ORMAdapter::class, [
|
$dataTable->createAdapter(ORMAdapter::class, [
|
||||||
|
|
|
@ -34,7 +34,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
* Every database table which are managed with this class (or a subclass of it)
|
* Every database table which are managed with this class (or a subclass of it)
|
||||||
* must have the table row "id"!! The ID is the unique key to identify the elements.
|
* must have the table row "id"!! The ID is the unique key to identify the elements.
|
||||||
*
|
*
|
||||||
* @ORM\MappedSuperclass()
|
* @ORM\MappedSuperclass(repositoryClass="App\Repository\DBElementRepository")
|
||||||
*
|
*
|
||||||
* @ORM\EntityListeners({"App\Security\EntityListeners\ElementPermissionListener"})
|
* @ORM\EntityListeners({"App\Security\EntityListeners\ElementPermissionListener"})
|
||||||
*
|
*
|
||||||
|
|
|
@ -31,7 +31,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||||
/**
|
/**
|
||||||
* All subclasses of this class have an attribute "name".
|
* All subclasses of this class have an attribute "name".
|
||||||
*
|
*
|
||||||
* @ORM\MappedSuperclass(repositoryClass="App\Repository\UserRepository")
|
* @ORM\MappedSuperclass(repositoryClass="App\Repository\NamedDBElement")
|
||||||
* @ORM\HasLifecycleCallbacks()
|
* @ORM\HasLifecycleCallbacks()
|
||||||
*/
|
*/
|
||||||
abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface
|
abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface
|
||||||
|
|
55
src/Entity/Contracts/LogWithEventUndoInterface.php
Normal file
55
src/Entity/Contracts/LogWithEventUndoInterface.php
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Entity\Contracts;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
|
||||||
|
interface LogWithEventUndoInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if this element undoes another event.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isUndoEvent(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of the undone event or null if no event is undone.
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getUndoEventID(): ?int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the event that is undone, and the undo mode.
|
||||||
|
* @param AbstractLogEntry $event
|
||||||
|
* @param string $mode
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): self;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mode how the event was undone:
|
||||||
|
* "undo" = Only a single event was applied to element
|
||||||
|
* "revert" = Element was reverted to the state it was to the timestamp of the log.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUndoMode(): string;
|
||||||
|
}
|
|
@ -368,6 +368,17 @@ abstract class AbstractLogEntry extends AbstractDBElement
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the target ID of the element associated with this element.
|
||||||
|
* @param int $target_id
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setTargetElementID(int $target_id): self
|
||||||
|
{
|
||||||
|
$this->target_id = $target_id;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getExtraData(): array
|
public function getExtraData(): array
|
||||||
{
|
{
|
||||||
return $this->extra;
|
return $this->extra;
|
||||||
|
|
|
@ -22,6 +22,7 @@ namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use App\Entity\Contracts\LogWithEventUndoInterface;
|
||||||
use App\Entity\Contracts\NamedElementInterface;
|
use App\Entity\Contracts\NamedElementInterface;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
* This log entry is created when an element is deleted, that is used in a collection of an other entity.
|
* This log entry is created when an element is deleted, that is used in a collection of an other entity.
|
||||||
* This is needed to signal time travel, that it has to undelete the deleted entity.
|
* This is needed to signal time travel, that it has to undelete the deleted entity.
|
||||||
*/
|
*/
|
||||||
class CollectionElementDeleted extends AbstractLogEntry
|
class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventUndoInterface
|
||||||
{
|
{
|
||||||
protected $typeString = 'collection_element_deleted';
|
protected $typeString = 'collection_element_deleted';
|
||||||
protected $level = self::LEVEL_INFO;
|
protected $level = self::LEVEL_INFO;
|
||||||
|
@ -85,4 +86,51 @@ class CollectionElementDeleted extends AbstractLogEntry
|
||||||
{
|
{
|
||||||
return $this->extra['i'];
|
return $this->extra['i'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function isUndoEvent(): bool
|
||||||
|
{
|
||||||
|
return isset($this->extra['u']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoEventID(): ?int
|
||||||
|
{
|
||||||
|
return $this->extra['u'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface
|
||||||
|
{
|
||||||
|
$this->extra['u'] = $event->getID();
|
||||||
|
|
||||||
|
if ($mode === 'undo') {
|
||||||
|
$this->extra['um'] = 1;
|
||||||
|
} elseif ($mode === 'revert') {
|
||||||
|
$this->extra['um'] = 2;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('Passed invalid $mode!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoMode(): string
|
||||||
|
{
|
||||||
|
$mode_int = $this->extra['um'] ?? 1;
|
||||||
|
if ($mode_int === 1) {
|
||||||
|
return 'undo';
|
||||||
|
} else {
|
||||||
|
return 'revert';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -44,12 +44,13 @@ namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Contracts\LogWithCommentInterface;
|
use App\Entity\Contracts\LogWithCommentInterface;
|
||||||
|
use App\Entity\Contracts\LogWithEventUndoInterface;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity()
|
* @ORM\Entity()
|
||||||
*/
|
*/
|
||||||
class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentInterface
|
class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentInterface, LogWithEventUndoInterface
|
||||||
{
|
{
|
||||||
protected $typeString = 'element_created';
|
protected $typeString = 'element_created';
|
||||||
|
|
||||||
|
@ -104,4 +105,51 @@ class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentI
|
||||||
$this->extra['m'] = $new_comment;
|
$this->extra['m'] = $new_comment;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function isUndoEvent(): bool
|
||||||
|
{
|
||||||
|
return isset($this->extra['u']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoEventID(): ?int
|
||||||
|
{
|
||||||
|
return $this->extra['u'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface
|
||||||
|
{
|
||||||
|
$this->extra['u'] = $event->getID();
|
||||||
|
|
||||||
|
if ($mode === 'undo') {
|
||||||
|
$this->extra['um'] = 1;
|
||||||
|
} elseif ($mode === 'revert') {
|
||||||
|
$this->extra['um'] = 2;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('Passed invalid $mode!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoMode(): string
|
||||||
|
{
|
||||||
|
$mode_int = $this->extra['um'] ?? 1;
|
||||||
|
if ($mode_int === 1) {
|
||||||
|
return 'undo';
|
||||||
|
} else {
|
||||||
|
return 'revert';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Contracts\LogWithCommentInterface;
|
use App\Entity\Contracts\LogWithCommentInterface;
|
||||||
|
use App\Entity\Contracts\LogWithEventUndoInterface;
|
||||||
use App\Entity\Contracts\NamedElementInterface;
|
use App\Entity\Contracts\NamedElementInterface;
|
||||||
use App\Entity\Contracts\TimeTravelInterface;
|
use App\Entity\Contracts\TimeTravelInterface;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
@ -51,7 +52,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity()
|
* @ORM\Entity()
|
||||||
*/
|
*/
|
||||||
class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface
|
class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface
|
||||||
{
|
{
|
||||||
protected $typeString = 'element_deleted';
|
protected $typeString = 'element_deleted';
|
||||||
|
|
||||||
|
@ -137,4 +138,50 @@ class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInter
|
||||||
$this->extra['m'] = $new_comment;
|
$this->extra['m'] = $new_comment;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function isUndoEvent(): bool
|
||||||
|
{
|
||||||
|
return isset($this->extra['u']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoEventID(): ?int
|
||||||
|
{
|
||||||
|
return $this->extra['u'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface
|
||||||
|
{
|
||||||
|
$this->extra['u'] = $event->getID();
|
||||||
|
|
||||||
|
if ($mode === 'undo') {
|
||||||
|
$this->extra['um'] = 1;
|
||||||
|
} elseif ($mode === 'revert') {
|
||||||
|
$this->extra['um'] = 2;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('Passed invalid $mode!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoMode(): string
|
||||||
|
{
|
||||||
|
$mode_int = $this->extra['um'] ?? 1;
|
||||||
|
if ($mode_int === 1) {
|
||||||
|
return 'undo';
|
||||||
|
}
|
||||||
|
return 'revert';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,13 +44,14 @@ namespace App\Entity\LogSystem;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Contracts\LogWithCommentInterface;
|
use App\Entity\Contracts\LogWithCommentInterface;
|
||||||
|
use App\Entity\Contracts\LogWithEventUndoInterface;
|
||||||
use App\Entity\Contracts\TimeTravelInterface;
|
use App\Entity\Contracts\TimeTravelInterface;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity()
|
* @ORM\Entity()
|
||||||
*/
|
*/
|
||||||
class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface
|
class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface
|
||||||
{
|
{
|
||||||
protected $typeString = 'element_edited';
|
protected $typeString = 'element_edited';
|
||||||
|
|
||||||
|
@ -150,4 +151,51 @@ class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterf
|
||||||
$this->extra['m'] = $new_comment;
|
$this->extra['m'] = $new_comment;
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function isUndoEvent(): bool
|
||||||
|
{
|
||||||
|
return isset($this->extra['u']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoEventID(): ?int
|
||||||
|
{
|
||||||
|
return $this->extra['u'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface
|
||||||
|
{
|
||||||
|
$this->extra['u'] = $event->getID();
|
||||||
|
|
||||||
|
if ($mode === 'undo') {
|
||||||
|
$this->extra['um'] = 1;
|
||||||
|
} elseif ($mode === 'revert') {
|
||||||
|
$this->extra['um'] = 2;
|
||||||
|
} else {
|
||||||
|
throw new \InvalidArgumentException('Passed invalid $mode!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public function getUndoMode(): string
|
||||||
|
{
|
||||||
|
$mode_int = $this->extra['um'] ?? 1;
|
||||||
|
if ($mode_int === 1) {
|
||||||
|
return 'undo';
|
||||||
|
} else {
|
||||||
|
return 'revert';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ use App\Entity\PriceInformations\Pricedetail;
|
||||||
use App\Entity\UserSystem\User;
|
use App\Entity\UserSystem\User;
|
||||||
use App\Services\LogSystem\EventCommentHelper;
|
use App\Services\LogSystem\EventCommentHelper;
|
||||||
use App\Services\LogSystem\EventLogger;
|
use App\Services\LogSystem\EventLogger;
|
||||||
|
use App\Services\LogSystem\EventUndoHelper;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
|
@ -65,18 +66,21 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
protected $logger;
|
protected $logger;
|
||||||
protected $serializer;
|
protected $serializer;
|
||||||
protected $eventCommentHelper;
|
protected $eventCommentHelper;
|
||||||
|
protected $eventUndoHelper;
|
||||||
protected $save_changed_fields;
|
protected $save_changed_fields;
|
||||||
protected $save_changed_data;
|
protected $save_changed_data;
|
||||||
protected $save_removed_data;
|
protected $save_removed_data;
|
||||||
protected $propertyAccessor;
|
protected $propertyAccessor;
|
||||||
|
|
||||||
public function __construct(EventLogger $logger, SerializerInterface $serializer, EventCommentHelper $commentHelper,
|
public function __construct(EventLogger $logger, SerializerInterface $serializer, EventCommentHelper $commentHelper,
|
||||||
bool $save_changed_fields, bool $save_changed_data, bool $save_removed_data, PropertyAccessorInterface $propertyAccessor)
|
bool $save_changed_fields, bool $save_changed_data, bool $save_removed_data, PropertyAccessorInterface $propertyAccessor,
|
||||||
|
EventUndoHelper $eventUndoHelper)
|
||||||
{
|
{
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->serializer = $serializer;
|
$this->serializer = $serializer;
|
||||||
$this->eventCommentHelper = $commentHelper;
|
$this->eventCommentHelper = $commentHelper;
|
||||||
$this->propertyAccessor = $propertyAccessor;
|
$this->propertyAccessor = $propertyAccessor;
|
||||||
|
$this->eventUndoHelper = $eventUndoHelper;
|
||||||
|
|
||||||
$this->save_changed_fields = $save_changed_fields;
|
$this->save_changed_fields = $save_changed_fields;
|
||||||
$this->save_changed_data = $save_changed_data;
|
$this->save_changed_data = $save_changed_data;
|
||||||
|
@ -120,6 +124,18 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
if ($this->eventCommentHelper->isMessageSet()) {
|
if ($this->eventCommentHelper->isMessageSet()) {
|
||||||
$log->setComment($this->eventCommentHelper->getMessage());
|
$log->setComment($this->eventCommentHelper->getMessage());
|
||||||
}
|
}
|
||||||
|
if ($this->eventUndoHelper->isUndo()) {
|
||||||
|
$undoEvent = $this->eventUndoHelper->getUndoneEvent();
|
||||||
|
|
||||||
|
$log->setUndoneEvent($undoEvent, $this->eventUndoHelper->getMode());
|
||||||
|
|
||||||
|
if($undoEvent instanceof ElementDeletedLogEntry && $undoEvent->getTargetClass() === $log->getTargetClass()) {
|
||||||
|
$log->setTargetElementID($undoEvent->getTargetID());
|
||||||
|
}
|
||||||
|
if($undoEvent instanceof CollectionElementDeleted && $undoEvent->getDeletedElementClass() === $log->getTargetClass()) {
|
||||||
|
$log->setTargetElementID($undoEvent->getDeletedElementID());
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->logger->log($log);
|
$this->logger->log($log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,6 +151,7 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
|
|
||||||
//Clear the message provided by user.
|
//Clear the message provided by user.
|
||||||
$this->eventCommentHelper->clearMessage();
|
$this->eventCommentHelper->clearMessage();
|
||||||
|
$this->eventUndoHelper->clearUndoneEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void
|
protected function logElementDeleted(AbstractDBElement $entity, EntityManagerInterface $em): void
|
||||||
|
@ -144,6 +161,9 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
if ($this->eventCommentHelper->isMessageSet()) {
|
if ($this->eventCommentHelper->isMessageSet()) {
|
||||||
$log->setComment($this->eventCommentHelper->getMessage());
|
$log->setComment($this->eventCommentHelper->getMessage());
|
||||||
}
|
}
|
||||||
|
if ($this->eventUndoHelper->isUndo()) {
|
||||||
|
$log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode());
|
||||||
|
}
|
||||||
if ($this->save_removed_data) {
|
if ($this->save_removed_data) {
|
||||||
//The 4th param is important here, as we delete the element...
|
//The 4th param is important here, as we delete the element...
|
||||||
$this->saveChangeSet($entity, $log, $em, true);
|
$this->saveChangeSet($entity, $log, $em, true);
|
||||||
|
@ -162,6 +182,9 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
if (in_array($field, $whitelist)) {
|
if (in_array($field, $whitelist)) {
|
||||||
$changed = $this->propertyAccessor->getValue($entity, $field);
|
$changed = $this->propertyAccessor->getValue($entity, $field);
|
||||||
$log = new CollectionElementDeleted($changed, $mapping['inversedBy'], $entity);
|
$log = new CollectionElementDeleted($changed, $mapping['inversedBy'], $entity);
|
||||||
|
if ($this->eventUndoHelper->isUndo()) {
|
||||||
|
$log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode());
|
||||||
|
}
|
||||||
$this->logger->log($log);
|
$this->logger->log($log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,12 +202,16 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
$this->saveChangeSet($entity, $log, $em);
|
$this->saveChangeSet($entity, $log, $em);
|
||||||
} elseif ($this->save_changed_fields) {
|
} elseif ($this->save_changed_fields) {
|
||||||
$changed_fields = array_keys($uow->getEntityChangeSet($entity));
|
$changed_fields = array_keys($uow->getEntityChangeSet($entity));
|
||||||
|
unset($changed_fields['lastModified']);
|
||||||
$log->setChangedFields($changed_fields);
|
$log->setChangedFields($changed_fields);
|
||||||
}
|
}
|
||||||
//Add user comment to log entry
|
//Add user comment to log entry
|
||||||
if ($this->eventCommentHelper->isMessageSet()) {
|
if ($this->eventCommentHelper->isMessageSet()) {
|
||||||
$log->setComment($this->eventCommentHelper->getMessage());
|
$log->setComment($this->eventCommentHelper->getMessage());
|
||||||
}
|
}
|
||||||
|
if ($this->eventUndoHelper->isUndo()) {
|
||||||
|
$log->setUndoneEvent($this->eventUndoHelper->getUndoneEvent(), $this->eventUndoHelper->getMode());
|
||||||
|
}
|
||||||
$this->logger->log($log);
|
$this->logger->log($log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,6 +239,8 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
*/
|
*/
|
||||||
protected function filterFieldRestrictions(AbstractDBElement $element, array $fields): array
|
protected function filterFieldRestrictions(AbstractDBElement $element, array $fields): array
|
||||||
{
|
{
|
||||||
|
unset($fields['lastModified']);
|
||||||
|
|
||||||
if (!$this->hasFieldRestrictions($element)) {
|
if (!$this->hasFieldRestrictions($element)) {
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
@ -256,9 +285,9 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
$old_data = $uow->getOriginalEntityData($entity);
|
$old_data = $uow->getOriginalEntityData($entity);
|
||||||
} else { //Otherwise we have to get it from entity changeset
|
} else { //Otherwise we have to get it from entity changeset
|
||||||
$changeSet = $uow->getEntityChangeSet($entity);
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
$old_data = array_diff(array_combine(array_keys($changeSet), array_column($changeSet, 0)), [null]);
|
$old_data = array_combine(array_keys($changeSet), array_column($changeSet, 0));
|
||||||
}
|
}
|
||||||
$this->filterFieldRestrictions($entity, $old_data);
|
$old_data = $this->filterFieldRestrictions($entity, $old_data);
|
||||||
|
|
||||||
//Restrict length of string fields, to save memory...
|
//Restrict length of string fields, to save memory...
|
||||||
$old_data = array_map(function ($value) {
|
$old_data = array_map(function ($value) {
|
||||||
|
@ -271,6 +300,7 @@ class EventLoggerSubscriber implements EventSubscriber
|
||||||
|
|
||||||
$logEntry->setOldData($old_data);
|
$logEntry->setOldData($old_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given entity can be logged.
|
* Check if the given entity can be logged.
|
||||||
* @param object $entity
|
* @param object $entity
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace App\Repository;
|
||||||
|
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
class AttachmentRepository extends EntityRepository
|
class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Gets the count of all private/secure attachments.
|
* Gets the count of all private/secure attachments.
|
||||||
|
|
56
src/Repository/DBElementRepository.php
Normal file
56
src/Repository/DBElementRepository.php
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\Base\AbstractDBElement;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
|
class DBElementRepository extends EntityRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Changes the ID of the given element to a new value.
|
||||||
|
* You should only use it to undelete former existing elements, everything else is most likely a bad idea!
|
||||||
|
* @param AbstractDBElement $element The element whose ID should be changed
|
||||||
|
* @param int $new_id The new ID
|
||||||
|
*/
|
||||||
|
public function changeID(AbstractDBElement $element, int $new_id): void
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('element');
|
||||||
|
$q = $qb->update(get_class($element), 'element')
|
||||||
|
->set('element.id', $new_id)
|
||||||
|
->where('element.id = ?1')
|
||||||
|
->setParameter(1, $element->getID())
|
||||||
|
->getQuery();
|
||||||
|
|
||||||
|
$p = $q->execute();
|
||||||
|
|
||||||
|
$this->setField($element, 'id', $new_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setField(AbstractDBElement $element, string $field, $new_value)
|
||||||
|
{
|
||||||
|
$reflection = new \ReflectionClass(get_class($element));
|
||||||
|
$property = $reflection->getProperty($field);
|
||||||
|
$property->setAccessible(true);
|
||||||
|
$property->setValue($element, $new_value);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ use App\Entity\UserSystem\User;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
class LogEntryRepository extends EntityRepository
|
class LogEntryRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ use App\Entity\Base\AbstractNamedDBElement;
|
||||||
use App\Helpers\Trees\TreeViewNode;
|
use App\Helpers\Trees\TreeViewNode;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
class NamedDBElementRepository extends EntityRepository
|
class NamedDBElementRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Gets a tree of TreeViewNode elements. The root elements has $parent as parent.
|
* Gets a tree of TreeViewNode elements. The root elements has $parent as parent.
|
||||||
|
|
|
@ -46,7 +46,7 @@ use App\Entity\Parts\PartLot;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
class PartRepository extends EntityRepository
|
class PartRepository extends NamedDBElementRepository
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Gets the summed up instock of all parts (only parts without an measurent unit)
|
* Gets the summed up instock of all parts (only parts without an measurent unit)
|
||||||
|
|
90
src/Services/LogSystem/EventUndoHelper.php
Normal file
90
src/Services/LogSystem/EventUndoHelper.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||||
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Services\LogSystem;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\LogSystem\AbstractLogEntry;
|
||||||
|
|
||||||
|
class EventUndoHelper
|
||||||
|
{
|
||||||
|
public const MODE_UNDO = 'undo';
|
||||||
|
public const MODE_REVERT = 'revert';
|
||||||
|
|
||||||
|
protected const ALLOWED_MODES = [self::MODE_REVERT, self::MODE_UNDO];
|
||||||
|
|
||||||
|
protected $undone_event;
|
||||||
|
protected $mode;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$undone_event = null;
|
||||||
|
$this->mode = self::MODE_UNDO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMode(string $mode): void
|
||||||
|
{
|
||||||
|
if (!in_array($mode, self::ALLOWED_MODES)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid mode passed!');
|
||||||
|
}
|
||||||
|
$this->mode = $mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMode(): string
|
||||||
|
{
|
||||||
|
return $this->mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set which event log is currently undone.
|
||||||
|
* After the flush this message is cleared.
|
||||||
|
* @param AbstractLogEntry|null $message
|
||||||
|
*/
|
||||||
|
public function setUndoneEvent(?AbstractLogEntry $undone_event): void
|
||||||
|
{
|
||||||
|
$this->undone_event = $undone_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns event that is currently undone.
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getUndoneEvent(): ?AbstractLogEntry
|
||||||
|
{
|
||||||
|
return $this->undone_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the currently the set undone event.
|
||||||
|
*/
|
||||||
|
public function clearUndoneEvent(): void
|
||||||
|
{
|
||||||
|
$this->undone_event = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a event is undone
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isUndo(): bool
|
||||||
|
{
|
||||||
|
return ($this->undone_event instanceof AbstractLogEntry);
|
||||||
|
}
|
||||||
|
}
|
|
@ -123,8 +123,15 @@ class LogEntryExtraFormatter
|
||||||
|
|
||||||
if ($context instanceof ElementCreatedLogEntry ) {
|
if ($context instanceof ElementCreatedLogEntry ) {
|
||||||
$comment = '';
|
$comment = '';
|
||||||
|
if ($context->isUndoEvent()) {
|
||||||
|
if ($context->getUndoMode() === 'undo') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.undo').': '.$context->getUndoEventID().';';
|
||||||
|
} elseif($context->getUndoMode() === 'revert') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.revert').': '.$context->getUndoEventID().';';
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($context->hasComment()) {
|
if ($context->hasComment()) {
|
||||||
$comment = htmlspecialchars($context->getComment()) . '; ';
|
$comment .= htmlspecialchars($context->getComment()) . '; ';
|
||||||
}
|
}
|
||||||
if($context->hasCreationInstockValue()) {
|
if($context->hasCreationInstockValue()) {
|
||||||
return $comment . sprintf(
|
return $comment . sprintf(
|
||||||
|
@ -138,8 +145,15 @@ class LogEntryExtraFormatter
|
||||||
|
|
||||||
if ($context instanceof ElementDeletedLogEntry) {
|
if ($context instanceof ElementDeletedLogEntry) {
|
||||||
$comment = '';
|
$comment = '';
|
||||||
|
if ($context->isUndoEvent()) {
|
||||||
|
if ($context->getUndoMode() === 'undo') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.undo').': '.$context->getUndoEventID().';';
|
||||||
|
} elseif($context->getUndoMode() === 'revert') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.revert').': '.$context->getUndoEventID().';';
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($context->hasComment()) {
|
if ($context->hasComment()) {
|
||||||
$comment = htmlspecialchars($context->getComment()) . '; ';
|
$comment .= htmlspecialchars($context->getComment()) . '; ';
|
||||||
}
|
}
|
||||||
return $comment . sprintf(
|
return $comment . sprintf(
|
||||||
'<i>%s</i>: %s',
|
'<i>%s</i>: %s',
|
||||||
|
@ -150,6 +164,13 @@ class LogEntryExtraFormatter
|
||||||
|
|
||||||
if ($context instanceof ElementEditedLogEntry) {
|
if ($context instanceof ElementEditedLogEntry) {
|
||||||
$str = '';
|
$str = '';
|
||||||
|
if ($context->isUndoEvent()) {
|
||||||
|
if ($context->getUndoMode() === 'undo') {
|
||||||
|
$str .= $this->translator->trans('log.undo_mode.undo').': '.$context->getUndoEventID().';';
|
||||||
|
} elseif($context->getUndoMode() === 'revert') {
|
||||||
|
$str .= $this->translator->trans('log.undo_mode.revert').': '.$context->getUndoEventID().';';
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($context->hasComment()) {
|
if ($context->hasComment()) {
|
||||||
$str .= htmlspecialchars($context->getComment());
|
$str .= htmlspecialchars($context->getComment());
|
||||||
}
|
}
|
||||||
|
@ -180,7 +201,16 @@ class LogEntryExtraFormatter
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($context instanceof CollectionElementDeleted) {
|
if ($context instanceof CollectionElementDeleted) {
|
||||||
return sprintf('<i>%s</i>: %s: %s (%s)',
|
$comment = '';
|
||||||
|
if ($context->isUndoEvent()) {
|
||||||
|
if ($context->getUndoMode() === 'undo') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.undo').': '.$context->getUndoEventID().';';
|
||||||
|
} elseif($context->getUndoMode() === 'revert') {
|
||||||
|
$comment .= $this->translator->trans('log.undo_mode.revert').': '.$context->getUndoEventID().';';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $comment . sprintf('<i>%s</i>: %s: %s (%s)',
|
||||||
$this->translator->trans('log.collection_deleted.deleted'),
|
$this->translator->trans('log.collection_deleted.deleted'),
|
||||||
$this->elementTypeNameGenerator->getLocalizedTypeLabel($context->getDeletedElementClass()),
|
$this->elementTypeNameGenerator->getLocalizedTypeLabel($context->getDeletedElementClass()),
|
||||||
$context->getOldName() ?? $context->getDeletedElementID(),
|
$context->getOldName() ?? $context->getDeletedElementID(),
|
||||||
|
|
|
@ -60,6 +60,9 @@ class TimeTravel
|
||||||
//Set internal ID so the element can be reverted
|
//Set internal ID so the element can be reverted
|
||||||
$this->setField($element, 'id', $id);
|
$this->setField($element, 'id', $id);
|
||||||
|
|
||||||
|
//Let database determine when it will be created
|
||||||
|
$this->setField($element,'addedDate', null);
|
||||||
|
|
||||||
return $element;
|
return $element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
templates/LogSystem/_log_table.html.twig
Normal file
14
templates/LogSystem/_log_table.html.twig
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<form method="post" action="{{ url("log_undo") }}" data-delete-form data-title="{% trans %}log.undo.confirm_title{% endtrans %}"
|
||||||
|
data-message="{% trans %}log.undo.confirm_message{% endtrans %}">>
|
||||||
|
<input type="hidden" name="redirect_back" value="{{ app.request.uri }}">
|
||||||
|
<div id="part_list" class="" data-datatable data-settings='{{ datatable_settings(datatable) }}'>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
||||||
|
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -3,14 +3,5 @@
|
||||||
{% block title %}{% trans %}log.list.title{% endtrans %}{% endblock %}
|
{% block title %}{% trans %}log.list.title{% endtrans %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="part_list" class="" data-datatable data-settings='{{ datatable_settings(datatable) }}'>
|
{% include "LogSystem/_log_table.html.twig" %}
|
||||||
<div class="card-body">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body">
|
|
||||||
<h4>{% trans %}part_list.loading.caption{% endtrans %}</h4>
|
|
||||||
<h6>{% trans %}part_list.loading.message{% endtrans %}</h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -1,3 +1,3 @@
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{% include "Parts/lists/_parts_list.html.twig" %}
|
{% include "LogSystem/_log_table.html.twig" %}
|
||||||
</div>
|
</div>
|
Loading…
Add table
Add a link
Reference in a new issue