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

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;
}
}