Skip to content

fix: customer.language maps to shopId, not localeId - adding correct association#195

Open
PantherX99 wants to merge 7 commits into
shopware:trunkfrom
PantherX99:trunk
Open

fix: customer.language maps to shopId, not localeId - adding correct association#195
PantherX99 wants to merge 7 commits into
shopware:trunkfrom
PantherX99:trunk

Conversation

@PantherX99

Copy link
Copy Markdown
Contributor

when migrating, unnecessary / unused languages are created. this is due to the mapping of customer.language which maps to the shopid and not the localeid.

this causes SWAG_MIGRATION_WRITE_EXCEPTION errors, because it assigns a wrong language id to the customer, so the customer cant be created and their order neither as well.

  1. [/languageId] The language "019e88ab8071730686a04a88ca695f99" is not assigned to the sales channel. in /var/www/vhosts/c8yb4.creoline.cloud/httpdocs/vendor/shopware/core/Framework/DataAbstractionLayer/Write/WriteContext.php:46
    Converted data (JSON):

    /**

    • Id of the language sub shop
    • var string
    • Orm\Column(name="language", type="string", length=10, nullable=false)
      */
      private $languageId = '1';

Updated joins to correctly map customer language to shop and locale.
customer.language maps to the shopId and not the localeId. mapping now correctly fetches existing locales from the shop association to prevent unnecessary / unused languages to be created
Refactor customer language joins in CustomerReader

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have a look at the tests too?

CustomerReaderTest:
static::assertSame('de-DE', $data[2]['customerlanguage']['locale']);

this should be failing no?

Also LanguageReaderTest should be updated as well

Comment thread src/Profile/Shopware/Gateway/Local/Reader/LanguageReader.php Outdated
…p and language of user 3, modified tests for sales channel and customer reader
@PantherX99

Copy link
Copy Markdown
Contributor Author

Jozsef Damokos (@jozsefdamokos) i'm new to this, sorry for any mistakes. i hope it's allright now.

@jozsefdamokos Jozsef Damokos (jozsefdamokos) added the external-contribution A PR contributed by a community member. label Jun 3, 2026
@PantherX99

Copy link
Copy Markdown
Contributor Author

https://github.com/shopware/SwagMigrationConnector/pull/13
I added the same fix to the sw5 connector plugin

@jozsefdamokos

Copy link
Copy Markdown
Member

PantherX99 no worries, we appreciate you taking the time to create the PR 😉

What i meant is that if indeed the shop id was used instead of the language id the assertion in CustomerReaderTest should have failed, no? or was it passing only by chance because the ids were matching?

Let's not add that extra shop in the main database file, instead we can insert it if needed in the test file itself, like in LanguageReaderTest::setUp

And i still feel like LanguageReaderTest should also be updated so it checks your changes

@PantherX99

PantherX99 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

What i meant is that if indeed the shop id was used instead of the language id the assertion in CustomerReaderTest should have failed, no? or was it passing only by chance because the ids were matching?

Jozsef Damokos (@jozsefdamokos) The default shops German and English have the ids 1 and 2 which actually match their locale ids for de_DE and en_GB. But as soon as you add another shop (id 3) with a different locale, it resolves to the locale with the id 3 which is Afar-Dschibuti and not the actual locale. So the ids were matching because it used the default config and no custom subshop was added in the test.

@PantherX99

Copy link
Copy Markdown
Contributor Author

Jozsef Damokos (@jozsefdamokos) Sorry, then it needs further support from some of you.

This test will fail because it directly assigns the locale to the customer.language property. The reader won't find a shop with id 9999 and only returns the locales for which a shop exists. So 'ar_EG' wont be returned as expected from the test.
Question is, what is the expected behaviour here?

