custom/plugins/CbaxModulAnalytics/src/Subscriber/BackendSubscriber.php line 348

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Cbax\ModulAnalytics\Subscriber;
  3. use Shopware\Core\Framework\Uuid\Uuid;
  4. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  5. use Shopware\Core\Content\Product\Events\ProductSearchResultEvent;
  6. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  7. use Shopware\Storefront\Pagelet\Header\HeaderPageletLoadedEvent;
  8. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  9. use Shopware\Core\System\SystemConfig\SystemConfigService;
  10. use Shopware\Core\Defaults;
  11. use Shopware\Core\Checkout\Order\OrderDefinition;
  12. use Shopware\Core\Checkout\Order\OrderEvents;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  15. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  16. use Doctrine\DBAL\Connection;
  17. use Shopware\Core\Content\Category\Event\NavigationLoadedEvent;
  18. class BackendSubscriber implements EventSubscriberInterface
  19. {
  20.     const MODUL_NAME 'CbaxModulAnalytics';
  21.     /**
  22.      * @var SystemConfigService
  23.      */
  24.     private $systemConfigService;
  25.     /**
  26.      * @var
  27.      */
  28.     private $config null;
  29.     /**
  30.      * @var EntityRepositoryInterface
  31.      */
  32.     private $searchRepository;
  33.     /**
  34.      * @var EntityRepositoryInterface
  35.      */
  36.     private $orderRepository;
  37.     /**
  38.      * @var EntityRepositoryInterface
  39.      */
  40.     private $poolRepository;
  41.     /**
  42.      * @var Connection
  43.      */
  44.     private $connection;
  45.     const DEFAULT_DEVICES = [
  46.         'desktop',
  47.         'tablet',
  48.         'mobile'
  49.     ];
  50.     public function __construct(
  51.         SystemConfigService $systemConfigService,
  52.         EntityRepositoryInterface $searchRepository,
  53.         EntityRepositoryInterface $orderRepository,
  54.         EntityRepositoryInterface $poolRepository,
  55.         Connection $connection
  56.     )
  57.     {
  58.         $this->systemConfigService $systemConfigService;
  59.         $this->orderRepository $orderRepository;
  60.         $this->searchRepository $searchRepository;
  61.         $this->poolRepository $poolRepository;
  62.         $this->connection $connection;
  63.     }
  64.     public static function getSubscribedEvents(): array
  65.     {
  66.         return[
  67.             ProductSearchResultEvent::class => 'onProductSearch',
  68.             OrderEvents::ORDER_WRITTEN_EVENT => 'onOrderWritten',
  69.             ProductPageLoadedEvent::class => 'onProductPageLoaded',
  70.             HeaderPageletLoadedEvent::class => 'onHeaderPageletLoaded',
  71.             NavigationLoadedEvent::class => 'onNavigationLoaded'
  72.         ];
  73.     }
  74.     public function getDomainString($url) {
  75.         if (empty($url)) {
  76.             return '';
  77.         }
  78.         $domainStr str_replace(['http://''https://''www.'], ''$url);
  79.         $domainArr explode('/'$domainStr);
  80.         return $domainArr[0];
  81.     }
  82.     public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event)
  83.     {
  84.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  85.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  86.         if (empty($this->config['recordVisitors'])) return;
  87.         if (empty($_SERVER)) return;
  88.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  89.         $request $event->getRequest();
  90.         $referer $this->getDomainString($request->headers->get('referer'));
  91.         $host $this->getDomainString($request->getHttpHost());
  92.         $context $event->getContext();
  93.         $salesChannelIdBytes Uuid::fromHexToBytes($salesChannelId);
  94.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  95.         $deviceType $this->getDeviceType($httpUserAgent);
  96.         //$userOS = $this->getOS($httpUserAgent);
  97.         //$userBrowser = $this->getBrowser($httpUserAgent);
  98.         $visitorHash hash('md5'$request->getClientIp() . $httpUserAgent);
  99.         $date = (new \DateTimeImmutable())->format('Y-m-d');
  100.         $isNewVisitor false;
  101.         $createdAt = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  102.         /*
  103.          * Criteria für cbax_analytics_pool
  104.          */
  105.         $poolCriteria = new Criteria();
  106.         $poolCriteria
  107.             ->addFilter(new EqualsFilter('date'$date))
  108.             ->addFilter(new EqualsFilter('remoteAddress'$visitorHash))
  109.             ->addFilter(new EqualsFilter('salesChannelId'$salesChannelId));
  110.         $poolResult $this->poolRepository->search($poolCriteria$context)->first();
  111.         if (empty($poolResult)) {
  112.             $isNewVisitor true;
  113.             $randomId Uuid::randomBytes();
  114.             $this->connection->executeUpdate('
  115.                 INSERT IGNORE INTO `cbax_analytics_pool`
  116.                     (`id`, `date`, `remote_address`, `sales_channel_id`, `created_at`)
  117.                 VALUES
  118.                     (:id, :date, :remote_address, :sales_channel_id, :created_at);',
  119.                 [
  120.                     'id' => $randomId,
  121.                     'date' => $date,
  122.                     'remote_address' => $visitorHash,
  123.                     'sales_channel_id' => $salesChannelIdBytes,
  124.                     'created_at' => $createdAt
  125.                 ]
  126.             );
  127.         }
  128.         if ($isNewVisitor)
  129.         {
  130.             $randomId Uuid::randomBytes();
  131.             $this->connection->executeUpdate('
  132.                 INSERT INTO `cbax_analytics_visitors`
  133.                     (`id`, `sales_channel_id`, `date`,`page_impressions`, `unique_visits`, `device_type`, `created_at`)
  134.                 VALUES
  135.                     (:id, :sales_channel_id, :date, :page_impressions, :unique_visits, :device_type, :created_at)
  136.                     ON DUPLICATE KEY UPDATE page_impressions=page_impressions+1, unique_visits=unique_visits+1;',
  137.                 [
  138.                     'id' => $randomId,
  139.                     'sales_channel_id' => $salesChannelIdBytes,
  140.                     'date' => $date,
  141.                     'page_impressions' => 1,
  142.                     'unique_visits' => 1,
  143.                     'device_type' => $deviceType,
  144.                     'created_at' => $createdAt
  145.                 ]
  146.             );
  147.         } else {
  148.             $this->connection->executeUpdate('
  149.                 UPDATE `cbax_analytics_visitors` SET page_impressions=page_impressions+1
  150.                 WHERE `sales_channel_id`=? AND `date`=? AND `device_type`=?;',
  151.                 [$salesChannelIdBytes$date$deviceType]
  152.             );
  153.         }
  154.         if (!empty($referer) && $referer != $host)
  155.         {
  156.             $randomId Uuid::randomBytes();
  157.             $this->connection->executeUpdate('
  158.                 INSERT INTO `cbax_analytics_referer`
  159.                     (`id`, `date`,`referer`, `sales_channel_id`, `counted`, `device_type`, `created_at`)
  160.                 VALUES
  161.                     (:id, :date, :referer, :sales_channel_id, :counted, :device_type, :created_at)
  162.                     ON DUPLICATE KEY UPDATE counted=counted+1;',
  163.                 [
  164.                     'id' => $randomId,
  165.                     'date' => $date,
  166.                     'referer' => $referer,
  167.                     'sales_channel_id' => $salesChannelIdBytes,
  168.                     'counted' => 1,
  169.                     'device_type' => $deviceType,
  170.                     'created_at' => $createdAt
  171.                 ]
  172.             );
  173.         }
  174.     }
  175.     public function onNavigationLoaded(NavigationLoadedEvent $event)
  176.     {
  177.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  178.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  179.         if (empty($this->config['recordVisitors'])) return;
  180.         $category $event->getNavigation()->getActive();
  181.         $salesChannelIdBytes Uuid::fromHexToBytes($salesChannelId);
  182.         $categoryId $category->getId();
  183.         $date = (new \DateTime())->format(Defaults::STORAGE_DATE_FORMAT);
  184.         if (empty($_SERVER)) return;
  185.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  186.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  187.         $deviceType $this->getDeviceType($httpUserAgent);
  188.         $createdAt = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  189.         $randomId Uuid::randomBytes();
  190.         $this->connection->executeUpdate('
  191.                 INSERT INTO `cbax_analytics_category_impressions`
  192.                     (`id`, `category_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  193.                 VALUES
  194.                     (:id, :category_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  195.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  196.             [
  197.                 'id' => $randomId,
  198.                 'category_id' => Uuid::fromHexToBytes($categoryId),
  199.                 'sales_channel_id' => $salesChannelIdBytes,
  200.                 'date' => $date,
  201.                 'impressions' => 1,
  202.                 'device_type' => $deviceType,
  203.                 'created_at' => $createdAt
  204.             ]
  205.         );
  206.     }
  207.     public function onProductPageLoaded(ProductPageLoadedEvent $event)
  208.     {
  209.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  210.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  211.         if (empty($this->config['recordVisitors'])) return;
  212.         $page $event->getPage();
  213.         $salesChannelIdBytes Uuid::fromHexToBytes($salesChannelId);
  214.         $productId $page->getProduct()->getId();
  215.         $date = (new \DateTime())->format(Defaults::STORAGE_DATE_FORMAT);
  216.         if (empty($_SERVER)) return;
  217.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  218.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  219.         $deviceType $this->getDeviceType($httpUserAgent);
  220.         $createdAt = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  221.         $randomId Uuid::randomBytes();
  222.         $this->connection->executeUpdate('
  223.                 INSERT INTO `cbax_analytics_product_impressions`
  224.                     (`id`, `product_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  225.                 VALUES
  226.                     (:id, :product_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  227.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  228.             [
  229.                 'id' => $randomId,
  230.                 'product_id' => Uuid::fromHexToBytes($productId),
  231.                 'sales_channel_id' => $salesChannelIdBytes,
  232.                 'date' => $date,
  233.                 'impressions' => 1,
  234.                 'device_type' => $deviceType,
  235.                 'created_at' => $createdAt
  236.             ]
  237.         );
  238.         $manufacturer $page->getProduct()->getManufacturer();
  239.         if (empty($manufacturer)) return;
  240.         $manufacturerId $manufacturer->getId();
  241.         $randomId Uuid::randomBytes();
  242.         $this->connection->executeUpdate('
  243.                 INSERT INTO `cbax_analytics_manufacturer_impressions`
  244.                     (`id`, `manufacturer_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  245.                 VALUES
  246.                     (:id, :manufacturer_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  247.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  248.             [
  249.                 'id' => $randomId,
  250.                 'manufacturer_id' => Uuid::fromHexToBytes($manufacturerId),
  251.                 'sales_channel_id' => $salesChannelIdBytes,
  252.                 'date' => $date,
  253.                 'impressions' => 1,
  254.                 'device_type' => $deviceType,
  255.                 'created_at' => $createdAt
  256.             ]
  257.         );
  258.     }
  259.     public function onOrderWritten(EntityWrittenEvent $event)
  260.     {
  261.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config') ?? []);
  262.         if (empty($this->config['recordAdditionalOrderData'])) return;
  263.         if (empty($_SERVER)) return;
  264.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  265.         if ($event->getEntityName() !== OrderDefinition::ENTITY_NAME) return;
  266.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  267.         $context $event->getContext();
  268.         foreach ($event->getWriteResults() as $writeResult) {
  269.             if ($writeResult->getExistence() !== null && $writeResult->getExistence()->exists()) continue;
  270.             if ($writeResult->getOperation() !== 'insert') continue;
  271.             $payload $writeResult->getPayload();
  272.             if (empty($payload)) continue;
  273.             $customFields = !empty($payload['customFields']) ? $payload['customFields'] : [];
  274.             if (empty($payload['versionId'])) continue;
  275.             if ($payload['versionId'] !== Defaults::LIVE_VERSION) continue;
  276.             if (!empty($customFields) && !empty($customFields['cbaxExternalOrderOrdernumber'])) continue;
  277.             $customFields['cbaxStatistics'] = [
  278.                 'device' => $this->getDeviceType($httpUserAgent),
  279.                 'os' => $this->getOS($httpUserAgent),
  280.                 'browser' => $this->getBrowser($httpUserAgent)
  281.             ];
  282.             $data = [
  283.                 [
  284.                     'id' => $payload['id'],
  285.                     'customFields' => $customFields
  286.                 ]
  287.             ];
  288.             $this->orderRepository->update($data$context);
  289.         }
  290.     }
  291.     public function onProductSearch(ProductSearchResultEvent $event)
  292.     {
  293.         $salesChannelId $event->getSalesChannelContext()->getSalesChannel()->getId();
  294.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  295.         if (empty($this->config['recordSearch'])) return;
  296.         $requestUri $event->getRequest()->attributes->get('sw-original-request-uri');
  297.         if (empty($requestUri)) return;
  298.         if (str_starts_with($requestUri'/widgets')) return;
  299.         $searchUriArray explode('='$requestUri);
  300.         $searchTerm count($searchUriArray) > strtolower(urldecode ($searchUriArray[1])) : '';
  301.         if (empty($searchTerm)) return;
  302.         $results $event->getResult()->getTotal();
  303.         $context $event->getContext();
  304.         $this->searchRepository->create([
  305.             [
  306.                 'searchTerm' => $searchTerm,
  307.                 'results' => $results,
  308.                 'salesChannelId' => $salesChannelId
  309.             ]
  310.         ], $context);
  311.     }
  312.     private function getDeviceType($httpUserAgent)
  313.     {
  314.         $httpUserAgent = (string)$httpUserAgent;
  315.         if (!empty($_COOKIE) && !empty($_COOKIE['x-ua-device']))
  316.         {
  317.             $deviceType strtolower($_COOKIE['x-ua-device']);
  318.             if (in_array($deviceTypeself::DEFAULT_DEVICES))
  319.             {
  320.                 return $deviceType;
  321.             }
  322.         }
  323.         $os $this->getOS($httpUserAgent);
  324.         $mobileOS = ['Windows Phone 10','Windows Phone 8.1','Windows Phone 8','BlackBerry','Mobile'];
  325.         $tabletOS = ['Android','iOS'];
  326.         if (preg_match('/mobile|phone|ipod/i'$httpUserAgent) || in_array($os$mobileOS))
  327.         {
  328.             return 'mobile';
  329.         }
  330.         if (preg_match('/tablet|ipad/i'$httpUserAgent) || in_array($os$tabletOS))
  331.         {
  332.             return 'tablet';
  333.         }
  334.         return 'desktop';
  335.     }
  336.     private function getOS($httpUserAgent)
  337.     {
  338.         $httpUserAgent = (string)$httpUserAgent;
  339.         foreach (self::OS as $key => $value) {
  340.             if (preg_match($key$httpUserAgent)) {
  341.                 return $value;
  342.             }
  343.         }
  344.         return 'Not Detected';
  345.     }
  346.     private function getBrowser($httpUserAgent)
  347.     {
  348.         $httpUserAgent = (string)$httpUserAgent;
  349.         foreach (self::BROWSER as $key => $value) {
  350.             if (preg_match($key$httpUserAgent)) {
  351.                 return $value;
  352.             }
  353.         }
  354.         return 'Not Detected';
  355.     }
  356.     const OS = [
  357.         '/windows nt 11/i'      =>  'Windows 11',
  358.         '/windows nt 10/i'      =>  'Windows 10',
  359.         '/windows phone 10/i'   =>  'Windows Phone 10',
  360.         '/windows phone 8.1/i'  =>  'Windows Phone 8.1',
  361.         '/windows phone 8/i'    =>  'Windows Phone 8',
  362.         '/windows nt 6.3/i'     =>  'Windows 8.1',
  363.         '/windows nt 6.2/i'     =>  'Windows 8',
  364.         '/windows nt 6.1/i'     =>  'Windows 7',
  365.         '/windows nt 6.0/i'     =>  'Windows Vista',
  366.         '/windows nt 5.2/i'     =>  'Windows Server 2003/XP x64',
  367.         '/windows nt 5.1/i'     =>  'Windows XP',
  368.         '/windows xp/i'         =>  'Windows XP',
  369.         '/windows nt 5.0/i'     =>  'Windows 2000',
  370.         '/windows me/i'         =>  'Windows ME',
  371.         '/win98/i'              =>  'Windows 98',
  372.         '/win95/i'              =>  'Windows 95',
  373.         '/win16/i'              =>  'Windows 3.11',
  374.         '/macintosh|mac os x/i' =>  'Mac OS X',
  375.         '/mac_powerpc/i'        =>  'Mac OS 9',
  376.         '/iphone/i'             =>  'iOS',
  377.         '/ipod/i'               =>  'iOS',
  378.         '/ipad/i'               =>  'iOS',
  379.         '/android/i'            =>  'Android',
  380.         '/linux/i'              =>  'Linux',
  381.         '/ubuntu/i'             =>  'Ubuntu',
  382.         '/blackberry/i'         =>  'BlackBerry',
  383.         '/webos/i'              =>  'Mobile'
  384.     ];
  385.     const BROWSER = [
  386.         '/firefox/i'    =>  'Firefox',
  387.         '/msie/i'       =>  'Internet Explorer',
  388.         '/edge/i'       =>  'Edge',
  389.         '/edg/i'        =>  'Edge',
  390.         '/opera/i'      =>  'Opera',
  391.         '/chrome/i'     =>  'Chrome',
  392.         '/safari/i'     =>  'Safari',
  393.         '/mobile/i'     =>  'Handheld Browser',
  394.         '/netscape/i'   =>  'Netscape',
  395.         '/maxthon/i'    =>  'Maxthon',
  396.         '/konqueror/i'  =>  'Konqueror'
  397.     ];
  398. }