From 3a0019a1b9f8b15950505f2853850f7f4854b10d Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Thu, 8 Jan 2026 09:48:02 +0100 Subject: [PATCH] Fixes --- src/new/config/reference.php | 4 +- .../Shop/ContactBundle/Event/Contact/Lock.php | 5 +- .../ContactBundle/Event/Contact/UnLock.php | 5 +- .../Shop/QueueBundle/Event/Contact/Login.php | 5 +- .../Controller/Backend/EditController.php | 262 ++++++++ .../Form/Backend/CsvUploadType.php | 60 ++ .../translations/core_voucher_list.de.yaml | 3 +- .../translations/core_voucher_list.en.yaml | 3 +- .../translations/core_voucher_upload.de.yaml | 18 + .../translations/core_voucher_upload.en.yaml | 18 + .../views/backend/edit/create.html.twig | 2 +- .../views/backend/edit/edit.html.twig | 2 +- .../views/backend/edit/upload.html.twig | 54 ++ .../backend/edit/upload_confirm.html.twig | 102 ++++ .../views/backend/list/index.html.twig | 6 + .../views/frontend/designer/start.html.twig | 567 +++++++++++++++--- src/new/var/tailwind/backend.built.css | 37 +- 17 files changed, 1060 insertions(+), 93 deletions(-) create mode 100644 src/new/src/PSC/Shop/VoucherBundle/Form/Backend/CsvUploadType.php create mode 100644 src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.de.yaml create mode 100644 src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.en.yaml create mode 100644 src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload.html.twig create mode 100644 src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload_confirm.html.twig diff --git a/src/new/config/reference.php b/src/new/config/reference.php index 1875f986e..577739b43 100644 --- a/src/new/config/reference.php +++ b/src/new/config/reference.php @@ -474,7 +474,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * datetime?: array{ * default_format?: scalar|null, // Default: "Y-m-d\\TH:i:sP" * default_deserialization_formats?: list, - * default_timezone?: scalar|null, // Default: "UTC" + * default_timezone?: scalar|null, // Default: "Europe/Berlin" * cdata?: scalar|null, // Default: true * }, * array_collection?: array{ @@ -574,7 +574,7 @@ namespace Symfony\Component\DependencyInjection\Loader\Configurator; * datetime?: array{ * default_format?: scalar|null, // Default: "Y-m-d\\TH:i:sP" * default_deserialization_formats?: list, - * default_timezone?: scalar|null, // Default: "UTC" + * default_timezone?: scalar|null, // Default: "Europe/Berlin" * cdata?: scalar|null, // Default: true * }, * array_collection?: array{ diff --git a/src/new/src/PSC/Shop/ContactBundle/Event/Contact/Lock.php b/src/new/src/PSC/Shop/ContactBundle/Event/Contact/Lock.php index 34da82335..72996ffca 100755 --- a/src/new/src/PSC/Shop/ContactBundle/Event/Contact/Lock.php +++ b/src/new/src/PSC/Shop/ContactBundle/Event/Contact/Lock.php @@ -8,6 +8,7 @@ class Lock extends Event { /** @var string */ protected $contact; + public function getType() { return 'contact_lock'; @@ -15,13 +16,13 @@ class Lock extends Event public function getDescription() { - return 'Kunde gesperrt'; + return 'Kunde deaktiviert'; } public function getData() { return array( - 'contact' => $this->contact + 'contact' => $this->contact, ); } diff --git a/src/new/src/PSC/Shop/ContactBundle/Event/Contact/UnLock.php b/src/new/src/PSC/Shop/ContactBundle/Event/Contact/UnLock.php index 3b3defd5e..0bdfa6c95 100755 --- a/src/new/src/PSC/Shop/ContactBundle/Event/Contact/UnLock.php +++ b/src/new/src/PSC/Shop/ContactBundle/Event/Contact/UnLock.php @@ -8,6 +8,7 @@ class UnLock extends Event { /** @var string */ protected $contact; + public function getType() { return 'contact_unlock'; @@ -15,13 +16,13 @@ class UnLock extends Event public function getDescription() { - return 'Kunde freigeschaltet'; + return 'Kunde aktiviert'; } public function getData() { return array( - 'contact' => $this->contact + 'contact' => $this->contact, ); } diff --git a/src/new/src/PSC/Shop/QueueBundle/Event/Contact/Login.php b/src/new/src/PSC/Shop/QueueBundle/Event/Contact/Login.php index e214491b8..0d6571438 100644 --- a/src/new/src/PSC/Shop/QueueBundle/Event/Contact/Login.php +++ b/src/new/src/PSC/Shop/QueueBundle/Event/Contact/Login.php @@ -9,6 +9,7 @@ class Login extends Event { /** @var string */ protected $contact; + public function getType() { return 'contact_login'; @@ -16,13 +17,13 @@ class Login extends Event public function getDescription() { - return 'Kunde hat sich angemeldet'; + return 'Kunde hat sich eingelogged'; } public function getData() { return array( - 'contact' => $this->contact + 'contact' => $this->contact, ); } diff --git a/src/new/src/PSC/Shop/VoucherBundle/Controller/Backend/EditController.php b/src/new/src/PSC/Shop/VoucherBundle/Controller/Backend/EditController.php index 6483827a0..d6e4eb298 100755 --- a/src/new/src/PSC/Shop/VoucherBundle/Controller/Backend/EditController.php +++ b/src/new/src/PSC/Shop/VoucherBundle/Controller/Backend/EditController.php @@ -24,6 +24,7 @@ use PSC\Shop\EntityBundle\Entity\VoucherItem; use PSC\Shop\NewsBundle\Form\Backend\DeleteType; use PSC\Shop\PaymentBundle\Form\Backend\PaymentType; use PSC\Shop\VoucherBundle\Form\Backend\VoucherType; +use PSC\Shop\VoucherBundle\Form\Backend\CsvUploadType; use PSC\System\PluginBundle\Form\Chain\Field; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -254,4 +255,265 @@ class EditController extends AbstractController $response->headers->set('Content-Disposition', 'attachment; filename="export.csv"'); return $response; } + + /** + * Upload CSV voucher codes - Step 1: Upload and preview + * + * @param Request $request + * @param SessionInterface $session + * @param \PSC\System\SettingsBundle\Service\Shop $shopService + * @param EntityManagerInterface $entityManager + * @param $uid + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + */ + #[Route(path: '/edit/upload/{uid}', name: 'psc_shop_voucher_backend_upload')] + #[Template('@PSCShopVoucher/backend/edit/upload.html.twig')] + public function uploadAction( + Request $request, + SessionInterface $session, + \PSC\System\SettingsBundle\Service\Shop $shopService, + EntityManagerInterface $entityManager, + $uid + ) { + /** @var \PSC\Shop\EntityBundle\Entity\Shop $selectedShop */ + $selectedShop = $shopService->getSelectedShop(); + + /** @var Voucher $voucher */ + $voucher = $entityManager + ->getRepository('PSC\Shop\EntityBundle\Entity\Voucher') + ->findOneBy(['uid' => $uid, 'shop' => $selectedShop]); + + if (!$voucher) { + $session->getFlashBag()->add('error', 'Voucher not found'); + return $this->redirectToRoute('psc_shop_voucher_backend_list'); + } + + $form = $this->createForm(CsvUploadType::class); + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /** @var UploadedFile $file */ + $file = $form['file']->getData(); + + $result = $this->parseCsvFile($file, $voucher, $entityManager); + + if ($result['success']) { + // Store codes in session for preview + $session->set('voucher_upload_preview', [ + 'voucher_uid' => $voucher->getUid(), + 'codes' => $result['codes'], + 'duplicates' => $result['duplicates'] + ]); + + return $this->redirectToRoute('psc_shop_voucher_backend_upload_confirm', ['uid' => $uid]); + } else { + $session->getFlashBag()->add('error', $result['error']); + } + } + + return [ + 'voucher' => $voucher, + 'form' => $form->createView() + ]; + } + + /** + * Confirm and import voucher codes - Step 2: Preview and confirm + * + * @param Request $request + * @param SessionInterface $session + * @param \PSC\System\SettingsBundle\Service\Shop $shopService + * @param EntityManagerInterface $entityManager + * @param $uid + * @return array|\Symfony\Component\HttpFoundation\RedirectResponse + */ + #[Route(path: '/edit/upload-confirm/{uid}', name: 'psc_shop_voucher_backend_upload_confirm')] + #[Template('@PSCShopVoucher/backend/edit/upload_confirm.html.twig')] + public function uploadConfirmAction( + Request $request, + SessionInterface $session, + \PSC\System\SettingsBundle\Service\Shop $shopService, + EntityManagerInterface $entityManager, + $uid + ) { + /** @var \PSC\Shop\EntityBundle\Entity\Shop $selectedShop */ + $selectedShop = $shopService->getSelectedShop(); + + /** @var Voucher $voucher */ + $voucher = $entityManager + ->getRepository('PSC\Shop\EntityBundle\Entity\Voucher') + ->findOneBy(['uid' => $uid, 'shop' => $selectedShop]); + + if (!$voucher) { + $session->getFlashBag()->add('error', 'Voucher not found'); + return $this->redirectToRoute('psc_shop_voucher_backend_list'); + } + + $previewData = $session->get('voucher_upload_preview'); + + if (!$previewData || $previewData['voucher_uid'] !== $voucher->getUid()) { + $session->getFlashBag()->add('error', 'No upload data found. Please upload a CSV file first.'); + return $this->redirectToRoute('psc_shop_voucher_backend_upload', ['uid' => $uid]); + } + + if ($request->isMethod('POST')) { + if ($request->request->get('confirm') === 'yes') { + // Import codes + $imported = $this->importCodes($previewData['codes'], $voucher, $selectedShop, $entityManager); + + // Disable "more" flag since we now have individual codes + if ($voucher->isMore()) { + $voucher->setMore(false); + $entityManager->persist($voucher); + $entityManager->flush(); + } + + // Clear session + $session->remove('voucher_upload_preview'); + + $session->getFlashBag()->add( + 'success', + sprintf( + 'Successfully imported %d voucher codes for "%s"', + $imported, + $voucher->getTitle() + ) + ); + + if ($previewData['duplicates'] > 0) { + $session->getFlashBag()->add( + 'warning', + sprintf( + '%d duplicate codes were skipped', + $previewData['duplicates'] + ) + ); + } + + return $this->redirectToRoute('psc_shop_voucher_backend_list'); + } else { + // Cancel + $session->remove('voucher_upload_preview'); + return $this->redirectToRoute('psc_shop_voucher_backend_upload', ['uid' => $uid]); + } + } + + return [ + 'voucher' => $voucher, + 'codes' => $previewData['codes'], + 'duplicates' => $previewData['duplicates'], + 'totalCodes' => count($previewData['codes']) + ]; + } + + /** + * Parse CSV file and return codes + * + * @param UploadedFile $file + * @param Voucher $voucher + * @param EntityManagerInterface $entityManager + * @return array + */ + private function parseCsvFile( + UploadedFile $file, + Voucher $voucher, + EntityManagerInterface $entityManager + ): array { + $codes = []; + $duplicates = 0; + $existingCodes = []; + + try { + // Get all existing codes for this voucher to check for duplicates + $voucherItemRepository = $entityManager->getRepository('PSC\Shop\EntityBundle\Entity\VoucherItem'); + $existingItems = $voucherItemRepository->findBy(['voucher' => $voucher]); + + foreach ($existingItems as $item) { + $existingCodes[trim($item->getCode())] = true; + } + + // Open and parse CSV file + if (($handle = fopen($file->getRealPath(), 'r')) !== false) { + while (($data = fgetcsv($handle, 1000, ',')) !== false) { + // Skip empty lines + if (empty($data) || !isset($data[0]) || trim($data[0]) === '') { + continue; + } + + $code = trim($data[0]); + + // Validate code length (max 40 chars per VoucherItem entity) + if (strlen($code) > 40) { + $code = substr($code, 0, 40); + } + + // Skip if code already exists + if (isset($existingCodes[$code])) { + $duplicates++; + continue; + } + + // Check if code is already in the parsed list (duplicates within file) + if (in_array($code, $codes)) { + $duplicates++; + continue; + } + + $codes[] = $code; + $existingCodes[$code] = true; + } + + fclose($handle); + + return [ + 'success' => true, + 'codes' => $codes, + 'duplicates' => $duplicates + ]; + } else { + return [ + 'success' => false, + 'error' => 'Could not open CSV file' + ]; + } + } catch (\Exception $e) { + return [ + 'success' => false, + 'error' => 'Error processing CSV: ' . $e->getMessage() + ]; + } + } + + /** + * Import codes into database + * + * @param array $codes + * @param Voucher $voucher + * @param Shop $shop + * @param EntityManagerInterface $entityManager + * @return int Number of imported codes + */ + private function importCodes( + array $codes, + Voucher $voucher, + Shop $shop, + EntityManagerInterface $entityManager + ): int { + $imported = 0; + + foreach ($codes as $code) { + $voucherItem = new VoucherItem(); + $voucherItem->setVoucher($voucher); + $voucherItem->setShop($shop); + $voucherItem->setCode($code); + $voucherItem->setUsed(false); + + $entityManager->persist($voucherItem); + $imported++; + } + + $entityManager->flush(); + + return $imported; + } } diff --git a/src/new/src/PSC/Shop/VoucherBundle/Form/Backend/CsvUploadType.php b/src/new/src/PSC/Shop/VoucherBundle/Form/Backend/CsvUploadType.php new file mode 100644 index 000000000..0a190ae0e --- /dev/null +++ b/src/new/src/PSC/Shop/VoucherBundle/Form/Backend/CsvUploadType.php @@ -0,0 +1,60 @@ + + * @copyright 2012-2013 PrintshopCreator GmbH + * @license Private + * @link http://www.printshopcreator.de + */ + +namespace PSC\Shop\VoucherBundle\Form\Backend; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\File; + +/** + * CsvUploadType + * + * @package PSC\Shop\Voucher + * @subpackage Form + */ +class CsvUploadType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('file', FileType::class, [ + 'label' => 'csvfile', + 'required' => true, + 'constraints' => [ + new File([ + 'maxSize' => '2M', + 'mimeTypes' => [ + 'text/csv', + 'text/plain', + 'application/csv', + 'text/comma-separated-values', + 'application/vnd.ms-excel', + ], + 'mimeTypesMessage' => 'Please upload a valid CSV file', + ]) + ], + ]) + ->add('upload', SubmitType::class, ['label' => 'upload']); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'translation_domain' => 'core_voucher_upload', + ]); + } +} diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.de.yaml b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.de.yaml index 5fe2c17f2..f94f87f30 100755 --- a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.de.yaml +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.de.yaml @@ -18,4 +18,5 @@ Addvoucher: Gutschein hinzufügen Numbertogenerate: Anzahl (zu generieren) Numbergenerated: Anzahl (generiert) Generate: Generieren -Export: Exportieren \ No newline at end of file +Export: Exportieren +Upload: Hochladen \ No newline at end of file diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.en.yaml b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.en.yaml index e96c70e67..f28537e56 100755 --- a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.en.yaml +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_list.en.yaml @@ -18,4 +18,5 @@ Addvoucher: Add Voucher Numbertogenerate: Number (to generate) Numbergenerated: Number (to generated) Generate: Generate -Export: Export \ No newline at end of file +Export: Export +Upload: Upload \ No newline at end of file diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.de.yaml b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.de.yaml new file mode 100644 index 000000000..03c0954c3 --- /dev/null +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.de.yaml @@ -0,0 +1,18 @@ +Upload: Hochladen +CSVCodes: CSV Codes +Uploadvouchercodes: Gutscheincodes hochladen +CSVUploadDescription: Laden Sie eine CSV-Datei mit Gutscheincodes hoch. Die Datei sollte eine Spalte ohne Kopfzeile enthalten, wobei jede Zeile einen Code enthält. Doppelte Codes werden übersprungen. +csvfile: CSV-Datei +upload: Hochladen +cancel: Abbrechen +back: Zurück +Preview: Vorschau +PreviewCodes: Vorschau der Codes +ReadyToImport: Codes bereit zum Import +PreviewDescription: Überprüfen Sie die Codes vor dem Import. Duplikate wurden bereits herausgefiltert. +NewCodes: Neue Codes +SkippedDuplicates: Übersprungene Duplikate +Code: Code +NoCodesToImport: Keine Codes zum Importieren gefunden. +ApplyImport: Codes übernehmen +Codes: Codes diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.en.yaml b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.en.yaml new file mode 100644 index 000000000..cc817faa6 --- /dev/null +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/translations/core_voucher_upload.en.yaml @@ -0,0 +1,18 @@ +Upload: Upload +CSVCodes: CSV Codes +Uploadvouchercodes: Upload voucher codes +CSVUploadDescription: Upload a CSV file containing voucher codes. The file should contain one column without a header, with each row containing one code. Duplicate codes will be skipped. +csvfile: CSV File +upload: Upload +cancel: Cancel +back: Back +Preview: Preview +PreviewCodes: Code Preview +ReadyToImport: Codes ready to import +PreviewDescription: Review the codes before importing. Duplicates have already been filtered out. +NewCodes: New codes +SkippedDuplicates: Skipped duplicates +Code: Code +NoCodesToImport: No codes found to import. +ApplyImport: Apply Import +Codes: Codes diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/create.html.twig b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/create.html.twig index 0c5af1c02..f9f5fb385 100755 --- a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/create.html.twig +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/create.html.twig @@ -13,7 +13,7 @@
- + diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload.html.twig b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload.html.twig new file mode 100644 index 000000000..ee69b9d79 --- /dev/null +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload.html.twig @@ -0,0 +1,54 @@ +{% extends 'backend_tailwind_base.html.twig' %} +{% form_theme form 'tailwind_formtheme.html.twig' %} +{% trans_default_domain 'core_voucher_upload' %} + +{% block header %} + +{% endblock %} + +{% block body %} +
+ {{ form_start(form, {attr: {class: '', enctype: 'multipart/form-data'}}) }} + +
+
{{'Uploadvouchercodes'|trans}}: {{ voucher.title }}
+ +

+ {{'CSVUploadDescription'|trans}} +

+ +
+
+ {{ form_row(form.file) }} +
+
+
+ +
+ {{ form_widget(form.upload, { + attr: { + class: 'inline-flex items-center justify-center py-1 gap-1 font-medium rounded-md px-4 text-sm text-white shadow-lg bg-psc-500 hover:bg-psc-600 hover:ring-2 hover:ring-psc-500 hover:ring-offset-2 min-h-[2.25rem]' + } + }) }} +
+ + {{ form_end(form) }} +
+{% endblock %} diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload_confirm.html.twig b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload_confirm.html.twig new file mode 100644 index 000000000..74badc11d --- /dev/null +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/edit/upload_confirm.html.twig @@ -0,0 +1,102 @@ +{% extends 'backend_tailwind_base.html.twig' %} +{% trans_default_domain 'core_voucher_upload' %} + +{% block header %} +
+
+

+ + + + + {{'Preview'|trans}} {{'CSVCodes'|trans}} +

+
+ +
+{% endblock %} + +{% block body %} +
+
+
{{'PreviewCodes'|trans}}: {{ voucher.title }}
+ +
+
+ + + +
+

{{'ReadyToImport'|trans}}

+

{{'PreviewDescription'|trans}}

+
    +
  • {{'NewCodes'|trans}}: {{ totalCodes }}
  • + {% if duplicates > 0 %} +
  • {{'SkippedDuplicates'|trans}}: {{ duplicates }}
  • + {% endif %} +
+
+
+
+ +
+ + + + + + + + + {% for code in codes %} + + + + + {% endfor %} + +
+ # + + {{'Code'|trans}} +
+ {{ loop.index }} + + {{ code }} +
+
+ + {% if totalCodes == 0 %} +
+

+ {{'NoCodesToImport'|trans}} +

+
+ {% endif %} +
+ +
+
+ + +
+
+
+{% endblock %} diff --git a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/list/index.html.twig b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/list/index.html.twig index d235d699e..6d8c37e93 100755 --- a/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/list/index.html.twig +++ b/src/new/src/PSC/Shop/VoucherBundle/Resources/views/backend/list/index.html.twig @@ -103,6 +103,12 @@ {% if voucher.more %}Keine Individuellen Codes{% else %}{{ voucher.voucherItems|length }}{% endif %}
@@ -211,6 +213,7 @@ +