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

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.         {
  123.             $randomId Uuid::randomBytes();
  124.             try {
  125.                 $this->connection->executeUpdate('
  126.                 INSERT INTO `cbax_analytics_visitors`
  127.                     (`id`, `sales_channel_id`, `date`,`page_impressions`, `unique_visits`, `device_type`, `created_at`)
  128.                 VALUES
  129.                     (:id, :sales_channel_id, :date, :page_impressions, :unique_visits, :device_type, :created_at)
  130.                     ON DUPLICATE KEY UPDATE page_impressions=page_impressions+1, unique_visits=unique_visits+1;',
  131.                     [
  132.                         'id' => $randomId,
  133.                         'sales_channel_id' => $salesChannelIdBytes,
  134.                         'date' => $date,
  135.                         'page_impressions' => 1,
  136.                         'unique_visits' => 1,
  137.                         'device_type' => $deviceType,
  138.                         'created_at' => $createdAt
  139.                     ]
  140.                 );
  141.             } catch(\Exception $e) {
  142.             }
  143.         } else {
  144.             try {
  145.                 $this->connection->executeUpdate('
  146.                 UPDATE `cbax_analytics_visitors` SET page_impressions=page_impressions+1
  147.                 WHERE `sales_channel_id`=? AND `date`=? AND `device_type`=?;',
  148.                     [$salesChannelIdBytes$date$deviceType]
  149.                 );
  150.             } catch(\Exception $e) {
  151.             }
  152.         }
  153.         if (!empty($referer) && $referer != $host)
  154.         {
  155.             $randomId Uuid::randomBytes();
  156.             try {
  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.             } catch(\Exception $e) {
  174.             }
  175.         }
  176.     }
  177.     public function onNavigationPageLoaded(NavigationPageLoadedEvent $event)
  178.     {
  179.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  180.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  181.         if (empty($this->config['recordVisitors'])) return;
  182.         $categoryId $event->getPage()->getNavigationId();
  183.         if (empty($categoryId)) return;
  184.         $salesChannelIdBytes Uuid::fromHexToBytes($salesChannelId);
  185.         $date = (new \DateTime())->format(Defaults::STORAGE_DATE_FORMAT);
  186.         if (empty($_SERVER)) return;
  187.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  188.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  189.         $deviceType $this->getDeviceType($httpUserAgent);
  190.         $createdAt = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  191.         $randomId Uuid::randomBytes();
  192.         try {
  193.             $this->connection->executeUpdate('
  194.                 INSERT INTO `cbax_analytics_category_impressions`
  195.                     (`id`, `category_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  196.                 VALUES
  197.                     (:id, :category_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  198.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  199.                 [
  200.                     'id' => $randomId,
  201.                     'category_id' => Uuid::fromHexToBytes($categoryId),
  202.                     'sales_channel_id' => $salesChannelIdBytes,
  203.                     'date' => $date,
  204.                     'impressions' => 1,
  205.                     'device_type' => $deviceType,
  206.                     'created_at' => $createdAt
  207.                 ]
  208.             );
  209.         } catch(\Exception $e) {
  210.         }
  211.     }
  212.     public function onProductPageLoaded(ProductPageLoadedEvent $event)
  213.     {
  214.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  215.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  216.         if (empty($this->config['recordVisitors'])) return;
  217.         $page $event->getPage();
  218.         if (empty($page->getProduct())) return;
  219.         $salesChannelIdBytes Uuid::fromHexToBytes($salesChannelId);
  220.         $productId $page->getProduct()->getId();
  221.         $date = (new \DateTime())->format(Defaults::STORAGE_DATE_FORMAT);
  222.         if (empty($_SERVER)) return;
  223.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  224.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  225.         $deviceType $this->getDeviceType($httpUserAgent);
  226.         $createdAt = (new \DateTimeImmutable())->format(Defaults::STORAGE_DATE_TIME_FORMAT);
  227.         $randomId Uuid::randomBytes();
  228.         try {
  229.             $this->connection->executeUpdate('
  230.                 INSERT INTO `cbax_analytics_product_impressions`
  231.                     (`id`, `product_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  232.                 VALUES
  233.                     (:id, :product_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  234.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  235.                 [
  236.                     'id' => $randomId,
  237.                     'product_id' => Uuid::fromHexToBytes($productId),
  238.                     'sales_channel_id' => $salesChannelIdBytes,
  239.                     'date' => $date,
  240.                     'impressions' => 1,
  241.                     'device_type' => $deviceType,
  242.                     'created_at' => $createdAt
  243.                 ]
  244.             );
  245.         } catch(\Exception $e) {
  246.         }
  247.         $manufacturer $page->getProduct()->getManufacturer();
  248.         if (empty($manufacturer)) return;
  249.         $manufacturerId $manufacturer->getId();
  250.         $randomId Uuid::randomBytes();
  251.         try {
  252.             $this->connection->executeUpdate('
  253.                 INSERT INTO `cbax_analytics_manufacturer_impressions`
  254.                     (`id`, `manufacturer_id`, `sales_channel_id`, `date`, `impressions`, `device_type`, `created_at`)
  255.                 VALUES
  256.                     (:id, :manufacturer_id, :sales_channel_id, :date, :impressions, :device_type, :created_at)
  257.                     ON DUPLICATE KEY UPDATE impressions=impressions+1;',
  258.                 [
  259.                     'id' => $randomId,
  260.                     'manufacturer_id' => Uuid::fromHexToBytes($manufacturerId),
  261.                     'sales_channel_id' => $salesChannelIdBytes,
  262.                     'date' => $date,
  263.                     'impressions' => 1,
  264.                     'device_type' => $deviceType,
  265.                     'created_at' => $createdAt
  266.                 ]
  267.             );
  268.         } catch(\Exception $e) {
  269.         }
  270.     }
  271.     public function onOrderFinished(CheckoutFinishPageLoadedEvent $event)
  272.     {
  273.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelId();
  274.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  275.         if (empty($this->config['recordAdditionalOrderData'])) return;
  276.         if (empty($_SERVER)) return;
  277.         if (empty($_SERVER['HTTP_USER_AGENT'])) return;
  278.         $order $event->getPage()->getOrder();
  279.         if (empty($order)) return;
  280.         $httpUserAgent $_SERVER['HTTP_USER_AGENT'];
  281.         $customFields $order->getCustomFields() ?? [];
  282.         $context $event->getContext();
  283.         $customFields['cbaxStatistics'] = [
  284.             'device' => $this->getDeviceType($httpUserAgent),
  285.             'os' => $this->getOS($httpUserAgent),
  286.             'browser' => $this->getBrowser($httpUserAgent)
  287.         ];
  288.         $data = [
  289.             [
  290.                 'id' => $order->getId(),
  291.                 'customFields' => $customFields
  292.             ]
  293.         ];
  294.         $this->orderRepository->update($data$context);
  295.     }
  296.     public function onProductSearch(ProductSearchResultEvent $event)
  297.     {
  298.         $salesChannelId $event->getSalesChannelContext()->getSalesChannelID();
  299.         $this->config $this->config ?? ($this->systemConfigService->get(self::MODUL_NAME '.config'$salesChannelId) ?? []);
  300.         if (empty($this->config['recordSearch'])) return;
  301.         $requestUri $event->getRequest()->attributes->get('sw-original-request-uri');
  302.         if (empty($requestUri)) return;
  303.         if (str_starts_with($requestUri'/widgets')) return;
  304.         $searchUriArray explode('='$requestUri);
  305.         $searchTerm count($searchUriArray) > strtolower(urldecode ($searchUriArray[1])) : '';
  306.         if (empty($searchTerm)) return;
  307.         $results $event->getResult()->getTotal();
  308.         $context $event->getContext();
  309.         $this->searchRepository->create([
  310.             [
  311.                 'searchTerm' => $searchTerm,
  312.                 'results' => $results,
  313.                 'salesChannelId' => $salesChannelId
  314.             ]
  315.         ], $context);
  316.     }
  317.     public function getDomainString($url) {
  318.         if (empty($url)) {
  319.             return '';
  320.         }
  321.         $domainStr str_replace(['http://''https://''www.'], ''$url);
  322.         $domainArr explode('/'$domainStr);
  323.         return $domainArr[0];
  324.     }
  325.     private function getDeviceType($httpUserAgent)
  326.     {
  327.         $httpUserAgent = (string)$httpUserAgent;
  328.         if (!empty($_COOKIE) && !empty($_COOKIE['x-ua-device']))
  329.         {
  330.             $deviceType strtolower($_COOKIE['x-ua-device']);
  331.             if (in_array($deviceTypeself::DEFAULT_DEVICES))
  332.             {
  333.                 return $deviceType;
  334.             }
  335.         }
  336.         $os $this->getOS($httpUserAgent);
  337.         $mobileOS = ['Windows Phone 10','Windows Phone 8.1','Windows Phone 8','BlackBerry','Mobile'];
  338.         $tabletOS = ['Android','iOS'];
  339.         if (preg_match('/mobile|phone|ipod/i'$httpUserAgent) || in_array($os$mobileOS))
  340.         {
  341.             return 'mobile';
  342.         }
  343.         if (preg_match('/tablet|ipad/i'$httpUserAgent) || in_array($os$tabletOS))
  344.         {
  345.             return 'tablet';
  346.         }
  347.         return 'desktop';
  348.     }
  349.     private function getOS($httpUserAgent)
  350.     {
  351.         $httpUserAgent = (string)$httpUserAgent;
  352.         foreach (self::OS as $key => $value) {
  353.             if (preg_match($key$httpUserAgent)) {
  354.                 return $value;
  355.             }
  356.         }
  357.         return 'Not Detected';
  358.     }
  359.     private function getBrowser($httpUserAgent)
  360.     {
  361.         $httpUserAgent = (string)$httpUserAgent;
  362.         foreach (self::BROWSER as $key => $value) {
  363.             if (preg_match($key$httpUserAgent)) {
  364.                 return $value;
  365.             }
  366.         }
  367.         return 'Not Detected';
  368.     }
  369.     const OS = [
  370.         '/windows nt 11/i'      =>  'Windows 11',
  371.         '/windows nt 10/i'      =>  'Windows 10',
  372.         '/windows phone 10/i'   =>  'Windows Phone 10',
  373.         '/windows phone 8.1/i'  =>  'Windows Phone 8.1',
  374.         '/windows phone 8/i'    =>  'Windows Phone 8',
  375.         '/windows nt 6.3/i'     =>  'Windows 8.1',
  376.         '/windows nt 6.2/i'     =>  'Windows 8',
  377.         '/windows nt 6.1/i'     =>  'Windows 7',
  378.         '/windows nt 6.0/i'     =>  'Windows Vista',
  379.         '/windows nt 5.2/i'     =>  'Windows Server 2003/XP x64',
  380.         '/windows nt 5.1/i'     =>  'Windows XP',
  381.         '/windows xp/i'         =>  'Windows XP',
  382.         '/windows nt 5.0/i'     =>  'Windows 2000',
  383.         '/windows me/i'         =>  'Windows ME',
  384.         '/win98/i'              =>  'Windows 98',
  385.         '/win95/i'              =>  'Windows 95',
  386.         '/win16/i'              =>  'Windows 3.11',
  387.         '/macintosh|mac os x/i' =>  'Mac OS X',
  388.         '/mac_powerpc/i'        =>  'Mac OS 9',
  389.         '/iphone/i'             =>  'iOS',
  390.         '/ipod/i'               =>  'iOS',
  391.         '/ipad/i'               =>  'iOS',
  392.         '/android/i'            =>  'Android',
  393.         '/linux/i'              =>  'Linux',
  394.         '/ubuntu/i'             =>  'Ubuntu',
  395.         '/blackberry/i'         =>  'BlackBerry',
  396.         '/webos/i'              =>  'Mobile'
  397.     ];
  398.     const BROWSER = [
  399.         '/firefox/i'    =>  'Firefox',
  400.         '/msie/i'       =>  'Internet Explorer',
  401.         '/edge/i'       =>  'Edge',
  402.         '/edg/i'        =>  'Edge',
  403.         '/opera/i'      =>  'Opera',
  404.         '/chrome/i'     =>  'Chrome',
  405.         '/safari/i'     =>  'Safari',
  406.         '/mobile/i'     =>  'Handheld Browser',
  407.         '/netscape/i'   =>  'Netscape',
  408.         '/maxthon/i'    =>  'Maxthon',
  409.         '/konqueror/i'  =>  'Konqueror'
  410.     ];
  411. }