From 16c4858fa08c871f13a873b2c71fd61f36325c23 Mon Sep 17 00:00:00 2001 From: Dominik Pfaffenbauer Date: Tue, 20 Jan 2026 08:04:17 +0100 Subject: [PATCH 01/19] [Release] 4.1.9 --- CHANGELOG-4.1.x.md | 3 +++ src/CoreShop/Bundle/CoreBundle/Application/Version.php | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) 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/src/CoreShop/Bundle/CoreBundle/Application/Version.php b/src/CoreShop/Bundle/CoreBundle/Application/Version.php
index 01bc265639..5bc0351ec0 100644
--- a/src/CoreShop/Bundle/CoreBundle/Application/Version.php
+++ b/src/CoreShop/Bundle/CoreBundle/Application/Version.php
@@ -24,7 +24,7 @@ final class Version
 
     public const MINOR_VERSION = '1';
 
-    public const RELEASE_VERSION = '8';
+    public const RELEASE_VERSION = '9';
 
     public const EXTRA_VERSION = '';
 

From 0f1e89614675ce65294065bd05297030ca3e19ff Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:43:09 +0000
Subject: [PATCH 02/19] Initial plan


From ddc0b5a5b2e346c3120182d79f580d5166ba8b35 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:43:24 +0000
Subject: [PATCH 03/19] Initial plan


From ebc277a4360206ee5d0cfbc93ae609a6a5c887f6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:43:59 +0000
Subject: [PATCH 04/19] Initial plan


From c94df7f606ab22507094a57cfd92c9dcb2c80517 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:44:59 +0000
Subject: [PATCH 05/19] Initial plan


From dc266d5611139cf153c4b40fa40a326a78bb05c5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:47:04 +0000
Subject: [PATCH 06/19] Add coreshop:setup:states CLI command for creating
 states/regions

Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com>
---
 .../03_States/02_Setup_Command.md             |  66 +++++++
 .../CoreBundle/Command/SetupStatesCommand.php | 182 ++++++++++++++++++
 .../Resources/config/services/commands.yml    |   9 +
 3 files changed, 257 insertions(+)
 create mode 100644 docs/03_Development/02_Localization/03_States/02_Setup_Command.md
 create mode 100644 src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php

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..d1072ef210
--- /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.', $countryCode));
+
+                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 (empty($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 }
+

From d665cad316e45c23a5a40895cea0e1b0bbd55ed0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:48:50 +0000
Subject: [PATCH 07/19] Address code review feedback: improve exception
 handling and consistency

Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com>
---
 src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php b/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php
index d1072ef210..c3945996f6 100644
--- a/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php
+++ b/src/CoreShop/Bundle/CoreBundle/Command/SetupStatesCommand.php
@@ -105,7 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
             try {
                 $rinvexCountry = CountryLoader::country($countryCode);
             } catch (\Exception $e) {
-                $io->warning(sprintf('Country data not found for code "%s" in Rinvex data.', $countryCode));
+                $io->warning(sprintf('Country data not found for code "%s" in Rinvex data: %s', $countryCode, $e->getMessage()));
 
                 continue;
             }
@@ -129,7 +129,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
             }
 
             foreach ($divisions as $isoCode => $division) {
-                if (empty($division['name'])) {
+                if (!$division['name']) {
                     continue;
                 }
 

From 1527ac80f97b573dab13ba4e1dc1a44d99cbeff5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:49:06 +0000
Subject: [PATCH 08/19] Initial plan


From 1f01b820bd78a311e2215c2e1a403737da3e0721 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:49:15 +0000
Subject: [PATCH 09/19] feat: Add multi-select drag & drop support for
 condition lists

Modified objectMultihref.js to iterate through all selected records in
data.records instead of only processing the first record. This enables
users to select multiple data objects (products, categories, customers,
etc.) and drag them all at once into condition lists.

Fixes the issue where each data object had to be added individually to
condition lists in cart price rules, product price rules, shipping rules,
and other rule configurations.

Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com>
---
 .../pimcore/js/object/objectMultihref.js      | 48 +++++++++++--------
 1 file changed, 27 insertions(+), 21 deletions(-)

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 897af4c39a..377ead92fa 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
@@ -153,20 +153,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 i = 0; i < data.records.length; i++) {
+                            var recordData = data.records[i].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
@@ -174,24 +173,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 i = 0; i < data.records.length; i++) {
+                            var recordData = data.records[i].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));

