commit 627caccdaf6aa29a7cdde8dfe125355afc94ccf8 Author: Sven Ullmann Date: Mon Mar 16 18:06:14 2026 +0100 initiatione diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..089f7c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/vendor/ +/node_modules/ +/.idea/ +/.vscode/ +/var/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..925b09c --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# SumediaStage + +Shopware 6 Plugin zur Nutzung einer lizenzierten Installation unter einem URL-Unterverzeichnis (z. B. `/stage/`, `/local/`), ohne dass Plugin-Lizenzprüfungen oder die interne Shop-Identifikation fehlschlagen. + +## Hintergrund + +Shopware speichert die `APP_URL` zusammen mit der Shop-ID (`core.app.shopId`) in der Systemkonfiguration. Bei jedem Request vergleicht `ShopIdProvider` die aktuelle `APP_URL` mit dem gespeicherten Wert. Weichen Live (`https://example.com`) und Stage (`https://example.com/stage/`) ab, wirft Shopware eine `AppUrlChangeDetectedException` – Plugins melden daraufhin fehlende Lizenzen. + +Das Plugin registriert einen `EnvironmentHelperTransformer`, der bei jedem internen `APP_URL`-Lesezugriff den Pfad-Anteil automatisch entfernt: + +``` +https://www.example.com/stage/ → https://www.example.com +https://www.example.com/local/ → https://www.example.com +https://www.example.com → https://www.example.com (no-op) +``` + +## Installation auf dem Stage-System + +### Voraussetzungen + +- Shopware 6.6.x +- SSH-Zugang zum Stage-Server +- `APP_ENV` muss `dev` oder `staging` sein – **nicht** `prod` +- `APP_URL` in `.env.local` ist auf das Unterverzeichnis gesetzt, z. B.: + ``` + APP_URL=https://www.example.com/stage/ + ``` + +> **Wichtig:** Das Plugin lässt sich in `APP_ENV=prod` nicht aktivieren und registriert den Transformer auch dann nicht, falls es doch installiert sein sollte. Es ist ausschliesslich fuer Staging- und lokale Umgebungen gedacht. + +### 1. Plugin übertragen + +Plugin-Verzeichnis auf den Stage-Server kopieren (z. B. per rsync oder git): + +```bash +rsync -av custom/plugins/SumediaStage/ user@stage-server:/var/www/shopware/custom/plugins/SumediaStage/ +``` + +Oder per git, wenn das Stage-System dasselbe Repository nutzt – dann ist das Plugin bereits vorhanden. + +### 2. Plugin installieren und aktivieren + +```bash +php bin/console plugin:refresh +php bin/console plugin:install --activate SumediaStage +php bin/console cache:clear +``` + +### 3. Konfiguration prüfen + +Im Shopware-Admin unter **Einstellungen → Erweiterungen → SumediaStage**: + +| Einstellung | Empfohlener Wert | +|---|---| +| Normalize APP_URL | aktiv (true) | +| Base URL override | leer lassen | + +### 4. Verifizieren + +Nach der Installation sollte der Admin-Login funktionieren und keine Plugin-Lizenzfehler mehr erscheinen. Zur Kontrolle: + +```bash +php bin/console system:config:get core.app.shopId +``` + +Der gespeicherte `app_url`-Wert sollte nun ohne den Unterverzeichnis-Pfad gespeichert werden. + +## Deinstallation + +```bash +php bin/console plugin:uninstall SumediaStage +php bin/console cache:clear +``` + +## Konfigurationsoptionen + +**Admin (Einstellungen → Erweiterungen → SumediaStage):** + +| Schlüssel | Typ | Default | Beschreibung | +|---|---|---|---| +| `normalizeAppUrl` | bool | `true` | Transformer aktiv/inaktiv | +| `baseUrl` | string | leer | Explizite Basis-URL statt Auto-Erkennung (z. B. hinter Proxy mit falschem Host-Header) | + +**Umgebungsvariablen (`.env.local`):** + +| Variable | Wert | Beschreibung | +|---|---|---| +| `SUMEDIA_STAGE_ENABLED` | `0` | Transformer deaktivieren, ohne das Plugin zu deinstallieren | +| `APP_ENV` | `prod` | Transformer wird automatisch blockiert, Aktivierung wird verweigert | + +Beispiel `.env.local` um das Plugin temporaer zu deaktivieren: +``` +SUMEDIA_STAGE_ENABLED=0 +``` + +## Hinweis + +Das Plugin muss auf **allen** Instanzen (live, stage, local) installiert und aktiviert sein. Auf Live-Instanzen, bei denen `APP_URL` keinen Pfad-Anteil enthält, ist der Transformer ein No-Op. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7eb7877 --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "sumedia-webdesign/sumedia-stage", + "description": "Normalizes APP_URL for staging environments served under a subdirectory", + "type": "shopware-platform-plugin", + "version": "1.0.0", + "license": "MIT", + "require": { + "shopware/core": "~6.6.0" + }, + "extra": { + "shopware-plugin-class": "SumediaStage\\SumediaStage", + "plugin-icon": "src/Resources/config/plugin.png", + "label": { + "de-DE": "Sumedia Stage", + "en-GB": "Sumedia Stage" + } + }, + "autoload": { + "psr-4": { + "SumediaStage\\": "src/" + } + } +} diff --git a/src/DevOps/AppUrlNormalizer.php b/src/DevOps/AppUrlNormalizer.php new file mode 100644 index 0000000..075969b --- /dev/null +++ b/src/DevOps/AppUrlNormalizer.php @@ -0,0 +1,57 @@ +getKey() !== 'APP_URL' || !self::$enabled) { + return; + } + + if (self::$baseUrl !== null) { + $data->setValue(rtrim(self::$baseUrl, '/')); + return; + } + + $url = $data->getValue(); + if (!\is_string($url)) { + return; + } + + $parsed = parse_url($url); + if ($parsed === false || empty($parsed['host'])) { + return; + } + + $normalized = ($parsed['scheme'] ?? 'https') . '://' . $parsed['host']; + if (isset($parsed['port'])) { + $normalized .= ':' . $parsed['port']; + } + + $data->setValue($normalized); + } +} diff --git a/src/Resources/config/config.xml b/src/Resources/config/config.xml new file mode 100644 index 0000000..6bd58ae --- /dev/null +++ b/src/Resources/config/config.xml @@ -0,0 +1,62 @@ + + + + + + Sumedia Stage + Sumedia Stage + + + normalizeAppUrl + + + + TECHNICAL BACKGROUND: Shopware stores APP_URL alongside the Shop ID (core.app.shopId) and compares it on every request via ShopIdProvider. If live uses "https://example.com" but stage uses "https://example.com/stage/", Shopware throws AppUrlChangeDetectedException and plugins report license errors. + + HOW IT WORKS: This plugin registers an EnvironmentHelperTransformer that intercepts every internal APP_URL read and strips the path component before Shopware sees it. Both live and stage then present the same base URL to Shopware. + + Example: https://example.com/stage/ becomes https://example.com + + Safe to enable on live instances – if APP_URL has no path, the transformer is a no-op. + + Changes take effect after cache:clear. + + + TECHNISCHER HINTERGRUND: Shopware speichert APP_URL zusammen mit der Shop-ID (core.app.shopId) und vergleicht sie bei jedem Request via ShopIdProvider. Weichen Live ("https://example.com") und Stage ("https://example.com/stage/") ab, wirft Shopware eine AppUrlChangeDetectedException und Plugins melden Lizenzfehler. + + FUNKTIONSWEISE: Das Plugin registriert einen EnvironmentHelperTransformer, der jeden internen APP_URL-Lesezugriff abfängt und den Pfad-Anteil entfernt, bevor Shopware ihn verarbeitet. Damit sehen Live und Stage dieselbe Basis-URL. + + Beispiel: https://example.com/stage/ wird zu https://example.com + + Auf Live-Instanzen ohne Pfad ist der Transformer wirkungslos. + + Aenderungen greifen nach cache:clear. + + true + + + + baseUrl + + + + Leave empty for automatic detection – the transformer extracts scheme and host from APP_URL automatically. + + Set this only if auto-detection produces a wrong result, e.g. behind a reverse proxy that alters the Host header. + + Format: https://www.example.com (no trailing slash) + + + Leer lassen fuer automatische Erkennung – der Transformer extrahiert Scheme und Host automatisch aus APP_URL. + + Nur setzen, wenn die automatische Erkennung ein falsches Ergebnis liefert, z. B. hinter einem Reverse-Proxy, der den Host-Header veraendert. + + Format: https://www.example.com (kein abschliessender Slash) + + + + + + + diff --git a/src/Resources/config/plugin.png b/src/Resources/config/plugin.png new file mode 100644 index 0000000..f433073 Binary files /dev/null and b/src/Resources/config/plugin.png differ diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml new file mode 100644 index 0000000..51c0f1b --- /dev/null +++ b/src/Resources/config/services.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/Subscriber/SystemConfigSubscriber.php b/src/Subscriber/SystemConfigSubscriber.php new file mode 100644 index 0000000..d40c790 --- /dev/null +++ b/src/Subscriber/SystemConfigSubscriber.php @@ -0,0 +1,39 @@ + ['onKernelRequest', 512], + ]; + } + + public function onKernelRequest(RequestEvent $event): void + { + if ($this->configured) { + return; + } + + $this->configured = true; + + $enabled = (bool) ($this->systemConfigService->get('SumediaStage.config.normalizeAppUrl') ?? true); + $baseUrl = (string) ($this->systemConfigService->get('SumediaStage.config.baseUrl') ?? ''); + + AppUrlNormalizer::configure($enabled, $baseUrl ?: null); + } +} diff --git a/src/SumediaStage.php b/src/SumediaStage.php new file mode 100644 index 0000000..168262c --- /dev/null +++ b/src/SumediaStage.php @@ -0,0 +1,45 @@ +isAllowed()) { + return; + } + + EnvironmentHelper::addTransformer(AppUrlNormalizer::class); + } + + public function activate(ActivateContext $activateContext): void + { + if ((string) EnvironmentHelper::getVariable('APP_ENV') === 'prod') { + throw new \RuntimeException( + 'SumediaStage must not be activated in production (APP_ENV=prod). ' + . 'This plugin is intended for staging and local environments only.' + ); + } + } + + private function isAllowed(): bool + { + if ((string) EnvironmentHelper::getVariable('APP_ENV') === 'prod') { + return false; + } + + if ((string) EnvironmentHelper::getVariable('SUMEDIA_STAGE_ENABLED', '1') === '0') { + return false; + } + + return true; + } +}