diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index a4c41b16..5590e44b 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -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(); diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index 192cb497..1265bf32 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -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'); diff --git a/src/Entity/Contracts/LogWithCommentInterface.php b/src/Entity/Contracts/LogWithCommentInterface.php new file mode 100644 index 00000000..3a423166 --- /dev/null +++ b/src/Entity/Contracts/LogWithCommentInterface.php @@ -0,0 +1,45 @@ +. + */ + +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; +} \ No newline at end of file diff --git a/src/Entity/LogSystem/ElementCreatedLogEntry.php b/src/Entity/LogSystem/ElementCreatedLogEntry.php index f03f2187..b8cac342 100644 --- a/src/Entity/LogSystem/ElementCreatedLogEntry.php +++ b/src/Entity/LogSystem/ElementCreatedLogEntry.php @@ -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; + } } diff --git a/src/Entity/LogSystem/ElementDeletedLogEntry.php b/src/Entity/LogSystem/ElementDeletedLogEntry.php index e53c31a3..93912784 100644 --- a/src/Entity/LogSystem/ElementDeletedLogEntry.php +++ b/src/Entity/LogSystem/ElementDeletedLogEntry.php @@ -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; + } } diff --git a/src/Entity/LogSystem/ElementEditedLogEntry.php b/src/Entity/LogSystem/ElementEditedLogEntry.php index c9371142..0cc400f1 100644 --- a/src/Entity/LogSystem/ElementEditedLogEntry.php +++ b/src/Entity/LogSystem/ElementEditedLogEntry.php @@ -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; + } } diff --git a/src/EventSubscriber/EventLoggerSubscriber.php b/src/EventSubscriber/EventLoggerSubscriber.php index b0f0db0b..bc60f36e 100644 --- a/src/EventSubscriber/EventLoggerSubscriber.php +++ b/src/EventSubscriber/EventLoggerSubscriber.php @@ -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(); } /** diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index 6930b02e..da9dfcfe 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -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', diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 0390bbad..cf30f988 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -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']) diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 35d531d4..4c31e1b8 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -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([ diff --git a/src/Services/EntityExporter.php b/src/Services/EntityExporter.php index 5f06e2ca..0b9d0d20 100644 --- a/src/Services/EntityExporter.php +++ b/src/Services/EntityExporter.php @@ -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); diff --git a/src/Services/LogSystem/EventCommentHelper.php b/src/Services/LogSystem/EventCommentHelper.php new file mode 100644 index 00000000..6a7b25fc --- /dev/null +++ b/src/Services/LogSystem/EventCommentHelper.php @@ -0,0 +1,72 @@ +. + */ + +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); + } +} \ No newline at end of file diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index 434cadf0..941c57e0 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -116,24 +116,38 @@ class LogEntryExtraFormatter ); } - if ($context instanceof ElementCreatedLogEntry && $context->hasCreationInstockValue()) { - return sprintf( - '%s: %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( + '%s: %s', + $this->translator->trans('log.element_created.original_instock'), + $context->getCreationInstockValue() + ); + } + return $comment; } if ($context instanceof ElementDeletedLogEntry) { - return sprintf( - '%s: %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( + '%s: %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) { diff --git a/templates/AdminPages/EntityAdminBase.html.twig b/templates/AdminPages/EntityAdminBase.html.twig index 2c96c401..3d335c6d 100644 --- a/templates/AdminPages/EntityAdminBase.html.twig +++ b/templates/AdminPages/EntityAdminBase.html.twig @@ -1,5 +1,7 @@ {% extends "main_card.html.twig" %} +{% form_theme form.log_comment 'bootstrap_4_layout.html.twig' %} + {% block card_content %}
@@ -96,7 +98,21 @@
- {{ form_row(form.save) }} +
+
+
+ {{ form_widget(form.save) }} + + +
+ +
+
+ {{ form_row(form.reset) }} {{ form_end(form) }} diff --git a/templates/AdminPages/_delete_form.html.twig b/templates/AdminPages/_delete_form.html.twig index c20fdae5..b05d4fc0 100644 --- a/templates/AdminPages/_delete_form.html.twig +++ b/templates/AdminPages/_delete_form.html.twig @@ -7,12 +7,22 @@
{% set delete_disabled = (not is_granted("delete", entity)) or (entity.group is defined and entity.id == 1) %} - - {% if entity.parent is defined %} -
- - +
+ + +
+ {% if entity.parent is defined %} +
+ + +
{% endif %}
diff --git a/templates/Parts/edit/edit_part_info.html.twig b/templates/Parts/edit/edit_part_info.html.twig index ec8d1c4f..b57e7653 100644 --- a/templates/Parts/edit/edit_part_info.html.twig +++ b/templates/Parts/edit/edit_part_info.html.twig @@ -84,11 +84,23 @@
{{ form_widget(form.comment)}}
- - - - {{ form_row(form.save) }} + +
+
+
+ {{ form_widget(form.save) }} + + +
+ +
+
+ {{ form_row(form.reset) }} {{ form_errors(form) }} {{ form_end(form) }} diff --git a/templates/Parts/info/_tools.html.twig b/templates/Parts/info/_tools.html.twig index 48a84e04..eb2cbe72 100644 --- a/templates/Parts/info/_tools.html.twig +++ b/templates/Parts/info/_tools.html.twig @@ -31,9 +31,19 @@
- +
+ + + +
\ No newline at end of file diff --git a/tests/Services/LogSystem/EventCommentHelperTest.php b/tests/Services/LogSystem/EventCommentHelperTest.php new file mode 100644 index 00000000..8ce44503 --- /dev/null +++ b/tests/Services/LogSystem/EventCommentHelperTest.php @@ -0,0 +1,71 @@ +. + */ + +namespace App\Tests\Services\LogSystem; + +use App\Services\LogSystem\EventCommentHelper; +use App\Services\LogSystem\EventLogger; +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; + +class EventCommentHelperTest extends WebTestCase +{ + /** + * @var EventCommentHelper + */ + protected $service; + + protected function setUp(): void + { + parent::setUp(); // TODO: Change the autogenerated stub + + //Get an service instance. + self::bootKernel(); + $this->service = self::$container->get(EventCommentHelper::class); + } + + public function testInitialState() + { + $this->assertNull($this->service->getMessage()); + $this->assertFalse($this->service->isMessageSet()); + } + + public function testClearMessage() + { + $this->service->setMessage('Test'); + $this->assertTrue($this->service->isMessageSet()); + $this->service->clearMessage(); + $this->assertFalse($this->service->isMessageSet()); + } + + public function testGetSetMessage() + { + $this->service->setMessage('Test'); + $this->assertSame('Test', $this->service->getMessage()); + } + + public function testIsMessageSet() + { + $this->service->setMessage('Test'); + $this->assertTrue($this->service->isMessageSet()); + $this->service->clearMessage(); + $this->assertFalse($this->service->isMessageSet()); + } +} diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index 40bc848d..5222af15 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -6376,7 +6376,7 @@ Element 3 part.info.timetravel_hint - Please note that this feature is experimental, so the infos are maybe not correct.]]> + Please note that this feature is experimental, so the infos are maybe not correct.]]>