public function testReadIncludesCustomerLocalesOutsideShopLocales(): void
    {
        $existingLocaleId = $this->dbConnection->fetchOne(
            'SELECT id FROM s_core_locales WHERE locale = :locale',
            ['locale' => 'ar_EG']
        );

        if ($existingLocaleId === false) {
            $this->dbConnection->executeStatement(
                'INSERT INTO s_core_locales (id, locale, language, territory) VALUES (:id, :locale, :language, :territory)',
                [
                    'id' => $this->customerLocaleId,
                    'locale' => 'ar_EG',
                    'language' => 'Arabic',
                    'territory' => 'Egypt',
                ]
            );

            $this->customerLocaleInserted = true;
        } else {
            $this->customerLocaleId = (int) $existingLocaleId;
        }

        $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [
            'language' => $this->customerLocaleId,
            'id' => 1,
        ]);

        $data = $this->languageReader->read($this->migrationContext);

        static::assertCount(3, $data);
        static::assertContains('ar-EG', \array_column($data, 'locale'));
    }

As there might be customers in the with a language value of a deleted shop (i dont know if this is even possible in sw5 but since a test for this case exists, i assume it is), it wont find a locale and $data['customerlanguage']['locale'] will be null. The CustomerConverter wont set a locale either, so the languageId in the converted data will be missing. This should cause a write fail because the language_id is NOT NULL.
To avoid this, maybe the converter should fall back to the mainlocale if no locale via the shop mapping is found?

if (!isset($converted['languageId'])) {
    $languageUuid = $this->resolveLanguageId(
        $this->mainLocale,
        $this->languageLookup,
        $context,
    );

    if ($languageUuid !== null) {
        $converted['languageId'] = $languageUuid;
    }
}

To test if my changes work, you should create a shop in the setup function with a different locale, modify the users language to this shop id and then test if it properly retrieves the correct locale. In addition to that, a user with an old/deleted language (shop) should be created to test the above mentioned case. But therefore the expected behaviour has to be determined.
Is that what you meant?

LOCK TABLES `s_core_shops` WRITE;
/*!40000 ALTER TABLE `s_core_shops` DISABLE KEYS */;
INSERT INTO `s_core_shops` VALUES (1,NULL,'Deutsch',NULL,0,'sw55.local','',NULL,'',0,23,23,3,1,1,1,NULL,0,1,1),(2,1,'English','English',0,NULL,NULL,NULL,'',0,NULL,NULL,39,2,1,1,2,0,0,1);
INSERT INTO `s_core_shops` VALUES (1,NULL,'Deutsch',NULL,0,'sw55.local','',NULL,'',0,23,23,3,1,1,1,NULL,0,1,1),(2,1,'English','English',0,NULL,NULL,NULL,'',0,NULL,NULL,39,2,1,1,2,0,0,1),(3, 1, 'Romanian', 'Romanian', 0, NULL, NULL, NULL, '', 0, NULL, NULL, 39, 192, 1, 1, 3, 0, 0, 1);

@jozsefdamokos Jozsef Damokos (jozsefdamokos) Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert this addition here.

Instead we should add this special scenario in CustomerReaderTest andLanguageReaderTest

// in setUp method:
$this->dbConnection->executeStatement(
    'INSERT INTO s_core_shops (
        id, name, position, hosts, secure, locale_id, customer_scope, `default`, active
    ) VALUES (
        999, "English shop", 0, "", 0, 2, 0, 0, 1
    )'
);

$this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [
    'language' => 999,
    'id' => 1,
]);

in LanguageReaderTest we can remove testReadIncludesCustomerLocalesOutsideShopLocales so it becomes something like:

class LanguageReaderTest extends TestCase
{
    use LocalCredentialTrait;

    private LanguageReader $languageReader;

    private MigrationContext $migrationContext;

    private Connection $dbConnection;

    protected function setUp(): void
    {
        $this->connectionSetup();

        $connectionFactory = new ConnectionFactory();
        $this->languageReader = new LanguageReader($connectionFactory);

        $this->migrationContext = new MigrationContext(
            $this->connection,
            new Shopware55Profile(),
            null,
            new LanguageDataSet(),
            $this->runId,
            0,
            10
        );

        $this->migrationContext->setGateway(new DummyLocalGateway());

        $this->dbConnection = $connectionFactory->createDatabaseConnection($this->migrationContext);

        $this->dbConnection->executeStatement(
            'INSERT INTO s_core_shops (
                id, name, position, hosts, secure, locale_id, customer_scope, `default`, active
            ) VALUES (
                999, "English shop", 0, "", 0, 2, 0, 0, 1
            )'
        );
        $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [
            'language' => 999,
            'id' => 1,
        ]);
    }

    protected function tearDown(): void
    {
        $this->dbConnection->executeStatement('UPDATE s_user SET language = :language WHERE id = :id', [
            'language' => 1,
            'id' => 1,
        ]);
        $this->dbConnection->executeStatement('DELETE FROM s_core_shops WHERE id = :id', [
            'id' => 999,
        ]);
    }

    public function testRead(): void
    {
        static::assertTrue($this->languageReader->supports($this->migrationContext));

        $data = $this->languageReader->read($this->migrationContext);
        $locales = \array_column($data, 'locale');

        static::assertCount(2, $data);
        static::assertContains('de-DE', $locales);
        static::assertContains('en-GB', $locales);
        static::assertNotContains('bn-IN', $locales);
    }
}

