Allow to add a comment when editing/creating/deleting an element.

This commit is contained in:
Jan Böhmer 2020-02-23 00:44:52 +01:00
parent c14d6d91ff
commit b6f95ebe48
19 changed files with 421 additions and 47 deletions

View file

@ -52,6 +52,7 @@ use App\Services\Attachments\AttachmentManager;
use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\EntityExporter;
use App\Services\EntityImporter;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\StructuralElementRecursionHelper;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
@ -77,9 +78,11 @@ abstract class BaseAdminController extends AbstractController
protected $translator;
protected $attachmentHelper;
protected $attachmentSubmitHandler;
protected $commentHelper;
public function __construct(TranslatorInterface $translator, UserPasswordEncoderInterface $passwordEncoder,
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler)
AttachmentManager $attachmentHelper, AttachmentSubmitHandler $attachmentSubmitHandler,
EventCommentHelper $commentHelper)
{
if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) {
throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!');
@ -93,6 +96,7 @@ abstract class BaseAdminController extends AbstractController
$this->passwordEncoder = $passwordEncoder;
$this->attachmentHelper = $attachmentHelper;
$this->attachmentSubmitHandler = $attachmentSubmitHandler;
$this->commentHelper = $commentHelper;
}
@ -131,6 +135,8 @@ abstract class BaseAdminController extends AbstractController
}
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($entity);
$em->flush();
$this->addFlash('success', 'entity.edit_flash');
@ -188,6 +194,8 @@ abstract class BaseAdminController extends AbstractController
}
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($new_entity);
$em->flush();
$this->addFlash('success', 'entity.created_flash');
@ -215,6 +223,10 @@ abstract class BaseAdminController extends AbstractController
'csv_separator' => $data['csv_separator'],
];
$this->commentHelper->setMessage('Import ' . $file->getClientOriginalName());
$errors = $importer->fileToDBEntities($file, $this->entity_class, $options);
foreach ($errors as $name => $error) {
@ -280,6 +292,8 @@ abstract class BaseAdminController extends AbstractController
$entityManager->remove($entity);
}
$this->commentHelper->setMessage($request->request->get('log_comment', null));
//Flush changes
$entityManager->flush();

View file

@ -50,6 +50,7 @@ use App\Form\Part\PartBaseType;
use App\Services\Attachments\AttachmentManager;
use App\Services\Attachments\AttachmentSubmitHandler;
use App\Services\Attachments\PartPreviewGenerator;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\LogSystem\HistoryHelper;
use App\Services\LogSystem\TimeTravel;
use App\Services\PricedetailHelper;
@ -71,12 +72,15 @@ class PartController extends AbstractController
protected $attachmentManager;
protected $pricedetailHelper;
protected $partPreviewGenerator;
protected $commentHelper;
public function __construct(AttachmentManager $attachmentManager, PricedetailHelper $pricedetailHelper, PartPreviewGenerator $partPreviewGenerator)
public function __construct(AttachmentManager $attachmentManager, PricedetailHelper $pricedetailHelper,
PartPreviewGenerator $partPreviewGenerator, EventCommentHelper $commentHelper)
{
$this->attachmentManager = $attachmentManager;
$this->pricedetailHelper = $pricedetailHelper;
$this->partPreviewGenerator = $partPreviewGenerator;
$this->commentHelper = $commentHelper;
}
/**
@ -166,6 +170,8 @@ class PartController extends AbstractController
}
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($part);
$em->flush();
$this->addFlash('info', 'part.edited_flash');
@ -197,6 +203,8 @@ class PartController extends AbstractController
if ($this->isCsrfTokenValid('delete'.$part->getId(), $request->request->get('_token'))) {
$entityManager = $this->getDoctrine()->getManager();
$this->commentHelper->setMessage($request->request->get('log_comment', null));
//Remove part
$entityManager->remove($part);
@ -257,6 +265,8 @@ class PartController extends AbstractController
}
}
$this->commentHelper->setMessage($form['log_comment']->getData());
$em->persist($new_part);
$em->flush();
$this->addFlash('success', 'part.created_flash');

View file

@ -0,0 +1,45 @@
<?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;
interface LogWithCommentInterface
{
/**
* Checks if this log entry has a user provided comment.
* @return bool
*/
public function hasComment(): bool;
/**
* Gets the user provided comment associated with this log entry.
* Returns null if not comment was set.
* @return string|null
*/
public function getComment(): ?string;
/**
* Sets the user provided comment associated with this log entry.
* @param string|null $new_comment
* @return $this
*/
public function setComment(?string $new_comment): self;
}

View file