From 8953519dd3588eb306a0b218ae082aa176de82b8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 11:53:34 +0000
Subject: [PATCH 10/19] Add auto-refresh feature and rotate queue names in
 MessengerBundle UI

Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com>
---
 .../Resources/public/pimcore/js/list.js       | 70 ++++++++++++++++++-
 .../Resources/translations/admin.de.yml       |  7 +-
 .../Resources/translations/admin.en.yml       |  7 +-
 3 files changed, 80 insertions(+), 4 deletions(-)

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 6181d36dbe..ac4b67254a 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js
@@ -20,6 +20,10 @@ coreshop.messenger.list = Class.create({
     messagesStore: null,
     failedMessagesStore: null,
 
+    autoRefreshInterval: null,
+    autoRefreshTimer: null,
+    lastRefreshLabel: null,
+
     initialize: function () {
         this.getPanel();
     },
@@ -36,8 +40,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'),
@@ -49,7 +92,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',
@@ -78,6 +135,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));
 
@@ -98,7 +156,10 @@ coreshop.messenger.list = Class.create({
                     rootProperty: 'data'
                 }
             },
-            fields: ['name', 'count']
+            fields: ['name', 'count'],
+            listeners: {
+                load: this.updateLastRefreshLabel.bind(this)
+            }
         });
         this.chartStore.load();
 
