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

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