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

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