initiatione

This commit is contained in:
Sven Ullmann 2026-03-16 18:06:14 +01:00
commit 627caccdaf
9 changed files with 346 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/vendor/
/node_modules/
/.idea/
/.vscode/
/var/
*.log

98
README.md Normal file
View file

@ -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.

23
composer.json Normal file
View file

@ -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/"
}
}
}

View file

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace SumediaStage\DevOps;
use Shopware\Core\DevOps\Environment\EnvironmentHelperTransformerData;
use Shopware\Core\DevOps\Environment\EnvironmentHelperTransformerInterface;
/**
* Normalizes APP_URL by stripping the path component.
*
* This allows a Shopware installation licensed for "https://example.com" to run
* under a subdirectory like "https://example.com/stage/" without triggering
* AppUrlChangeDetectedException or plugin license violations.
*
* Configuration is injected via configure() by the SystemConfigSubscriber.
*/
class AppUrlNormalizer implements EnvironmentHelperTransformerInterface
{
private static bool $enabled = true;
private static ?string $baseUrl = null;
public static function configure(bool $enabled, ?string $baseUrl): void
{
self::$enabled = $enabled;
self::$baseUrl = $baseUrl ?: null;
}
public static function transform(EnvironmentHelperTransformerData $data): void
{
if ($data->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);
}
}

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/SystemConfig/Schema/config.xsd">
<card>
<title>Sumedia Stage</title>
<title lang="de-DE">Sumedia Stage</title>
<input-field type="bool">
<name>normalizeAppUrl</name>
<label>Normalize APP_URL (strip subdirectory)</label>
<label lang="de-DE">APP_URL normalisieren (Unterverzeichnis entfernen)</label>
<helpText>
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.
</helpText>
<helpText lang="de-DE">
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.
</helpText>
<defaultValue>true</defaultValue>
</input-field>
<input-field type="text">
<name>baseUrl</name>
<label>Base URL override (optional)</label>
<label lang="de-DE">Basis-URL manuell setzen (optional)</label>
<helpText>
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)
</helpText>
<helpText lang="de-DE">
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)
</helpText>
<defaultValue></defaultValue>
</input-field>
</card>
</config>

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -0,0 +1,16 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="SumediaStage\Subscriber\SystemConfigSubscriber">
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService" />
<tag name="kernel.event_subscriber" />
</service>
</services>
</container>

View file

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace SumediaStage\Subscriber;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use SumediaStage\DevOps\AppUrlNormalizer;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SystemConfigSubscriber implements EventSubscriberInterface
{
private bool $configured = false;
public function __construct(private readonly SystemConfigService $systemConfigService)
{
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['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);
}
}

45
src/SumediaStage.php Normal file
View file

@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace SumediaStage;
use Shopware\Core\DevOps\Environment\EnvironmentHelper;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\ActivateContext;
use SumediaStage\DevOps\AppUrlNormalizer;
class SumediaStage extends Plugin
{
public function boot(): void
{
parent::boot();
if (!$this->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;
}
}