From 7510dc362f5222fa588e349e5e1a21247cc78ba1 Mon Sep 17 00:00:00 2001 From: SLINIcraftet204 Date: Wed, 5 Mar 2025 16:41:57 +0100 Subject: [PATCH] updated some things and made plugin work with snippets and mail-template //TODO configure menu and button on productpage --- composer.json | 2 +- plugin.xml | 16 +- src/Controller/NotificationController.php | 32 ++-- src/Entity/NotificationRepository.php | 56 +++++-- src/NotifyIfAvail.php | 143 ++++++++++++++++++ src/Resources/app/storefront/src/main.js | 4 + .../storefront/src/plugin/notify-me.plugin.js | 35 +++++ src/Resources/config/plugin.png | Bin 0 -> 10565 bytes src/Resources/config/services.xml | 8 +- .../notification_email.html.twig | 18 ++- .../page/product-detail/buy-widget.html.twig | 22 +-- src/Service/NotificationService.php | 80 +++++++--- .../Subscriber/ProductSubscriber.php | 6 +- 13 files changed, 332 insertions(+), 90 deletions(-) create mode 100644 src/NotifyIfAvail.php create mode 100644 src/Resources/app/storefront/src/main.js create mode 100644 src/Resources/app/storefront/src/plugin/notify-me.plugin.js create mode 100644 src/Resources/config/plugin.png diff --git a/composer.json b/composer.json index 1d265cf..0342935 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "slinicraftet204/notifyifavail", "description": "benachrichtigt Kunden, sobald ein Produkt wieder verfügbar ist", - "version": "0.1.0", + "version": "0.1.41", "type": "shopware-platform-plugin", "license": "MIT", "authors": [ diff --git a/plugin.xml b/plugin.xml index 9c9aac5..9334c23 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,4 +1,3 @@ - NotifyIfAvail @@ -8,4 +7,19 @@ TTT-Games MIT + + + + + NotifyIfAvail + + sw-settings-index + 50 + sw.mail.template.detail + + c6d2c6141e9f44c4a3eb110d2c58c823 + + + + diff --git a/src/Controller/NotificationController.php b/src/Controller/NotificationController.php index dfb62ff..8e52be9 100644 --- a/src/Controller/NotificationController.php +++ b/src/Controller/NotificationController.php @@ -2,24 +2,21 @@ namespace NotifyIfAvail\Controller; -use Shopware\Core\Framework\Routing\Annotation\RouteScope; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\JsonResponse; -use Doctrine\DBAL\Connection; -use Shopware\Core\Framework\Uuid\Uuid; -use Symfony\Component\Mailer\MailerInterface; -use Symfony\Component\Mime\Email; +use NotifyIfAvail\Service\NotificationService; +use Psr\Log\LoggerInterface; class NotificationController { - private Connection $connection; - private MailerInterface $mailer; + private NotificationService $notificationService; + private LoggerInterface $logger; - public function __construct(Connection $connection, MailerInterface $mailer) + public function __construct(NotificationService $notificationService, LoggerInterface $logger) { - $this->connection = $connection; - $this->mailer = $mailer; + $this->notificationService = $notificationService; + $this->logger = $logger; } /** @@ -34,13 +31,12 @@ class NotificationController return new JsonResponse(['message' => 'Invalid data'], 400); } - $this->connection->insert('notifyifavail_plugin_notification', [ - 'id' => Uuid::randomBytes(), - 'product_id' => Uuid::fromHexToBytes($productId), - 'email' => $email, - 'created_at' => (new \DateTime())->format('Y-m-d H:i:s') - ]); - - return new JsonResponse(['message' => 'Successfully subscribed']); + try { + $this->notificationService->saveNotification($email, $productId); + return new JsonResponse(['message' => 'Successfully subscribed']); + } catch (\Exception $e) { + $this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage()); + return new JsonResponse(['message' => 'An error occurred'], 500); + } } } diff --git a/src/Entity/NotificationRepository.php b/src/Entity/NotificationRepository.php index 733892b..1533176 100644 --- a/src/Entity/NotificationRepository.php +++ b/src/Entity/NotificationRepository.php @@ -1,43 +1,67 @@ repository = $repository; $this->connection = $connection; + $this->logger = $logger; } public function saveNotification(string $email, string $productId): void { - $this->connection->insert('notifyifavail_plugin_notification', [ - 'id' => Uuid::randomBytes(), - 'email' => $email, - 'product_id' => Uuid::fromHexToBytes($productId), - 'created_at' => (new \DateTime())->format('Y-m-d H:i:s'), - ]); + try { + $notification = [ + 'id' => Uuid::randomHex(), + 'email' => $email, + 'productId' => $productId, + 'createdAt' => (new \DateTime())->format(DATE_ATOM), + ]; + + $this->repository->create([$notification], \Shopware\Core\Framework\Context::createDefaultContext()); + } catch (\Exception $e) { + $this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage()); + } } public function deleteNotification(string $productId): void { - $this->connection->delete('notifyifavail_plugin_notification', [ - 'product_id' => Uuid::fromHexToBytes($productId), - ]); + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('productId', $productId)); + + $result = $this->repository->search($criteria, \Shopware\Core\Framework\Context::createDefaultContext()); + + $idsToDelete = []; + foreach ($result->getEntities() as $entity) { + $idsToDelete[] = ['id' => $entity->getId()]; + } + + if (!empty($idsToDelete)) { + $this->repository->delete($idsToDelete, \Shopware\Core\Framework\Context::createDefaultContext()); + } } public function getNotificationsForProduct(string $productId): array { - return $this->connection->fetchFirstColumn( - "SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId", - ['productId' => Uuid::fromHexToBytes($productId)] - ); + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('productId', $productId)); + + $notifications = $this->repository->search($criteria, \Shopware\Core\Framework\Context::createDefaultContext()); + + return array_map(fn ($entity) => $entity->getEmail(), $notifications->getEntities()->getElements()); } } diff --git a/src/NotifyIfAvail.php b/src/NotifyIfAvail.php new file mode 100644 index 0000000..a68e512 --- /dev/null +++ b/src/NotifyIfAvail.php @@ -0,0 +1,143 @@ +registerMailTemplateType($context->getContext()); + $this->registerEmailTemplate($context->getContext()); + $this->registerSnippets($context->getContext()); + } + + private function registerEmailTemplate(Context $context): void + { + $mailTemplateRepository = $this->container->get('mail_template.repository'); + + $mailTemplateRepository->upsert([ + [ + 'id' => 'c6d2c6141e9f44c4a3eb110d2c58c823', + 'mailTemplateTypeId' => '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7', // ID des Mail-Template-Typs + 'systemDefault' => false, + 'description' => 'Benachrichtigung, wenn Produkt wieder verfügbar ist', + 'contentHtml' => file_get_contents(__DIR__ . '/Resources/email-templates/notification_email.html.twig'), + 'contentPlain' => strip_tags(file_get_contents(__DIR__ . '/Resources/email-templates/notification_email.html.twig')), + 'subject' => 'Ihr gewünschter Artikel ist wieder verfügbar!' + ] + ], $context); + } + + + private function registerMailTemplateType(Context $context): void + { + $mailTemplateTypeRepository = $this->container->get('mail_template_type.repository'); + + $mailTemplateTypeRepository->upsert([ + [ + 'id' => '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7', + 'name' => 'Notify If Available Email', + 'technicalName' => 'notify_if_avail_email', + 'availableEntities' => ['product' => 'product'], + ] + ], $context); + } + + + private function getMailTemplateTypeId(Context $context): ?string + { + $mailTemplateTypeRepository = $this->container->get('mail_template_type.repository'); + $criteria = new Criteria(); + $criteria->addFilter(new EqualsFilter('technicalName', 'notify_if_avail_email')); + + $mailTemplateType = $mailTemplateTypeRepository->search($criteria, $context)->first(); + + return $mailTemplateType ? $mailTemplateType->getId() : '9f5b31d14c9d4b6a8d2e8f01b5b2a3d7'; + } + + + private function registerSnippets(Context $context): void + { + $snippetRepository = $this->container->get('snippet.repository'); + + $snippetRepository->upsert([ + [ + 'id' => '1f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4b7fae35', // Standard-Set-ID für Shopware-Snippets + 'translationKey' => 'NotifyIfAvail.notify_me', + 'value' => 'Benachrichtigen, wenn verfügbar', + 'author' => 'NotifyIfAvail Plugin', + ], + [ + 'id' => '2f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4b7fae35', + 'translationKey' => 'NotifyIfAvail.email_placeholder', + 'value' => 'Geben Sie Ihre E-Mail-Adresse ein', + 'author' => 'NotifyIfAvail Plugin', + ], + [ + 'id' => '3f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4b7fae35', + 'translationKey' => 'NotifyIfAvail.success_message', + 'value' => 'Sie werden benachrichtigt, sobald der Artikel verfügbar ist.', + 'author' => 'NotifyIfAvail Plugin', + ], + [ + 'id' => '4f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4c20138e', // Englische ID + 'translationKey' => 'NotifyIfAvail.notify_me', + 'value' => 'Notify me when available', + 'author' => 'NotifyIfAvail Plugin', + ], + [ + 'id' => '5f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4c20138e', + 'translationKey' => 'NotifyIfAvail.email_placeholder', + 'value' => 'Enter your email address', + 'author' => 'NotifyIfAvail Plugin', + ], + [ + 'id' => '6f5b31d14c9d4b6a8d2e8f01b5b2a3d8', + 'setId' => '0194da4af1f7720a8183c85d4c20138e', + 'translationKey' => 'NotifyIfAvail.success_message', + 'value' => 'You will be notified when the item is available.', + 'author' => 'NotifyIfAvail Plugin', + ] + ], $context); + } + + public function uninstall(UninstallContext $context): void + { + if (!$context->keepUserData()) { + $connection = $this->container->get(Connection::class); + $connection->executeStatement('DROP TABLE IF EXISTS notifyifavail_plugin_notification'); + } + parent::uninstall($context); + } + + public function activate(ActivateContext $context): void + { + parent::activate($context); + } + + public function deactivate(DeactivateContext $context): void + { + parent::deactivate($context); + } +} diff --git a/src/Resources/app/storefront/src/main.js b/src/Resources/app/storefront/src/main.js new file mode 100644 index 0000000..534c774 --- /dev/null +++ b/src/Resources/app/storefront/src/main.js @@ -0,0 +1,4 @@ +import PluginManager from 'src/plugin-system/plugin.manager'; +import NotifyMe from './plugin/notify-me.plugin'; + +PluginManager.register('NotifyMe', NotifyMe, '[data-notify-me]'); diff --git a/src/Resources/app/storefront/src/plugin/notify-me.plugin.js b/src/Resources/app/storefront/src/plugin/notify-me.plugin.js new file mode 100644 index 0000000..0a0b58b --- /dev/null +++ b/src/Resources/app/storefront/src/plugin/notify-me.plugin.js @@ -0,0 +1,35 @@ +import Plugin from 'src/plugin-system/plugin.class'; + +export default class NotifyMe extends Plugin { + init() { + this.notifyButton = this.el; + this.notifyForm = this.el.nextElementSibling; + this.emailInput = this.notifyForm.querySelector('#notify-me-email'); + this.submitButton = this.notifyForm.querySelector('#submit-notify'); + + this.registerEvents(); + } + + registerEvents() { + this.notifyButton.addEventListener('click', () => this.showForm()); + this.submitButton.addEventListener('click', () => this.submitForm()); + } + + showForm() { + this.notifyForm.style.display = 'block'; + } + + submitForm() { + const email = this.emailInput.value; + const productId = this.notifyButton.dataset.productId; + + fetch('/notification/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: `email=${encodeURIComponent(email)}&productId=${encodeURIComponent(productId)}` + }) + .then(response => response.json()) + .then(data => alert(data.message)) + .catch(error => console.error('Error:', error)); + } +} diff --git a/src/Resources/config/plugin.png b/src/Resources/config/plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..5769d8ebfc7543433cf24a23fb63a8e0ae7a55a0 GIT binary patch literal 10565 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 NIvR%R<*K%!{{w+v#<&0g literal 0 HcmV?d00001 diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index 0a5bc6f..ba69b30 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -6,18 +6,22 @@ + + - + + - + + diff --git a/src/Resources/email-templates/notification_email.html.twig b/src/Resources/email-templates/notification_email.html.twig index b439a93..50e706a 100644 --- a/src/Resources/email-templates/notification_email.html.twig +++ b/src/Resources/email-templates/notification_email.html.twig @@ -1,13 +1,15 @@ -{SALUTATION} {CUSTOMER_NAME}, +{% block content %} + {{ salutation }} {{ customerName }}, -Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist: + Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist: -**{PRODUCT_NAME}** -Jetzt wieder auf Lager! Sie können den Artikel hier aufrufen und bestellen: + **{{ productName }}** + Jetzt wieder auf Lager! Sie können den Artikel hier aufrufen und bestellen: -[Zum Artikel]({PRODUCT_URL}) + [Zum Artikel]({{ productUrl }}) -Vielen Dank für Ihr Interesse an unseren Produkten! + Vielen Dank für Ihr Interesse an unseren Produkten! -Beste Grüße, -Ihr {SHOP_NAME}-Team + Beste Grüße, + Ihr {{ shopName }}-Team +{% endblock %} diff --git a/src/Resources/views/storefront/page/product-detail/buy-widget.html.twig b/src/Resources/views/storefront/page/product-detail/buy-widget.html.twig index 427e48a..ef3d6c6 100644 --- a/src/Resources/views/storefront/page/product-detail/buy-widget.html.twig +++ b/src/Resources/views/storefront/page/product-detail/buy-widget.html.twig @@ -1,4 +1,4 @@ -{% if product.extensions.notification is defined %} +{% if product.extensions.notifyIfAvail is defined and product.extensions.notifyIfAvail.enabled %}
- - {% endif %} + diff --git a/src/Service/NotificationService.php b/src/Service/NotificationService.php index 2ddc83c..eba9e95 100644 --- a/src/Service/NotificationService.php +++ b/src/Service/NotificationService.php @@ -6,43 +6,77 @@ use Doctrine\DBAL\Connection; use Symfony\Component\Mailer\MailerInterface; use Symfony\Component\Mime\Email; use Shopware\Core\Framework\Uuid\Uuid; +use Psr\Log\LoggerInterface; +use Shopware\Core\System\SystemConfig\SystemConfigService; class NotificationService { private Connection $connection; private MailerInterface $mailer; + private LoggerInterface $logger; + private SystemConfigService $configService; - public function __construct(Connection $connection, MailerInterface $mailer) - { + public function __construct( + Connection $connection, + MailerInterface $mailer, + LoggerInterface $logger, + SystemConfigService $configService + ) { $this->connection = $connection; $this->mailer = $mailer; + $this->logger = $logger; + $this->configService = $configService; + } + + public function saveNotification(string $email, string $productId): void + { + try { + $this->connection->insert('notifyifavail_plugin_notification', [ + 'id' => Uuid::randomBytes(), + 'email' => $email, + 'product_id' => Uuid::fromHexToBytes($productId), + 'created_at' => (new \DateTime())->format('Y-m-d H:i:s'), + ]); + } catch (\Exception $e) { + $this->logger->error('Fehler beim Speichern der Benachrichtigung: ' . $e->getMessage()); + throw $e; + } } public function notifyCustomers(string $productId, string $productName, string $productUrl, string $shopName): void { - $sql = "SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId"; - $emails = $this->connection->fetchFirstColumn($sql, ['productId' => Uuid::fromHexToBytes($productId)]); + try { + $emails = $this->connection->fetchFirstColumn( + "SELECT email FROM notifyifavail_plugin_notification WHERE product_id = :productId", + ['productId' => Uuid::fromHexToBytes($productId)] + ); - foreach ($emails as $email) { - $message = (new Email()) - ->from('shop@example.com') - ->to($email) - ->subject('Ihr gewünschter Artikel ist wieder verfügbar!') - ->html(" -

Hallo,

-

Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:

-

{$productName}

-

Jetzt bestellen

-

Vielen Dank für Ihr Interesse an unseren Produkten!

-

Beste Grüße,
{$shopName}-Team

- "); + $emailSender = $this->configService->get('NotifyIfAvail.config.emailSender') ?? 'shop@example.com'; + $emailSubject = $this->configService->get('NotifyIfAvail.config.emailSubject') ?? 'Ihr gewünschter Artikel ist wieder verfügbar!'; - $this->mailer->send($message); + foreach ($emails as $email) { + $message = (new Email()) + ->from($emailSender) + ->to($email) + ->subject($emailSubject) + ->html(" +

Hallo,

+

Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:

+

{$productName}

+

Jetzt bestellen

+

Vielen Dank für Ihr Interesse an unseren Produkten!

+

Beste Grüße,
{$shopName}-Team

+ "); + + $this->mailer->send($message); + } + + $this->connection->executeStatement( + "DELETE FROM notifyifavail_plugin_notification WHERE product_id = :productId", + ['productId' => Uuid::fromHexToBytes($productId)] + ); + } catch (\Exception $e) { + $this->logger->error('Fehler beim Versand der Benachrichtigungen: ' . $e->getMessage()); } - - // Nach Versand aus der Datenbank löschen - $this->connection->executeStatement("DELETE FROM notifyifavail_plugin_notification WHERE product_id = :productId", [ - 'productId' => Uuid::fromHexToBytes($productId) - ]); } } diff --git a/src/Storefront/Subscriber/ProductSubscriber.php b/src/Storefront/Subscriber/ProductSubscriber.php index 55aa6cf..d9b73cb 100644 --- a/src/Storefront/Subscriber/ProductSubscriber.php +++ b/src/Storefront/Subscriber/ProductSubscriber.php @@ -4,6 +4,8 @@ namespace NotifyIfAvail\Storefront\Subscriber; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Shopware\Storefront\Page\Product\ProductPageLoadedEvent; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\Framework\Struct\ArrayStruct; class ProductSubscriber implements EventSubscriberInterface { @@ -19,7 +21,9 @@ class ProductSubscriber implements EventSubscriberInterface $product = $event->getPage()->getProduct(); if ($product->getAvailableStock() <= 0) { - $product->addExtension('notification', true); + $product->addExtension('notifyIfAvail', new ArrayStruct([ + 'enabled' => true + ])); } } }