LOCK TABLES `s_user` WRITE;
/*!40000 ALTER TABLE `s_user` DISABLE KEYS */;
INSERT INTO `s_user` VALUES (1,'a256a310bc1e5db755fd392c524028a8','md5','test@example.com',1,0,'',5,0,NULL,NULL,'2011-11-23','2012-01-04 14:12:05','uiorqd755gaar8dn89ukp178c7',0,'',0,'EK',0,'1',1,'',NULL,'',0,NULL,1,3,NULL,'mr','Max','Mustermann',NULL,'20001',NULL,NULL,'2019-11-13 13:47:27',NULL),(2,'352db51c3ff06159d380d3d9935ec814','md5','mustermann@b2b.de',1,0,'',4,0,NULL,NULL,'2012-08-30','2012-08-30 11:43:17','66e9b10064a19b1fcf6eb9310c0753866c764836',0,'0',0,'H',4,'1',1,'',NULL,'',0,NULL,2,4,NULL,'mr','Händler','Kundengruppe-Netto',NULL,'20003',NULL,NULL,'2019-11-13 13:47:27',NULL),(3,'$2y$10$NVX9a/qzhoo0jS6U3i7YH.jBluI0hsOFqCnw/obStDxxkexPlDuHC','bcrypt','k.luetjann@shopware.com',1,0,'',5,0,NULL,NULL,'2020-02-26','2020-02-26 08:01:53','bioapqo043f3csuoctclulor7t',0,'0',0,'H',0,'1',1,'',NULL,'',0,NULL,5,5,'','mr','Krispin','Luetjann',NULL,'20005','27e8f8d6-a59d-476e-a6f0-9fcbbd03faf1.1','2020-02-26 08:00:31','2020-02-26 07:40:50',NULL);
INSERT INTO `s_user` VALUES (1,'a256a310bc1e5db755fd392c524028a8','md5','test@example.com',1,0,'',5,0,NULL,NULL,'2011-11-23','2012-01-04 14:12:05','uiorqd755gaar8dn89ukp178c7',0,'',0,'EK',0,'1',1,'',NULL,'',0,NULL,1,3,NULL,'mr','Max','Mustermann',NULL,'20001',NULL,NULL,'2019-11-13 13:47:27',NULL),(2,'352db51c3ff06159d380d3d9935ec814','md5','mustermann@b2b.de',1,0,'',4,0,NULL,NULL,'2012-08-30','2012-08-30 11:43:17','66e9b10064a19b1fcf6eb9310c0753866c764836',0,'0',0,'H',4,'1',1,'',NULL,'',0,NULL,2,4,NULL,'mr','Händler','Kundengruppe-Netto',NULL,'20003',NULL,NULL,'2019-11-13 13:47:27',NULL),(3,'$2y$10$NVX9a/qzhoo0jS6U3i7YH.jBluI0hsOFqCnw/obStDxxkexPlDuHC','bcrypt','k.luetjann@shopware.com',1,0,'',5,0,NULL,NULL,'2020-02-26','2020-02-26 08:01:53','bioapqo043f3csuoctclulor7t',0,'0',0,'H',0,'3',3,'',NULL,'',0,NULL,5,5,'','mr','Krispin','Luetjann',NULL,'20005','27e8f8d6-a59d-476e-a6f0-9fcbbd03faf1.1','2020-02-26 08:00:31','2020-02-26 07:40:50',NULL);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, please revert

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contribution A PR contributed by a community member.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants