<?php
declare(strict_types=1);
namespace WnsSecurityComplianceSuite\Logging\Subscriber;
use Psr\Log\LoggerInterface;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
use Shopware\Core\PlatformRequest;
use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigChangedEvent;
use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use WnsSecurityComplianceSuite\Logging\DTO\Log;
use WnsSecurityComplianceSuite\Logging\DTO\LogSource;
use WnsSecurityComplianceSuite\Logging\Enum\EventTypes;
use WnsSecurityComplianceSuite\Logging\Provider\SourceInfoProvider;
use WnsSecurityComplianceSuite\Logging\Service\ActionLogger;
final class ConfigSubscriber implements EventSubscriberInterface
{
private const NULL_SALES_CHANNEL_PLACEHOLDER = 'NULL';
private RequestStack $requestStack;
private ActionLogger $actionLogger;
private SourceInfoProvider $sourceInfoProvider;
private LoggerInterface $wscsLogger;
private SystemConfigService $systemConfigService;
/**
* @var array<string, mixed>
*/
private array $previousConfig = [];
/**
* @var array<string, mixed>
*/
private array $currentBatch = [];
public function __construct(
RequestStack $requestStack,
ActionLogger $actionLogger,
SourceInfoProvider $sourceInfoProvider,
LoggerInterface $wscsLogger,
SystemConfigService $systemConfigService
) {
$this->requestStack = $requestStack;
$this->actionLogger = $actionLogger;
$this->sourceInfoProvider = $sourceInfoProvider;
$this->wscsLogger = $wscsLogger;
$this->systemConfigService = $systemConfigService;
}
public static function getSubscribedEvents()
{
return [
// TODO: use SystemConfigMultipleChangedEvent in Shopware 6.6.8+
BeforeSystemConfigChangedEvent::class => 'fetchPreviousConfig',
SystemConfigChangedEvent::class => 'onConfigChanged',
BeforeSendResponseEvent::class => 'onConfigChangeFinished',
];
}
public function fetchPreviousConfig(BeforeSystemConfigChangedEvent $event): void
{
$salesChannelKey = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
if (isset($this->previousConfig[$salesChannelKey])) {
return;
}
$config = $this->systemConfigService->all($event->getSalesChannelId());
$this->previousConfig[$salesChannelKey] = $config;
}
public function onConfigChangeFinished(BeforeSendResponseEvent $event): void
{
foreach ($this->currentBatch as $salesChannelId => $config) {
$this->logConfigChange($config, $salesChannelId === self::NULL_SALES_CHANNEL_PLACEHOLDER ? null : $salesChannelId);
}
$this->currentBatch = [];
}
public function onConfigChanged(SystemConfigChangedEvent $event): void
{
$salesChannelId = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
if ($this->hasConfigChanged($event) === false) {
return;
}
$this->currentBatch[$salesChannelId][$event->getKey()] = $event->getValue();
}
public function logConfigChange(array $config, ?string $salesChannelId): void
{
/** @var Request|null $request */
$request = $this->requestStack->getMainRequest();
$detail = [
'config' => $config,
'salesChannelId' => $salesChannelId,
];
try {
/** @var Context $context */
$context = $request === null ? Context::createDefaultContext() : $request->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
$source = $this->sourceInfoProvider->getSourceDTO($context->getSource());
} catch (\Exception $e) {
$this->wscsLogger->error('Error getting scope: ' . $e->getMessage(), ['request' => $request]);
}
$log = new Log(
EventTypes::CONFIG_CHANGED,
$source ?? LogSource::createEmpty(),
$detail,
new \DateTimeImmutable()
);
$this->actionLogger->log($log, $salesChannelId);
}
public function hasConfigChanged(SystemConfigChangedEvent $event): bool
{
$salesChannelKey = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
$configKey = $event->getKey();
try {
$value = array_reduce(
explode('.', $configKey),
function ($arr, $key) use ($configKey) {
if (!\is_array($arr) || !\array_key_exists($key, $arr)) {
throw new \InvalidArgumentException(\sprintf('Path "%s" not found in configuration', $configKey));
}
return $arr[$key];
},
$this->previousConfig[$salesChannelKey]
);
return $value !== $event->getValue();
} catch (\InvalidArgumentException $e) {
// If key wasn't found, it is new
return true;
}
}
}