vendor/wns/security-compliance-suite/src/Logging/Subscriber/ConfigSubscriber.php line 71

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace WnsSecurityComplianceSuite\Logging\Subscriber;
  4. use Psr\Log\LoggerInterface;
  5. use Shopware\Core\Framework\Context;
  6. use Shopware\Core\Framework\Event\BeforeSendResponseEvent;
  7. use Shopware\Core\PlatformRequest;
  8. use Shopware\Core\System\SystemConfig\Event\BeforeSystemConfigChangedEvent;
  9. use Shopware\Core\System\SystemConfig\Event\SystemConfigChangedEvent;
  10. use Shopware\Core\System\SystemConfig\SystemConfigService;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. use WnsSecurityComplianceSuite\Logging\DTO\Log;
  15. use WnsSecurityComplianceSuite\Logging\DTO\LogSource;
  16. use WnsSecurityComplianceSuite\Logging\Enum\EventTypes;
  17. use WnsSecurityComplianceSuite\Logging\Provider\SourceInfoProvider;
  18. use WnsSecurityComplianceSuite\Logging\Service\ActionLogger;
  19. final class ConfigSubscriber implements EventSubscriberInterface
  20. {
  21. private const NULL_SALES_CHANNEL_PLACEHOLDER = 'NULL';
  22. private RequestStack $requestStack;
  23. private ActionLogger $actionLogger;
  24. private SourceInfoProvider $sourceInfoProvider;
  25. private LoggerInterface $wscsLogger;
  26. private SystemConfigService $systemConfigService;
  27. /**
  28. * @var array<string, mixed>
  29. */
  30. private array $previousConfig = [];
  31. /**
  32. * @var array<string, mixed>
  33. */
  34. private array $currentBatch = [];
  35. public function __construct(
  36. RequestStack $requestStack,
  37. ActionLogger $actionLogger,
  38. SourceInfoProvider $sourceInfoProvider,
  39. LoggerInterface $wscsLogger,
  40. SystemConfigService $systemConfigService
  41. ) {
  42. $this->requestStack = $requestStack;
  43. $this->actionLogger = $actionLogger;
  44. $this->sourceInfoProvider = $sourceInfoProvider;
  45. $this->wscsLogger = $wscsLogger;
  46. $this->systemConfigService = $systemConfigService;
  47. }
  48. public static function getSubscribedEvents()
  49. {
  50. return [
  51. // TODO: use SystemConfigMultipleChangedEvent in Shopware 6.6.8+
  52. BeforeSystemConfigChangedEvent::class => 'fetchPreviousConfig',
  53. SystemConfigChangedEvent::class => 'onConfigChanged',
  54. BeforeSendResponseEvent::class => 'onConfigChangeFinished',
  55. ];
  56. }
  57. public function fetchPreviousConfig(BeforeSystemConfigChangedEvent $event): void
  58. {
  59. $salesChannelKey = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
  60. if (isset($this->previousConfig[$salesChannelKey])) {
  61. return;
  62. }
  63. $config = $this->systemConfigService->all($event->getSalesChannelId());
  64. $this->previousConfig[$salesChannelKey] = $config;
  65. }
  66. public function onConfigChangeFinished(BeforeSendResponseEvent $event): void
  67. {
  68. foreach ($this->currentBatch as $salesChannelId => $config) {
  69. $this->logConfigChange($config, $salesChannelId === self::NULL_SALES_CHANNEL_PLACEHOLDER ? null : $salesChannelId);
  70. }
  71. $this->currentBatch = [];
  72. }
  73. public function onConfigChanged(SystemConfigChangedEvent $event): void
  74. {
  75. $salesChannelId = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
  76. if ($this->hasConfigChanged($event) === false) {
  77. return;
  78. }
  79. $this->currentBatch[$salesChannelId][$event->getKey()] = $event->getValue();
  80. }
  81. public function logConfigChange(array $config, ?string $salesChannelId): void
  82. {
  83. /** @var Request|null $request */
  84. $request = $this->requestStack->getMainRequest();
  85. $detail = [
  86. 'config' => $config,
  87. 'salesChannelId' => $salesChannelId,
  88. ];
  89. try {
  90. /** @var Context $context */
  91. $context = $request === null ? Context::createDefaultContext() : $request->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
  92. $source = $this->sourceInfoProvider->getSourceDTO($context->getSource());
  93. } catch (\Exception $e) {
  94. $this->wscsLogger->error('Error getting scope: ' . $e->getMessage(), ['request' => $request]);
  95. }
  96. $log = new Log(
  97. EventTypes::CONFIG_CHANGED,
  98. $source ?? LogSource::createEmpty(),
  99. $detail,
  100. new \DateTimeImmutable()
  101. );
  102. $this->actionLogger->log($log, $salesChannelId);
  103. }
  104. public function hasConfigChanged(SystemConfigChangedEvent $event): bool
  105. {
  106. $salesChannelKey = $event->getSalesChannelId() ?? self::NULL_SALES_CHANNEL_PLACEHOLDER;
  107. $configKey = $event->getKey();
  108. try {
  109. $value = array_reduce(
  110. explode('.', $configKey),
  111. function ($arr, $key) use ($configKey) {
  112. if (!\is_array($arr) || !\array_key_exists($key, $arr)) {
  113. throw new \InvalidArgumentException(\sprintf('Path "%s" not found in configuration', $configKey));
  114. }
  115. return $arr[$key];
  116. },
  117. $this->previousConfig[$salesChannelKey]
  118. );
  119. return $value !== $event->getValue();
  120. } catch (\InvalidArgumentException $e) {
  121. // If key wasn't found, it is new
  122. return true;
  123. }
  124. }
  125. }