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

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