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 @@ -<?xml version="1.0" encoding="UTF-8"?> <plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://developer.shopware.com/schemas/plugin.xsd"> <name>NotifyIfAvail</name> @@ -8,4 +7,19 @@ <copyright>TTT-Games</copyright> <license>MIT</license> <compatibility minVersion="6.5.0" /> + + <administration> + <menu> + <item> + <name>NotifyIfAvail</name> + <label>Benachrichtigungs-Einstellungen</label> + <parent>sw-settings-index</parent> + <position>50</position> + <target>sw.mail.template.detail</target> + <params> + <param name="id">c6d2c6141e9f44c4a3eb110d2c58c823</param> + </params> + </item> + </menu> + </administration> </plugin> 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 @@ <?php -namespace NotifyIfAvail\Entity; +namespace NotifyIfAvail\Repository; -use Doctrine\DBAL\Connection; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\Uuid\Uuid; +use Doctrine\DBAL\Connection; +use Psr\Log\LoggerInterface; class NotificationRepository { + private EntityRepository $repository; private Connection $connection; + private LoggerInterface $logger; - public function __construct(Connection $connection) + public function __construct(EntityRepository $repository, Connection $connection, LoggerInterface $logger) { + $this->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 @@ +<?php + +namespace NotifyIfAvail; + +use Shopware\Core\Framework\Plugin; +use Shopware\Core\Framework\Plugin\Context\ActivateContext; +use Shopware\Core\Framework\Plugin\Context\DeactivateContext; +use Shopware\Core\Framework\Plugin\Context\InstallContext; +use Shopware\Core\Framework\Plugin\Context\UninstallContext; +use Doctrine\DBAL\Connection; +use Psr\Log\LoggerInterface; +use Shopware\Core\Content\MailTemplate\Service\MailTemplateService; +use Shopware\Core\Framework\Context; +use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository; +use Shopware\Core\System\Snippet\SnippetService; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; +use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; + +class NotifyIfAvail extends Plugin +{ + public function install(InstallContext $context): void + { + parent::install($context); + + $this->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 0000000..5769d8e Binary files /dev/null and b/src/Resources/config/plugin.png differ 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 @@ <services> <service id="NotifyIfAvail\Storefront\Subscriber\ProductSubscriber"> + <argument type="service" id="product.repository"/> <tag name="kernel.event_subscriber"/> </service> + <service id="NotifyIfAvail\Controller\NotificationController"> - <argument type="service" id="doctrine.dbal.default_connection"/> + <argument type="service" id="Doctrine\DBAL\Connection"/> <argument type="service" id="mailer.mailer"/> + <argument type="service" id="logger"/> <!-- Logger hinzugefügt --> <tag name="controller.service_arguments"/> </service> <service id="NotifyIfAvail\Service\NotificationService"> - <argument type="service" id="doctrine.dbal.default_connection"/> + <argument type="service" id="Doctrine\DBAL\Connection"/> <argument type="service" id="mailer.mailer"/> + <argument type="service" id="logger"/> <!-- Logger hinzugefügt --> </service> </services> </container> 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 %} <div id="notification-container"> <button class="btn btn-primary" id="notify-me-button"> {{ "NotifyIfAvail.notify_me"|trans }} @@ -12,23 +12,5 @@ </button> </div> </div> - - <script> - document.getElementById('notify-me-button').addEventListener('click', function() { - document.getElementById('notify-me-form').style.display = 'block'; - }); - - document.getElementById('submit-notify').addEventListener('click', function() { - let email = document.getElementById('notify-me-email').value; - let productId = '{{ product.id }}'; - - 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); - }); - }); - </script> {% 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(" - <p>Hallo,</p> - <p>Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:</p> - <p><strong>{$productName}</strong></p> - <p><a href='{$productUrl}'>Jetzt bestellen</a></p> - <p>Vielen Dank für Ihr Interesse an unseren Produkten!</p> - <p>Beste Grüße,<br>{$shopName}-Team</p> - "); + $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(" + <p>Hallo,</p> + <p>Sie haben sich für eine Benachrichtigung angemeldet, sobald der folgende Artikel wieder verfügbar ist:</p> + <p><strong>{$productName}</strong></p> + <p><a href='{$productUrl}'>Jetzt bestellen</a></p> + <p>Vielen Dank für Ihr Interesse an unseren Produkten!</p> + <p>Beste Grüße,<br>{$shopName}-Team</p> + "); + + $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 + ])); } } }