PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/main/lib/analytics/catalog.php

https://gitlab.com/alexprowars/bitrix
PHP | 621 lines | 467 code | 95 blank | 59 comment | 82 complexity | 087fcd74aeb68ed06183647211f3e3c9 MD5 | raw file
  1. <?php
  2. /**
  3. * Bitrix Framework
  4. * @package bitrix
  5. * @subpackage main
  6. * @copyright 2001-2014 Bitrix
  7. */
  8. namespace Bitrix\Main\Analytics;
  9. use Bitrix\Catalog\CatalogViewedProductTable;
  10. use Bitrix\Main\Application;
  11. use Bitrix\Main\Config\Option;
  12. use Bitrix\Main\Context;
  13. use Bitrix\Main\Event;
  14. use Bitrix\Main\Loader;
  15. use Bitrix\Main\UserTable;
  16. use Bitrix\Sale\BasketItem;
  17. use Bitrix\Sale\Order;
  18. use Bitrix\Sale\OrderTable;
  19. if (!Loader::includeModule('catalog'))
  20. return;
  21. /**
  22. * @package bitrix
  23. * @subpackage main
  24. */
  25. class Catalog
  26. {
  27. protected static $cookieLogName = 'RCM_PRODUCT_LOG';
  28. // basket (sale:OnSaleBasketItemSaved)
  29. public static function catchCatalogBasket(Event $event)
  30. {
  31. $isNew = $event->getParameter("IS_NEW");
  32. // new items only
  33. if (!$isNew)
  34. {
  35. return;
  36. }
  37. // exclude empty cookie
  38. if (!static::getBxUserId())
  39. {
  40. return;
  41. }
  42. // alter b_sale_basket - add recommendation, update it here
  43. if (!static::isOn())
  44. {
  45. return;
  46. }
  47. /** @var BasketItem $basketItem */
  48. $basketItem = $event->getParameter("ENTITY");
  49. // get product id by offer id
  50. $iblockId = 0;
  51. $realProductId = $basketItem->getProductId();
  52. $isCatalog = $basketItem->getField('MODULE') == 'catalog';
  53. if ($isCatalog)
  54. {
  55. $productInfo = \CCatalogSKU::GetProductInfo($realProductId);
  56. if (!empty($productInfo['ID']))
  57. {
  58. $realProductId = $productInfo['ID'];
  59. $iblockId = $productInfo['IBLOCK_ID'];
  60. }
  61. else
  62. {
  63. // get iblock id
  64. $element = \Bitrix\Iblock\ElementTable::getRow(array(
  65. 'select' => array('IBLOCK_ID'),
  66. 'filter' => array('=ID' => $realProductId)
  67. ));
  68. if (!empty($element))
  69. {
  70. $iblockId = $element['IBLOCK_ID'];
  71. }
  72. }
  73. }
  74. // select site user id & recommendation id
  75. $siteUserId = 0;
  76. $recommendationId = '';
  77. // first, try to find in cookies
  78. $recommendationCookie = Context::getCurrent()->getRequest()->getCookie(static::getCookieLogName());
  79. if (!empty($recommendationCookie))
  80. {
  81. $recommendations = static::decodeProductLog($recommendationCookie);
  82. if (is_array($recommendations) && isset($recommendations[$realProductId]))
  83. {
  84. $recommendationId = $recommendations[$realProductId][0];
  85. }
  86. }
  87. if (empty($recommendationId) && $isCatalog)
  88. {
  89. // ok then, lets see in views history
  90. //if(\COption::GetOptionString("sale", "encode_fuser_id", "N") == "Y")
  91. if (!is_numeric($basketItem->getFUserId()))
  92. {
  93. $filter = array('CODE' => $basketItem->getFUserId());
  94. }
  95. else
  96. {
  97. $filter = array('ID' => $basketItem->getFUserId());
  98. }
  99. $result = \CSaleUser::getList($filter);
  100. if (!empty($result))
  101. {
  102. $siteUserId = $result['USER_ID'];
  103. // select recommendation id
  104. $fuser = $result['ID'];
  105. $viewResult = CatalogViewedProductTable::getList(array(
  106. 'select' => array('RECOMMENDATION'),
  107. 'filter' => array(
  108. '=FUSER_ID' => $fuser,
  109. '=PRODUCT_ID' => $basketItem->getProductId()
  110. ),
  111. 'order' => array('DATE_VISIT' => 'DESC')
  112. ))->fetch();
  113. if (!empty($viewResult['RECOMMENDATION']))
  114. {
  115. $recommendationId = $viewResult['RECOMMENDATION'];
  116. }
  117. }
  118. }
  119. // prepare data
  120. $data = array(
  121. 'product_id' => $realProductId,
  122. 'iblock_id' => $iblockId,
  123. 'user_id' => $siteUserId,
  124. 'bx_user_id' => static::getBxUserId(),
  125. 'domain' => Context::getCurrent()->getServer()->getHttpHost(),
  126. 'recommendation' => $recommendationId,
  127. 'date' => date(DATE_ISO8601)
  128. );
  129. // debug info
  130. global $USER;
  131. $data['real_user_id'] = $USER->getId() ?: 0;
  132. $data['is_admin'] = (int) $USER->IsAdmin();
  133. $data['admin_section'] = (int) (defined('ADMIN_SECTION') && ADMIN_SECTION);
  134. $data['admin_panel'] = (int) \CTopPanel::shouldShowPanel();
  135. // try to guess unnatural baskets
  136. $data['artificial_basket'] = (int) (
  137. ($data['user_id'] > 0 && $data['user_id'] != $data['real_user_id'])
  138. || $data['is_admin'] || $data['admin_section'] || $data['admin_panel']
  139. );
  140. // save
  141. CounterDataTable::add(array(
  142. 'TYPE' => 'basket',
  143. 'DATA' => $data
  144. ));
  145. // update basket with recommendation id
  146. if (!empty($recommendationId))
  147. {
  148. $conn = Application::getConnection();
  149. $helper = $conn->getSqlHelper();
  150. $conn->query(
  151. "UPDATE ".$helper->quote('b_sale_basket')
  152. ." SET RECOMMENDATION='".$helper->forSql($recommendationId)."' WHERE ID=".(int) $basketItem->getId()
  153. );
  154. }
  155. }
  156. // order detailed info (sale:OnSaleOrderSaved)
  157. public static function catchCatalogOrder(Event $event)
  158. {
  159. if (!static::isOn())
  160. {
  161. return;
  162. }
  163. $isNew = $event->getParameter("IS_NEW");
  164. if (!$isNew)
  165. {
  166. // only new orders
  167. return;
  168. }
  169. /** @var Order $orderItem */
  170. $orderItem = $event->getParameter("ENTITY");
  171. $data = static::getOrderInfo($orderItem->getId());
  172. if (empty($data['products']))
  173. {
  174. return;
  175. }
  176. // add bxuid
  177. $data['bx_user_id'] = static::getBxUserId();
  178. if (empty($data['bx_user_id']) && !empty($data['user_id']))
  179. {
  180. $orderUser = UserTable::getRow(array(
  181. 'select' => array('BX_USER_ID'),
  182. 'filter' => array('=ID' => $data['user_id'])
  183. ));
  184. if (!empty($orderUser) && !empty($orderUser['BX_USER_ID']))
  185. {
  186. $data['bx_user_id'] = $orderUser['BX_USER_ID'];
  187. }
  188. }
  189. // add general info
  190. $data['paid'] = '0';
  191. $data['domain'] = Context::getCurrent()->getServer()->getHttpHost();
  192. $data['date'] = date(DATE_ISO8601);
  193. // add debug info
  194. global $USER;
  195. $data['real_user_id'] = $USER->getId() ?: 0;
  196. $data['cookie_size'] = count($_COOKIE);
  197. $data['is_admin'] = (int) $USER->IsAdmin();
  198. $data['admin_section'] = (int) (defined('ADMIN_SECTION') && ADMIN_SECTION);
  199. $data['admin_panel'] = (int) \CTopPanel::shouldShowPanel();
  200. // try to guess unnatural orders
  201. $data['artificial_order'] = (int) (
  202. ($data['user_id'] != $data['real_user_id']) || !$data['cookie_size']
  203. || $data['is_admin'] || $data['admin_section'] || $data['admin_panel']
  204. );
  205. CounterDataTable::add(array(
  206. 'TYPE' => 'order',
  207. 'DATA' => $data
  208. ));
  209. // set bxuid to the order
  210. if (!empty($data['bx_user_id']))
  211. {
  212. // if sale version is fresh enough
  213. if (OrderTable::getEntity()->hasField('BX_USER_ID'))
  214. {
  215. OrderTable::update($data['order_id'], array('BX_USER_ID' => $data['bx_user_id']));
  216. }
  217. }
  218. }
  219. // order payment (sale:OnSaleOrderPaid)
  220. public static function catchCatalogOrderPayment(Event $event)
  221. {
  222. if (!static::isOn())
  223. {
  224. return;
  225. }
  226. /** @var Order $orderItem */
  227. $orderItem = $event->getParameter("ENTITY");
  228. $data = static::getOrderInfo($orderItem->getId());
  229. if (empty($data['products']))
  230. {
  231. return;
  232. }
  233. // add bxuid
  234. $data['bx_user_id'] = static::getBxUserId();
  235. if (empty($data['bx_user_id']) && OrderTable::getEntity()->hasField('BX_USER_ID'))
  236. {
  237. $order = OrderTable::getRow(array(
  238. 'select' => array('BX_USER_ID'),
  239. 'filter' => array('=ID' => $orderItem->getId())
  240. ));
  241. if (!empty($order) && !empty($order['BX_USER_ID']))
  242. {
  243. $data['bx_user_id'] = $order['BX_USER_ID'];
  244. }
  245. }
  246. // add general info
  247. $data['paid'] = '1';
  248. $data['domain'] = Context::getCurrent()->getServer()->getHttpHost();
  249. $data['date'] = date(DATE_ISO8601);
  250. CounterDataTable::add(array(
  251. 'TYPE' => 'order_pay',
  252. 'DATA' => $data
  253. ));
  254. }
  255. public static function getOrderInfo($orderId)
  256. {
  257. // order itself
  258. $order = \CSaleOrder::getById($orderId);
  259. // buyer info
  260. $siteUserId = $order['USER_ID'];
  261. $phone = '';
  262. $phone256 = '';
  263. $phone256_e164 = '';
  264. $email = '';
  265. $email256 = '';
  266. $result = \CSaleOrderPropsValue::GetList(array(), array("ORDER_ID" => $orderId));
  267. while ($row = $result->fetch())
  268. {
  269. if (empty($phone) && mb_stripos($row['CODE'], 'PHONE') !== false)
  270. {
  271. $stPhone = static::normalizePhoneNumber($row['VALUE']);
  272. if (!empty($stPhone))
  273. {
  274. $phone = sha1($stPhone);
  275. $phone256 = hash('sha256', $stPhone);
  276. $phone256_e164 = hash('sha256', '+'.$stPhone);
  277. }
  278. }
  279. if (empty($email) && mb_stripos($row['CODE'], 'EMAIL') !== false)
  280. {
  281. if (!empty($row['VALUE']))
  282. {
  283. $email = sha1($row['VALUE']);
  284. $email256 = hash('sha256', mb_strtolower(trim($row['VALUE'])));
  285. }
  286. }
  287. }
  288. // products info
  289. $products = array();
  290. $result = \CSaleBasket::getList(
  291. array(), $arFilter = array('ORDER_ID' => $orderId), false, false,
  292. array('PRODUCT_ID', 'RECOMMENDATION', 'QUANTITY', 'PRICE', 'CURRENCY', 'MODULE')
  293. );
  294. while ($row = $result->fetch())
  295. {
  296. $realProductId = $row['PRODUCT_ID'];
  297. $iblockId = 0;
  298. // get iblock id for catalog products
  299. if ($row['MODULE'] == 'catalog')
  300. {
  301. $productInfo = \CCatalogSKU::GetProductInfo($row['PRODUCT_ID']);
  302. if (!empty($productInfo['ID']))
  303. {
  304. $realProductId = $productInfo['ID'];
  305. $iblockId = $productInfo['IBLOCK_ID'];
  306. }
  307. else
  308. {
  309. $realProductId = $row['PRODUCT_ID'];
  310. // get iblock id
  311. $element = \Bitrix\Iblock\ElementTable::getRow(array(
  312. 'select' => array('IBLOCK_ID'),
  313. 'filter' => array('=ID' => $realProductId)
  314. ));
  315. if (!empty($element))
  316. {
  317. $iblockId = $element['IBLOCK_ID'];
  318. }
  319. }
  320. }
  321. $products[] = array(
  322. 'product_id' => $realProductId,
  323. 'iblock_id' => $iblockId,
  324. 'quantity' => $row['QUANTITY'],
  325. 'price' => $row['PRICE'],
  326. 'currency' => $row['CURRENCY'],
  327. 'recommendation' => $row['RECOMMENDATION']
  328. );
  329. }
  330. // all together
  331. $data = array(
  332. 'order_id' => $orderId,
  333. 'user_id' => $siteUserId,
  334. 'phone' => $phone,
  335. 'phone256' => $phone256,
  336. 'phone256_e164' => $phone256_e164,
  337. 'email' => $email,
  338. 'email256' => $email256,
  339. 'products' => $products,
  340. 'price' => $order['PRICE'],
  341. 'currency' => $order['CURRENCY']
  342. );
  343. return $data;
  344. }
  345. protected static function getBxUserId()
  346. {
  347. return $_COOKIE['BX_USER_ID'];
  348. }
  349. public static function normalizePhoneNumber($phone)
  350. {
  351. $phone = preg_replace('/[^\d]/', '', $phone);
  352. $cleanPhone = \NormalizePhone($phone, 6);
  353. if (mb_strlen($cleanPhone) == 10)
  354. {
  355. $cleanPhone = '7'.$cleanPhone;
  356. }
  357. return $cleanPhone;
  358. }
  359. public static function isOn()
  360. {
  361. return SiteSpeed::isOn()
  362. && Option::get("main", "gather_catalog_stat", "Y") === "Y"
  363. && defined("LICENSE_KEY") && LICENSE_KEY !== "DEMO"
  364. ;
  365. }
  366. public static function getProductIdsByOfferIds($offerIds)
  367. {
  368. if (empty($offerIds))
  369. return array();
  370. $bestList = array();
  371. $iblockGroup = array();
  372. $itemIterator = \Bitrix\Iblock\ElementTable::getList(array(
  373. 'select' => array('ID', 'IBLOCK_ID'),
  374. 'filter' => array('@ID' => $offerIds, '=ACTIVE'=> 'Y')
  375. ));
  376. while ($item = $itemIterator->fetch())
  377. {
  378. if (!isset($iblockGroup[$item['IBLOCK_ID']]))
  379. $iblockGroup[$item['IBLOCK_ID']] = array();
  380. $iblockGroup[$item['IBLOCK_ID']][] = $item['ID'];
  381. $bestList[$item['ID']] = array();
  382. }
  383. if (empty($iblockGroup))
  384. return array();
  385. $iblockSku = array();
  386. $iblockOffers = array();
  387. if (!empty($iblockGroup))
  388. {
  389. $iblockIterator = \Bitrix\Catalog\CatalogIblockTable::getList(array(
  390. 'select' => array('IBLOCK_ID', 'PRODUCT_IBLOCK_ID', 'SKU_PROPERTY_ID', 'VERSION' => 'IBLOCK.VERSION'),
  391. 'filter' => array('=IBLOCK_ID' => array_keys($iblockGroup), '!=PRODUCT_IBLOCK_ID' => 0)
  392. ));
  393. while ($iblock = $iblockIterator->fetch())
  394. {
  395. $iblock['IBLOCK_ID'] = (int)$iblock['IBLOCK_ID'];
  396. $iblock['PRODUCT_IBLOCK_ID'] = (int)$iblock['PRODUCT_IBLOCK_ID'];
  397. $iblock['SKU_PROPERTY_ID'] = (int)$iblock['SKU_PROPERTY_ID'];
  398. $iblock['VERSION'] = (int)$iblock['VERSION'];
  399. $iblockSku[$iblock['IBLOCK_ID']] = $iblock;
  400. $iblockOffers[$iblock['IBLOCK_ID']] = $iblockGroup[$iblock['IBLOCK_ID']];
  401. }
  402. unset($iblock, $iblockIterator);
  403. }
  404. if (empty($iblockOffers))
  405. return array();
  406. $offerLink = array();
  407. foreach ($iblockOffers as $iblockId => $items)
  408. {
  409. $skuProperty = 'PROPERTY_'.$iblockSku[$iblockId]['SKU_PROPERTY_ID'];
  410. $iblockFilter = array(
  411. 'IBLOCK_ID' => $iblockId,
  412. '=ID' => $items
  413. );
  414. $iblockFields = array('ID', 'IBLOCK_ID', $skuProperty);
  415. $skuProperty .= '_VALUE';
  416. $offersIterator = \CIBlockElement::getList(
  417. array('ID' => 'ASC'),
  418. $iblockFilter,
  419. false,
  420. false,
  421. $iblockFields
  422. );
  423. while ($offer = $offersIterator->Fetch())
  424. {
  425. $productId = (int)$offer[$skuProperty];
  426. if ($productId <= 0)
  427. {
  428. unset($bestList[$offer['ID']]);
  429. }
  430. else
  431. {
  432. $bestList[$offer['ID']]['PARENT_ID'] = $productId;
  433. $bestList[$offer['ID']]['PARENT_IBLOCK'] = $iblockSku[$iblockId]['PRODUCT_IBLOCK_ID'];
  434. if (!isset($offerLink[$productId]))
  435. $offerLink[$productId] = array();
  436. $offerLink[$productId][] = $offer['ID'];
  437. }
  438. }
  439. }
  440. if (!empty($offerLink))
  441. {
  442. $productIterator = \Bitrix\Iblock\ElementTable::getList(array(
  443. 'select' => array('ID'),
  444. 'filter' => array('@ID' => array_keys($offerLink), '=ACTIVE' => 'N')
  445. ));
  446. while ($product = $productIterator->fetch())
  447. {
  448. if (empty($offerLink[$product['ID']]))
  449. continue;
  450. foreach ($offerLink[$product['ID']] as $value)
  451. {
  452. unset($bestList[$value]);
  453. }
  454. }
  455. }
  456. if (empty($bestList))
  457. return array();
  458. $finalIds = array();
  459. $dublicate = array();
  460. foreach ($bestList as $id => $info)
  461. {
  462. if (empty($info))
  463. {
  464. if (!isset($dublicate[$id]))
  465. $finalIds[] = $id;
  466. $dublicate[$id] = true;
  467. }
  468. else
  469. {
  470. if (!isset($dublicate[$id]))
  471. $finalIds[] = $info['PARENT_ID'];
  472. $dublicate[$info['PARENT_ID']] = true;
  473. }
  474. }
  475. unset($id, $info, $dublicate);
  476. return $finalIds;
  477. }
  478. /**
  479. * @param array $log
  480. *
  481. * @return string
  482. */
  483. public static function encodeProductLog(array $log)
  484. {
  485. $value = array();
  486. foreach ($log as $itemId => $recommendation)
  487. {
  488. $rcmId = $recommendation[0];
  489. $rcmTime = $recommendation[1];
  490. $value[] = $itemId.'-'.$rcmId.'-'.$rcmTime;
  491. }
  492. return join('.', $value);
  493. }
  494. /**
  495. * @param $log
  496. *
  497. * @return array
  498. */
  499. public static function decodeProductLog($log)
  500. {
  501. $value = array();
  502. $tmp = explode('.', $log);
  503. foreach ($tmp as $tmpval)
  504. {
  505. $meta = explode('-', $tmpval);
  506. if (count($meta) > 2)
  507. {
  508. $itemId = $meta[0];
  509. $rcmId = $meta[1];
  510. $rcmTime = $meta[2];
  511. if ($itemId && $rcmId && $rcmTime)
  512. {
  513. $value[(int)$itemId] = array($rcmId, (int) $rcmTime);
  514. }
  515. }
  516. }
  517. return $value;
  518. }
  519. /**
  520. * @return string
  521. */
  522. public static function getCookieLogName()
  523. {
  524. return self::$cookieLogName;
  525. }
  526. }