@ -43,12 +43,13 @@ declare(strict_types=1);
namespace App\Entity\LogSystem;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\LogWithCommentInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class ElementCreatedLogEntry extends AbstractLogEntry
class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentInterface
{
protected $typeString = 'element_created';
@ -78,4 +79,29 @@ class ElementCreatedLogEntry extends AbstractLogEntry
{
return null !== $this->getCreationInstockValue();
}
/**
* @inheritDoc
*/
public function hasComment(): bool
{
return isset($this->extra['m']);
}
/**
* @inheritDoc
*/
public function getComment(): ?string
{
return $this->extra['m'] ?? null;
}
/**
* @inheritDoc
*/
public function setComment(?string $new_comment): LogWithCommentInterface
{
$this->extra['m'] = $new_comment;
return $this;
}
}

View file

@ -43,6 +43,7 @@ declare(strict_types=1);
namespace App\Entity\LogSystem;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\LogWithCommentInterface;
use App\Entity\Contracts\NamedElementInterface;
use App\Entity\Contracts\TimeTravelInterface;
use Doctrine\ORM\Mapping as ORM;
@ -50,7 +51,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface
class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface
{
protected $typeString = 'element_deleted';
@ -111,4 +112,29 @@ class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInter
{
return $this->extra['d'] ?? [];
}
/**
* @inheritDoc
*/
public function hasComment(): bool
{
return isset($this->extra['m']);
}
/**
* @inheritDoc
*/
public function getComment(): ?string
{
return $this->extra['m'] ?? null;
}
/**
* @inheritDoc
*/
public function setComment(?string $new_comment): LogWithCommentInterface
{
$this->extra['m'] = $new_comment;
return $this;
}
}

View file

@ -43,13 +43,14 @@ declare(strict_types=1);
namespace App\Entity\LogSystem;
use App\Entity\Base\AbstractDBElement;
use App\Entity\Contracts\LogWithCommentInterface;
use App\Entity\Contracts\TimeTravelInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity()
*/
class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface
class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface
{
protected $typeString = 'element_edited';
@ -72,16 +73,6 @@ class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterf
return $this;
}
/**
* Returns the message associated with this edit change.
*
* @return string
*/
public function getMessage(): string
{
return $this->extra['m'] ?? '';
}
/**
* @inheritDoc
*/
@ -97,4 +88,29 @@ class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterf
{
return $this->extra['d'] ?? [];
}
/**
* @inheritDoc
*/
public function hasComment(): bool
{
return isset($this->extra['m']);
}
/**
* @inheritDoc
*/
public function getComment(): ?string
{
return $this->extra['m'] ?? null;
}
/**
* @inheritDoc
*/
public function setComment(?string $new_comment): LogWithCommentInterface
{
$this->extra['m'] = $new_comment;
return $this;
}
}

View file