@@ -118,6 +179,11 @@ coreshop.messenger.list = Class.create({
                 position: 'bottom',
                 grid: true,
                 fields: ['receiver'],
+                label: {
+                    rotate: {
+                        degrees: -45
+                    }
+                }
             }],
             series: [{
                 type: 'bar',
diff --git a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
index 94ebf33b84..bfca6f7216 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml
@@ -10,4 +10,9 @@ 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'
\ 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..b7517f5f01 100644
--- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml
+++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml
@@ -10,4 +10,9 @@ 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'
\ No newline at end of file

From fa143d90ab6f6af434b67d4d179c0233d3cf0c9e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 29 Jan 2026 12:02:41 +0000
Subject: [PATCH 11/19] Fix information exposure vulnerability in
 OrderInvoiceController and OrderShipmentController

Replace exception messages and stack traces in client responses with generic error
messages. Detailed error information is now logged server-side using LoggerInterface.

Fixes security vulnerability where internal implementation details were exposed to clients.

Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com>
---
 .../Controller/OrderInvoiceController.php            | 12 +++++++++++-
 .../Controller/OrderShipmentController.php           | 12 +++++++++++-
 2 files changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
index 6ebba0cc31..0e2dd2681a 100644
--- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
+++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php
@@ -36,6 +36,7 @@
 use CoreShop\Component\Pimcore\DataObject\NoteServiceInterface;
 use CoreShop\Component\Resource\Factory\FactoryInterface;
 use CoreShop\Component\Resource\Factory\PimcoreFactoryInterface;
+use Psr\Log\LoggerInterface;
 use Symfony\Component\DependencyInjection\Attribute\Autowire;
 use Symfony\Component\EventDispatcher\GenericEvent;
 use Symfony\Component\HttpFoundation\JsonResponse;
@@ -187,7 +188,15 @@ public function renderAction(Request $request): Response
                     'Content-Disposition' => 'inline; filename="invoice-' . $invoice->getId() . '.pdf"',
                 ];
             } catch (\Exception $e) {
-                $responseData = '' . $e->getMessage() . '
trace: ' . $e->getTraceAsString(); + $this->container->get(LoggerInterface::class)->error( + 'Failed to render invoice PDF', + [ + 'invoiceId' => $invoice->getId(), + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ], + ); + $responseData = 'An error occurred while rendering the invoice. Please try again later.'; $header = ['Content-Type' => 'text/html']; } @@ -244,6 +253,7 @@ public static function getSubscribedServices(): array new SubscribedService('coreshop.order.transformer.order_to_invoice', OrderDocumentTransformerInterface::class, attributes: new Autowire('@CoreShop\Component\Order\Transformer\OrderToInvoiceTransformer')), new SubscribedService('coreshop.repository.order_invoice', OrderInvoiceRepositoryInterface::class, attributes: new Autowire(service:'coreshop.repository.order_invoice')), new SubscribedService(StateMachineManagerInterface::class, StateMachineManagerInterface::class), + new SubscribedService(LoggerInterface::class, LoggerInterface::class), ]); } } diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php index c967b41552..c94d264919 100644 --- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php +++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php @@ -36,6 +36,7 @@ use CoreShop\Component\Order\Transformer\OrderToShipmentTransformer; use CoreShop\Component\Resource\Factory\FactoryInterface; use CoreShop\Component\Resource\Repository\PimcoreRepositoryInterface; +use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\HttpFoundation\JsonResponse; @@ -183,7 +184,15 @@ public function renderAction(Request $request): Response 'Content-Disposition' => 'inline; filename="shipment-' . $shipment->getId() . '.pdf"', ]; } catch (\Exception $e) { - $responseData = '' . $e->getMessage() . '
trace: ' . $e->getTraceAsString(); + $this->container->get(LoggerInterface::class)->error( + 'Failed to render shipment PDF', + [ + 'shipmentId' => $shipment->getId(), + 'exception' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ], + ); + $responseData = 'An error occurred while rendering the shipment. Please try again later.'; $header = ['Content-Type' => 'text/html']; } @@ -240,6 +249,7 @@ public static function getSubscribedServices(): array new SubscribedService('event_dispatcher', EventDispatcherInterface::class), new SubscribedService(OrderToShipmentTransformer::class, OrderToShipmentTransformer::class), new SubscribedService(ErrorSerializer::class, ErrorSerializer::class), + new SubscribedService(LoggerInterface::class, LoggerInterface::class), ]); } } From ac3b8a89f7ac7f746dfe1d020569c7309444e09a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:05:16 +0000 Subject: [PATCH 12/19] Return HTTP 500 status code on error in renderAction methods Address code review feedback to return appropriate error status code instead of HTTP 200 when an error occurs during PDF rendering. Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Bundle/OrderBundle/Controller/OrderInvoiceController.php | 2 ++ .../Bundle/OrderBundle/Controller/OrderShipmentController.php | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php index 0e2dd2681a..66582e1a1b 100644 --- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php +++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php @@ -198,6 +198,8 @@ public function renderAction(Request $request): Response ); $responseData = 'An error occurred while rendering the invoice. Please try again later.'; $header = ['Content-Type' => 'text/html']; + + return new Response($responseData, 500, $header); } 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 c94d264919..53cea76e04 100644 --- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php +++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php @@ -194,6 +194,8 @@ public function renderAction(Request $request): Response ); $responseData = 'An error occurred while rendering the shipment. Please try again later.'; $header = ['Content-Type' => 'text/html']; + + return new Response($responseData, 500, $header); } return new Response($responseData, 200, $header); From 24fb7c032915ae50100d49d627607f8dc1218ae9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 12:05:54 +0000 Subject: [PATCH 13/19] docs: update Menu Bundle JavaScript documentation to use modern event pattern Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- docs/03_Bundles/Menu_Bundle.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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'); } - } - + }); ``` From dbfac39c3e710f9e395f8bea82af4fc34af3bd9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:09:58 +0000 Subject: [PATCH 14/19] refactor: Use for-of loops instead of traditional for loops Address code review feedback to use for-of loops for simpler iteration over data.records arrays in both onNodeOver and onNodeDrop handlers. Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Resources/public/pimcore/js/object/objectMultihref.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 377ead92fa..d4bd157638 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 @@ -156,8 +156,8 @@ coreshop.object.objectMultihref = Class.create(pimcore.object.tags.manyToManyObj var fromTree = this.isFromTree(ddSource); // Check if any of the records can be dropped - for (var i = 0; i < data.records.length; i++) { - var recordData = data.records[i].data; + for (var record of data.records) { + var recordData = record.data; if (recordData.elementType === 'object' && this.dndAllowed(recordData, fromTree)) { return Ext.dd.DropZone.prototype.dropAllowed; } @@ -176,8 +176,8 @@ coreshop.object.objectMultihref = Class.create(pimcore.object.tags.manyToManyObj var addedAny = false; // Process all records in the drag selection - for (var i = 0; i < data.records.length; i++) { - var recordData = data.records[i].data; + for (var record of data.records) { + var recordData = record.data; if (recordData.elementType !== 'object') { continue; From 4db8fcd05cb1ed444714a180eb21d4b1b8760850 Mon Sep 17 00:00:00 2001 From: Dominik Pfaffenbauer Date: Thu, 29 Jan 2026 20:17:51 +0100 Subject: [PATCH 15/19] Add 'Copilot' to the allowlist in CLA check --- .github/workflows/cla-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From f58485a4714cb1f25df1465ad7838fe2cd8629b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:26:21 +0000 Subject: [PATCH 16/19] Change chart labels to trim with ellipsis and show full name on bar hover Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Resources/public/pimcore/js/list.js | 15 ++++++++++++--- .../Resources/translations/admin.de.yml | 3 ++- .../Resources/translations/admin.en.yml | 3 ++- 3 files changed, 16 insertions(+), 5 deletions(-) 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 ac4b67254a..d6bdf4c6c8 100644 --- a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js +++ b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js @@ -156,7 +156,7 @@ coreshop.messenger.list = Class.create({ rootProperty: 'data' } }, - fields: ['name', 'count'], + fields: ['receiver', 'count'], listeners: { load: this.updateLastRefreshLabel.bind(this) } @@ -180,8 +180,11 @@ coreshop.messenger.list = Class.create({ grid: true, fields: ['receiver'], label: { - rotate: { - degrees: -45 + renderer: function (value) { + if (value && value.length > 15) { + return Ext.String.ellipsis(value, 15); + } + return value; } } }], @@ -193,6 +196,12 @@ coreshop.messenger.list = Class.create({ 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 bfca6f7216..1d96fd740a 100644 --- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml +++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.de.yml @@ -15,4 +15,5 @@ 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' \ No newline at end of file +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 b7517f5f01..8358c63817 100644 --- a/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml +++ b/src/CoreShop/Bundle/MessengerBundle/Resources/translations/admin.en.yml @@ -15,4 +15,5 @@ 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' \ No newline at end of file +coreshop_messenger_auto_refresh_30s: 'Refresh every 30 seconds' +coreshop_messenger_messages: 'message(s)' \ No newline at end of file From de3345e13302ab058eb656399d66671d130c4dee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:34:34 +0000 Subject: [PATCH 17/19] Remove logging and simplify error response in renderAction methods Address review feedback to remove logging calls and simplify error responses while still preventing information exposure. Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Controller/OrderInvoiceController.php | 15 +-------------- .../Controller/OrderShipmentController.php | 15 +-------------- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php index 66582e1a1b..f8d366cd07 100644 --- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php +++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderInvoiceController.php @@ -36,7 +36,6 @@ use CoreShop\Component\Pimcore\DataObject\NoteServiceInterface; use CoreShop\Component\Resource\Factory\FactoryInterface; use CoreShop\Component\Resource\Factory\PimcoreFactoryInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\HttpFoundation\JsonResponse; @@ -188,18 +187,7 @@ public function renderAction(Request $request): Response 'Content-Disposition' => 'inline; filename="invoice-' . $invoice->getId() . '.pdf"', ]; } catch (\Exception $e) { - $this->container->get(LoggerInterface::class)->error( - 'Failed to render invoice PDF', - [ - 'invoiceId' => $invoice->getId(), - 'exception' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ], - ); - $responseData = 'An error occurred while rendering the invoice. Please try again later.'; - $header = ['Content-Type' => 'text/html']; - - return new Response($responseData, 500, $header); + return new Response('An error occurred while rendering the invoice.', 500, ['Content-Type' => 'text/html']); } return new Response($responseData, 200, $header); @@ -255,7 +243,6 @@ public static function getSubscribedServices(): array new SubscribedService('coreshop.order.transformer.order_to_invoice', OrderDocumentTransformerInterface::class, attributes: new Autowire('@CoreShop\Component\Order\Transformer\OrderToInvoiceTransformer')), new SubscribedService('coreshop.repository.order_invoice', OrderInvoiceRepositoryInterface::class, attributes: new Autowire(service:'coreshop.repository.order_invoice')), new SubscribedService(StateMachineManagerInterface::class, StateMachineManagerInterface::class), - new SubscribedService(LoggerInterface::class, LoggerInterface::class), ]); } } diff --git a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php index 53cea76e04..644727b8d8 100644 --- a/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php +++ b/src/CoreShop/Bundle/OrderBundle/Controller/OrderShipmentController.php @@ -36,7 +36,6 @@ use CoreShop\Component\Order\Transformer\OrderToShipmentTransformer; use CoreShop\Component\Resource\Factory\FactoryInterface; use CoreShop\Component\Resource\Repository\PimcoreRepositoryInterface; -use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\EventDispatcher\GenericEvent; use Symfony\Component\HttpFoundation\JsonResponse; @@ -184,18 +183,7 @@ public function renderAction(Request $request): Response 'Content-Disposition' => 'inline; filename="shipment-' . $shipment->getId() . '.pdf"', ]; } catch (\Exception $e) { - $this->container->get(LoggerInterface::class)->error( - 'Failed to render shipment PDF', - [ - 'shipmentId' => $shipment->getId(), - 'exception' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ], - ); - $responseData = 'An error occurred while rendering the shipment. Please try again later.'; - $header = ['Content-Type' => 'text/html']; - - return new Response($responseData, 500, $header); + return new Response('An error occurred while rendering the shipment.', 500, ['Content-Type' => 'text/html']); } return new Response($responseData, 200, $header); @@ -251,7 +239,6 @@ public static function getSubscribedServices(): array new SubscribedService('event_dispatcher', EventDispatcherInterface::class), new SubscribedService(OrderToShipmentTransformer::class, OrderToShipmentTransformer::class), new SubscribedService(ErrorSerializer::class, ErrorSerializer::class), - new SubscribedService(LoggerInterface::class, LoggerInterface::class), ]); } } From a40e747ae8980282af2af30834d75a6b37eba6ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:41:43 +0000 Subject: [PATCH 18/19] Show queue name in label for bars with 0 messages Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Resources/public/pimcore/js/list.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) 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 d6bdf4c6c8..d2e0d0d2e6 100644 --- a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js +++ b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js @@ -167,6 +167,7 @@ coreshop.messenger.list = Class.create({ xtype: 'cartesian', store: this.chartStore, height: 200, + innerPadding: {top: 20, bottom: 0, left: 0, right: 0}, axes: [{ type: 'numeric', position: 'left', @@ -179,13 +180,8 @@ coreshop.messenger.list = Class.create({ position: 'bottom', grid: true, fields: ['receiver'], - label: { - renderer: function (value) { - if (value && value.length > 15) { - return Ext.String.ellipsis(value, 15); - } - return value; - } + renderer: function (axis, label) { + return Ext.String.ellipsis(label, 20); } }], series: [{ @@ -193,9 +189,18 @@ coreshop.messenger.list = Class.create({ title: 'Messages', xField: 'receiver', yField: 'count', + highlight: true, label: { - field: 'count', - display: 'insideEnd' + field: 'receiver', + display: 'outside', + orientation: 'horizontal', + renderer: function (value, sprite, config, data) { + var count = data.store.getAt(data.index).get('count'); + if (count === 0) { + return Ext.String.ellipsis(value, 20) + ' (0)'; + } + return count; + } }, tooltip: { trackMouse: true, From 1b15e2c56ca9e2491cc0729a260bef974d9132f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:58:40 +0000 Subject: [PATCH 19/19] Revert chart to simple design with tooltip on bar hover Co-authored-by: dpfaffenbauer <5981845+dpfaffenbauer@users.noreply.github.com> --- .../Resources/public/pimcore/js/list.js | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) 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 d2e0d0d2e6..96c4848df0 100644 --- a/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js +++ b/src/CoreShop/Bundle/MessengerBundle/Resources/public/pimcore/js/list.js @@ -167,7 +167,6 @@ coreshop.messenger.list = Class.create({ xtype: 'cartesian', store: this.chartStore, height: 200, - innerPadding: {top: 20, bottom: 0, left: 0, right: 0}, axes: [{ type: 'numeric', position: 'left', @@ -179,10 +178,7 @@ coreshop.messenger.list = Class.create({ type: 'category', position: 'bottom', grid: true, - fields: ['receiver'], - renderer: function (axis, label) { - return Ext.String.ellipsis(label, 20); - } + fields: ['receiver'] }], series: [{ type: 'bar', @@ -191,16 +187,8 @@ coreshop.messenger.list = Class.create({ yField: 'count', highlight: true, label: { - field: 'receiver', - display: 'outside', - orientation: 'horizontal', - renderer: function (value, sprite, config, data) { - var count = data.store.getAt(data.index).get('count'); - if (count === 0) { - return Ext.String.ellipsis(value, 20) + ' (0)'; - } - return count; - } + field: 'count', + display: 'insideEnd' }, tooltip: { trackMouse: true,