diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index 41c9f4a47f..9a761cd6b4 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -25,6 +25,6 @@ jobs: path-to-signatures: 'signatures/version1/cla.json' path-to-document: 'https://github.com/coreshop/coreshop/blob/4.0/CLA.md' branch: "main" - allowlist: user1,bot* + allowlist: user1,bot*,Copilot remote-organization-name: "coreshop" remote-repository-name: "cla" diff --git a/CHANGELOG-4.1.x.md b/CHANGELOG-4.1.x.md index a42a6fcb10..3dcaef7c56 100644 --- a/CHANGELOG-4.1.x.md +++ b/CHANGELOG-4.1.x.md @@ -1,3 +1,6 @@ +## 4.1.9 +* Fix Injection in CustomerTransformerController by @dpfaffenbauer in https://github.com/coreshop/CoreShop/pull/2945 + ## 4.1.8 * [Messenger] dispatch `FailedMessageDetailsEvent` to allow customization of failed message details generation by @jdreesen in https://github.com/coreshop/CoreShop/pull/2911 * [Messenger] wrap failed message details info modal data in `
` tags by @jdreesen in https://github.com/coreshop/CoreShop/pull/2910
diff --git a/docs/03_Bundles/Menu_Bundle.md b/docs/03_Bundles/Menu_Bundle.md
index 5277296c8a..8f06ef70e0 100644
--- a/docs/03_Bundles/Menu_Bundle.md
+++ b/docs/03_Bundles/Menu_Bundle.md
@@ -75,14 +75,14 @@ public function registerBundlesToCollection(BundleCollection $collection)
```javascript
new coreshop.menu.coreshop.my_menu();
-
- pimcore.eventDispatcher.registerTarget('coreshopMenuOpen', new (Class.create({
- coreshopMenuOpen: function(type, item) {
+
+ document.addEventListener(coreshop.events.menu.open, (e) => {
+ var item = e.detail.item;
+
if (item.id === 'my-menu-item') {
alert('My Menu Item has been clicked');
}
- }
-
+ });
```
diff --git a/docs/03_Development/02_Localization/03_States/02_Setup_Command.md b/docs/03_Development/02_Localization/03_States/02_Setup_Command.md
new file mode 100644
index 0000000000..00d3fe789a
--- /dev/null
+++ b/docs/03_Development/02_Localization/03_States/02_Setup_Command.md
@@ -0,0 +1,66 @@
+# Setup States Command
+
+CoreShop provides a CLI command to create states/regions for countries after the initial installation.
+
+By default, CoreShop only creates states for Austria (AT) during installation. Use this command to add states for additional countries.
+
+## Usage
+
+```bash
+# Setup states for a single country
+php bin/console coreshop:setup:states DE
+
+# Setup states for multiple countries
+php bin/console coreshop:setup:states DE,US,FR
+
+# Setup states and activate the country if not already active
+php bin/console coreshop:setup:states DE --activate-country
+
+# Verbose output to see individual states being created
+php bin/console coreshop:setup:states DE -v
+```
+
+## Arguments
+
+| Argument | Description |
+|----------|-------------|
+| countries | Comma-separated list of country ISO codes (e.g., DE,US,FR) |
+
+## Options
+
+| Option | Description |
+|--------|-------------|
+| --activate-country | Also activate the country if not already active |
+
+## Prerequisites
+
+Before running this command, ensure that:
+
+1. CoreShop is installed (`coreshop:install` has been run)
+2. The country fixtures have been loaded (countries exist in the database)
+
+## How It Works
+
+The command:
+
+1. Looks up each specified country in the database by ISO code
+2. Loads the country's divisions (states/regions) from the Rinvex country data library
+3. Creates states for each division that doesn't already exist
+4. Optionally activates the country if `--activate-country` is specified
+
+## Example Output
+
+```
+CoreShop States Setup
+=====================
+
+Setting up states for countries: DE
+
+Processing country: DE
+-----------------------
+
+ [OK] Countries processed: 1
+ States created: 16
+ States skipped (already exist): 0
+ Countries activated: 0
+```
diff --git a/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php b/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php
new file mode 100644
index 0000000000..c3945996f6
--- /dev/null
+++ b/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php
@@ -0,0 +1,182 @@
+setName('coreshop:setup:states')
+ ->setDescription('Create states/regions for specified countries.')
+ ->addArgument(
+ 'countries',
+ InputArgument::REQUIRED,
+ 'Comma-separated list of country ISO codes (e.g., DE,US,FR)',
+ )
+ ->addOption(
+ 'activate-country',
+ null,
+ InputOption::VALUE_NONE,
+ 'Also activate the country if not already active',
+ )
+ ->setHelp(
+ <<%command.name% command creates states/regions for specified countries.
+
+Examples:
+ php bin/console %command.name% DE
+ php bin/console %command.name% DE,US,FR
+ php bin/console %command.name% DE --activate-country
+EOT
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $countriesArg = $input->getArgument('countries');
+ $activateCountry = $input->getOption('activate-country');
+ $countryCodes = array_map('strtoupper', array_map('trim', explode(',', $countriesArg)));
+
+ $languages = Tool::getValidLanguages();
+
+ $io->title('CoreShop States Setup');
+ $io->writeln(sprintf('Setting up states for countries: %s ', implode(', ', $countryCodes)));
+
+ $createdStates = 0;
+ $skippedStates = 0;
+ $countriesProcessed = 0;
+ $activatedCountries = 0;
+
+ foreach ($countryCodes as $countryCode) {
+ $io->section(sprintf('Processing country: %s', $countryCode));
+
+ // Check if country exists in database
+ $country = $this->countryRepository->findByCode($countryCode);
+ if ($country === null) {
+ $io->warning(sprintf('Country with code "%s" not found in database. Run fixtures first or install CoreShop.', $countryCode));
+
+ continue;
+ }
+
+ // Load country data from Rinvex
+ try {
+ $rinvexCountry = CountryLoader::country($countryCode);
+ } catch (\Exception $e) {
+ $io->warning(sprintf('Country data not found for code "%s" in Rinvex data: %s', $countryCode, $e->getMessage()));
+
+ continue;
+ }
+
+ // Activate country if requested
+ if ($activateCountry && !$country->getActive()) {
+ $country->setActive(true);
+ $this->entityManager->persist($country);
+ $activatedCountries++;
+ $io->writeln(sprintf(' Activated country: %s ', $countryCode));
+ }
+
+ // Get divisions (states/regions)
+ $divisions = $rinvexCountry->getDivisions();
+
+ if (!is_array($divisions) || empty($divisions)) {
+ $io->writeln(sprintf(' No divisions/states found for %s ', $countryCode));
+ $countriesProcessed++;
+
+ continue;
+ }
+
+ foreach ($divisions as $isoCode => $division) {
+ if (!$division['name']) {
+ continue;
+ }
+
+ // Check if state already exists
+ $existingState = $this->stateRepository->findOneBy([
+ 'isoCode' => $isoCode,
+ 'country' => $country,
+ ]);
+
+ if ($existingState !== null) {
+ $skippedStates++;
+ $io->writeln(sprintf(' Skipping existing state: %s (%s) ', $division['name'], $isoCode), OutputInterface::VERBOSITY_VERBOSE);
+
+ continue;
+ }
+
+ /**
+ * @var StateInterface $state
+ */
+ $state = $this->stateFactory->createNew();
+
+ foreach ($languages as $lang) {
+ $state->setName($division['name'], $lang);
+ }
+
+ $state->setIsoCode($isoCode);
+ $state->setCountry($country);
+ $state->setActive(true);
+
+ $this->entityManager->persist($state);
+ $createdStates++;
+
+ $io->writeln(sprintf(' Created state: %s (%s) ', $division['name'], $isoCode), OutputInterface::VERBOSITY_VERBOSE);
+ }
+
+ $countriesProcessed++;
+ }
+
+ $this->entityManager->flush();
+
+ $io->success([
+ sprintf('Countries processed: %d', $countriesProcessed),
+ sprintf('States created: %d', $createdStates),
+ sprintf('States skipped (already exist): %d', $skippedStates),
+ sprintf('Countries activated: %d', $activatedCountries),
+ ]);
+
+ return Command::SUCCESS;
+ }
+}
diff --git a/src/CoreShop/Bundle/CoreBundle/Resources/config/services/commands.yml b/src/CoreShop/Bundle/CoreBundle/Resources/config/services/commands.yml
index 510cd6f980..a42f6c30c3 100644
--- a/src/CoreShop/Bundle/CoreBundle/Resources/config/services/commands.yml
+++ b/src/CoreShop/Bundle/CoreBundle/Resources/config/services/commands.yml
@@ -50,3 +50,12 @@ services:
tags:
- { name: console.command, command: coreshop:migration:generate }
+ CoreShop\Bundle\CoreBundle\Command\SetupStatesCommand:
+ arguments:
+ - '@coreshop.repository.country'
+ - '@coreshop.repository.state'
+ - '@coreshop.factory.state'
+ - '@doctrine.orm.entity_manager'
+ tags:
+ - { name: console.command, command: coreshop:setup:states }
+
diff --git a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js
index e275cfb3c3..d27530d6ae 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js
@@ -21,6 +21,10 @@ coreshop.messenger.list = Class.create({
messagesStore: null,
failedMessagesStore: null,
+ autoRefreshInterval: null,
+ autoRefreshTimer: null,
+ lastRefreshLabel: null,
+
initialize: function () {
this.getPanel();
},
@@ -37,8 +41,47 @@ coreshop.messenger.list = Class.create({
}
},
+ updateLastRefreshLabel: function () {
+ if (this.lastRefreshLabel) {
+ var now = new Date();
+ var timeString = Ext.Date.format(now, 'Y-m-d H:i:s');
+ this.lastRefreshLabel.setText(t('coreshop_messenger_last_refresh') + ': ' + timeString);
+ }
+ },
+
+ startAutoRefresh: function (interval) {
+ this.stopAutoRefresh();
+
+ if (interval > 0) {
+ this.autoRefreshInterval = interval;
+ this.autoRefreshTimer = setInterval(this.reload.bind(this), interval * 1000);
+ }
+ },
+
+ stopAutoRefresh: function () {
+ if (this.autoRefreshTimer) {
+ clearInterval(this.autoRefreshTimer);
+ this.autoRefreshTimer = null;
+ }
+ this.autoRefreshInterval = null;
+ },
+
getPanel: function () {
if (!this.panel) {
+ this.lastRefreshLabel = Ext.create('Ext.toolbar.TextItem', {
+ text: ''
+ });
+
+ var autoRefreshStore = Ext.create('Ext.data.Store', {
+ fields: ['value', 'text'],
+ data: [
+ {value: 0, text: t('coreshop_messenger_auto_refresh_disabled')},
+ {value: 5, text: t('coreshop_messenger_auto_refresh_5s')},
+ {value: 10, text: t('coreshop_messenger_auto_refresh_10s')},
+ {value: 30, text: t('coreshop_messenger_auto_refresh_30s')}
+ ]
+ });
+
this.panel = Ext.create('Ext.panel.Panel', {
id: 'coreshop_messenger_list',
title: t('coreshop_messenger_list'),
@@ -50,7 +93,21 @@ coreshop.messenger.list = Class.create({
xtype: 'button',
iconCls: 'pimcore_icon_reload',
handler: this.reload.bind(this)
- }],
+ }, '-', {
+ xtype: 'combo',
+ store: autoRefreshStore,
+ displayField: 'text',
+ valueField: 'value',
+ value: 0,
+ editable: false,
+ width: 200,
+ queryMode: 'local',
+ listeners: {
+ select: function (combo, record) {
+ this.startAutoRefresh(record.get('value'));
+ }.bind(this)
+ }
+ }, '->', this.lastRefreshLabel],
items: [{
xtype: 'panel',
layout: 'border',
@@ -79,6 +136,7 @@ coreshop.messenger.list = Class.create({
tabPanel.setActiveItem('coreshop_messenger_list');
this.panel.on('destroy', function () {
+ this.stopAutoRefresh();
pimcore.globalmanager.remove('coreshop_messenger_list');
}.bind(this));
@@ -99,7 +157,10 @@ coreshop.messenger.list = Class.create({
rootProperty: 'data'
}
},
- fields: ['name', 'count']
+ fields: ['receiver', 'count'],
+ listeners: {
+ load: this.updateLastRefreshLabel.bind(this)
+ }
});
this.chartStore.load();
@@ -118,16 +179,23 @@ coreshop.messenger.list = Class.create({
type: 'category',
position: 'bottom',
grid: true,
- fields: ['receiver'],
+ fields: ['receiver']
}],
series: [{
type: 'bar',
title: 'Messages',
xField: 'receiver',
yField: 'count',
+ highlight: true,
label: {
field: 'count',
display: 'insideEnd'
+ },
+ tooltip: {
+ trackMouse: true,
+ renderer: function (tooltip, record) {
+ tooltip.setHtml(record.get('receiver') + ': ' + record.get('count') + ' ' + t('coreshop_messenger_messages'));
+ }
}
}]
};
diff --git a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
index 94ebf33b84..1d96fd740a 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
@@ -10,4 +10,10 @@ coreshop_messenger_failed_messages: 'Fehlgeschlagene Nachrichten'
coreshop_messenger_pending_messages: 'Ausstehende Nachrichten'
coreshop_messenger_receivers: 'Empfänger'
coreshop_messenger_info: 'Details'
-coreshop_permission_messenger: 'CoreShop: Messenger'
\ No newline at end of file
+coreshop_permission_messenger: 'CoreShop: Messenger'
+coreshop_messenger_last_refresh: 'Aktualisiert'
+coreshop_messenger_auto_refresh_disabled: 'Nicht aktualisieren'
+coreshop_messenger_auto_refresh_5s: 'Alle 5 Sekunden aktualisieren'
+coreshop_messenger_auto_refresh_10s: 'Alle 10 Sekunden aktualisieren'
+coreshop_messenger_auto_refresh_30s: 'Alle 30 Sekunden aktualisieren'
+coreshop_messenger_messages: 'Nachricht(en)'
\ No newline at end of file
diff --git a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml
index 5c1f54acf2..8358c63817 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml
@@ -10,4 +10,10 @@ coreshop_messenger_failed_messages: 'Failed Messages'
coreshop_messenger_pending_messages: 'Pending Messages'
coreshop_messenger_receivers: 'Receivers'
coreshop_messenger_info: 'Details'
-coreshop_permission_messenger: 'CoreShop: Messenger'
\ No newline at end of file
+coreshop_permission_messenger: 'CoreShop: Messenger'
+coreshop_messenger_last_refresh: 'Refreshed'
+coreshop_messenger_auto_refresh_disabled: 'Do not refresh'
+coreshop_messenger_auto_refresh_5s: 'Refresh every 5 seconds'
+coreshop_messenger_auto_refresh_10s: 'Refresh every 10 seconds'
+coreshop_messenger_auto_refresh_30s: 'Refresh every 30 seconds'
+coreshop_messenger_messages: 'message(s)'
\ No newline at end of file
diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
index 7b54fc1d48..50d063f68b 100644
--- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
+++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
@@ -186,8 +186,7 @@ public function renderAction(Request $request): Response
'Content-Disposition' => 'inline; filename="invoice-' . $invoice->getId() . '.pdf"',
];
} catch (\Exception $e) {
- $responseData = '' . $e->getMessage() . '
trace: ' . $e->getTraceAsString();
- $header = ['Content-Type' => 'text/html'];
+ return new Response('An error occurred while rendering the invoice.', 500, ['Content-Type' => 'text/html']);
}
return new Response($responseData, 200, $header);
diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php
index 55de86e161..e370910f25 100644
--- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php
+++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php
@@ -182,8 +182,7 @@ public function renderAction(Request $request): Response
'Content-Disposition' => 'inline; filename="shipment-' . $shipment->getId() . '.pdf"',
];
} catch (\Exception $e) {
- $responseData = '' . $e->getMessage() . '
trace: ' . $e->getTraceAsString();
- $header = ['Content-Type' => 'text/html'];
+ return new Response('An error occurred while rendering the shipment.', 500, ['Content-Type' => 'text/html']);
}
return new Response($responseData, 200, $header);
diff --git a/src/CoreShop/Bundle/ResourceBundle/Resources/public/pimcore/js/object/objectMultihref.js b/src/CoreShop/Bundle/ResourceBundle/Resources/public/pimcore/js/object/objectMultihref.js
index 4642f3741b..9b574712c5 100644
--- a/src/CoreShop/Bundle/ResourceBundle/Resources/public/pimcore/js/object/objectMultihref.js
+++ b/src/CoreShop/Bundle/ResourceBundle/Resources/public/pimcore/js/object/objectMultihref.js
@@ -154,20 +154,19 @@ coreshop.object.objectMultihref = Class.create(pimcore.object.tags.manyToManyObj
return this.component.getEl().dom;
}.bind(this),
onNodeOver: function (overHtmlNode, ddSource, e, data) {
- var record = data.records[0];
- var data = record.data;
var fromTree = this.isFromTree(ddSource);
- if (data.elementType == 'object' && this.dndAllowed(data, fromTree)) {
- return Ext.dd.DropZone.prototype.dropAllowed;
- } else {
- return Ext.dd.DropZone.prototype.dropNotAllowed;
+ // Check if any of the records can be dropped
+ for (var record of data.records) {
+ var recordData = record.data;
+ if (recordData.elementType === 'object' && this.dndAllowed(recordData, fromTree)) {
+ return Ext.dd.DropZone.prototype.dropAllowed;
+ }
}
+ return Ext.dd.DropZone.prototype.dropNotAllowed;
}.bind(this),
onNodeDrop: function (target, ddSource, e, data) {
- var record = data.records[0];
- var data = record.data;
var fromTree = this.isFromTree(ddSource);
// check if data is a treenode, if not allow drop because of the reordering
@@ -175,24 +174,31 @@ coreshop.object.objectMultihref = Class.create(pimcore.object.tags.manyToManyObj
return true;
}
- if (data.elementType != 'object') {
- return false;
- }
+ var addedAny = false;
- if (this.dndAllowed(data, fromTree)) {
- var initData = {
- id: data.id,
- path: data.path,
- type: data.className
- };
+ // Process all records in the drag selection
+ for (var record of data.records) {
+ var recordData = record.data;
+
+ if (recordData.elementType !== 'object') {
+ continue;
+ }
- if (!this.objectAlreadyExists(initData.id)) {
- this.store.add(initData);
- return true;
+ if (this.dndAllowed(recordData, fromTree)) {
+ var initData = {
+ id: recordData.id,
+ path: recordData.path,
+ type: recordData.className
+ };
+
+ if (!this.objectAlreadyExists(initData.id)) {
+ this.store.add(initData);
+ addedAny = true;
+ }
}
}
- return false;
+ return addedAny;
}.bind(this)
});
}.bind(this));