@ -25,6 +25,7 @@ use App\Entity\LogSystem\AbstractLogEntry;
use App\Entity\LogSystem\ElementCreatedLogEntry;
use App\Entity\LogSystem\ElementDeletedLogEntry;
use App\Entity\LogSystem\ElementEditedLogEntry;
use App\Services\LogSystem\EventCommentHelper;
use App\Services\LogSystem\EventLogger;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
@ -38,11 +39,13 @@ class EventLoggerSubscriber implements EventSubscriber
{
protected $logger;
protected $serializer;
protected $eventCommentHelper;
public function __construct(EventLogger $logger, SerializerInterface $serializer)
public function __construct(EventLogger $logger, SerializerInterface $serializer, EventCommentHelper $commentHelper)
{
$this->logger = $logger;
$this->serializer = $serializer;
$this->eventCommentHelper = $commentHelper;
}
public function onFlush(OnFlushEventArgs $eventArgs)
@ -59,6 +62,10 @@ class EventLoggerSubscriber implements EventSubscriber
if ($this->validEntity($entity)) {
$log = new ElementEditedLogEntry($entity);
$this->saveChangeSet($entity, $log, $uow);
//Add user comment to log entry
if ($this->eventCommentHelper->isMessageSet()) {
$log->setComment($this->eventCommentHelper->getMessage());
}
$this->logger->log($log);
}
}
@ -66,6 +73,10 @@ class EventLoggerSubscriber implements EventSubscriber
foreach ($uow->getScheduledEntityDeletions() as $entity) {
if ($this->validEntity($entity)) {
$log = new ElementDeletedLogEntry($entity);
//Add user comment to log entry
if ($this->eventCommentHelper->isMessageSet()) {
$log->setComment($this->eventCommentHelper->getMessage());
}
$this->saveChangeSet($entity, $log, $uow);
$this->logger->log($log);
}
@ -82,7 +93,6 @@ class EventLoggerSubscriber implements EventSubscriber
}
$changeSet = $uow->getEntityChangeSet($entity);
dump($changeSet);
$old_data = array_diff(array_combine(array_keys($changeSet), array_column($changeSet, 0)), [null]);
$logEntry->setOldData($old_data);
}
@ -95,6 +105,10 @@ class EventLoggerSubscriber implements EventSubscriber
$entity = $args->getObject();
if ($this->validEntity($entity)) {
$log = new ElementCreatedLogEntry($entity);
//Add user comment to log entry
if ($this->eventCommentHelper->isMessageSet()) {
$log->setComment($this->eventCommentHelper->getMessage());
}
$this->logger->log($log);
}
}
@ -107,6 +121,9 @@ class EventLoggerSubscriber implements EventSubscriber
if ($uow->hasPendingInsertions()) {
$em->flush();
}
//Clear the message provided by user.
$this->eventCommentHelper->clearMessage();
}
/**

View file

@ -144,6 +144,13 @@ class BaseEntityAdminForm extends AbstractType
'entity' => $entity,
]);
$builder->add('log_comment', TextType::class, [
'label' => 'edit.log_comment',
'mapped' => false,
'required' => false,
'empty_data' => null,
]);
//Buttons
$builder->add('save', SubmitType::class, [
'label' => $is_new ? 'entity.create' : 'entity.edit.save',

View file

@ -264,6 +264,13 @@ class PartBaseType extends AbstractType
],
]);
$builder->add('log_comment', TextType::class, [
'label' => 'edit.log_comment',
'mapped' => false,
'required' => false,
'empty_data' => null,
]);
$builder
//Buttons
->add('save', SubmitType::class, ['label' => 'part.edit.save'])

View file

@ -90,7 +90,7 @@ class LogEntryRepository extends EntityRepository
->where('log INSTANCE OF ' . ElementEditedLogEntry::class)
->andWhere('log.target_type = :target_type')
->andWhere('log.target_id = :target_id')
->andWhere('log.timestamp > :until')
->andWhere('log.timestamp >= :until')
->orderBy('log.timestamp', 'DESC');
$qb->setParameters([

View file

@ -151,7 +151,8 @@ class EntityExporter
// Create the disposition of the file
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$filename
$filename,
$string = preg_replace('![^'.preg_quote('-').'a-z0-_9\s]+!', '', strtolower($filename))
);
// Set the content disposition
$response->headers->set('Content-Disposition', $disposition);

View file

@ -0,0 +1,72 @@
<?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;
class EventCommentHelper
{
protected const MAX_MESSAGE_LENGTH = 255;
protected $message;
public function __construct()
{
$message = null;
}
/**
* Set the message that will be saved for all ElementEdited/Created/Deleted messages during the next flush.
* Set to null if no message should be shown.
* After the flush this message is cleared.
* @param string|null $message
*/
public function setMessage(?string $message): void
{
//Restrict the length of the string
$this->message = mb_strimwidth($message, 0, self::MAX_MESSAGE_LENGTH, '...');
}
/**
* Returns the currently set message, or null if no message is set yet.
* @return string|null
*/
public function getMessage(): ?string
{
return $this->message;
}
/**
* Clear the currently set message.
*/
public function clearMessage(): void
{
$this->message = null;
}
/**
* Check if a message is currently set.
* @return bool
*/
public function isMessageSet(): bool
{
return is_string($this->message);
}
}

View file

@ -116,24 +116,38 @@ class LogEntryExtraFormatter
);
}
if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) {
return sprintf(
'<i>%s</i>: %s',
$this->translator->trans('log.element_created.original_instock'),
$context->getCreationInstockValue()
);
if ($context instanceof ElementCreatedLogEntry ) {
$comment = '';
if ($context->hasComment()) {
$comment = htmlspecialchars($context->getComment()) . '; ';
}
if($context->hasCreationInstockValue()) {
return $comment . sprintf(
'<i>%s</i>: %s',
$this->translator->trans('log.element_created.original_instock'),
$context->getCreationInstockValue()
);
}
return $comment;
}
if ($context instanceof ElementDeletedLogEntry) {
return sprintf(
'<i>%s</i>: %s',
$this->translator->trans('log.element_deleted.old_name'),
$context->getOldName() ?? $this->translator->trans('log.element_deleted.old_name.unknown')
);
$comment = '';
if ($context->hasComment()) {
$comment = htmlspecialchars($context->getComment()) . '; ';
}
return $comment . sprintf(
'<i>%s</i>: %s',
$this->translator->trans('log.element_deleted.old_name'),
$context->getOldName() ?? $this->translator->trans('log.element_deleted.old_name.unknown')
);
}
if ($context instanceof ElementEditedLogEntry && ! empty($context->getMessage())) {
return htmlspecialchars($context->getMessage());
if ($context instanceof ElementEditedLogEntry) {
if ($context->hasComment()) {
return htmlspecialchars($context->getComment());
}
}
if ($context instanceof InstockChangedLogEntry) {