PageRenderTime 31ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/modules/pull/lib/event.php

https://gitlab.com/alexprowars/bitrix
PHP | 721 lines | 620 code | 100 blank | 1 comment | 102 complexity | bcc113f0cfe5227cf60ee75337715334 MD5 | raw file
  1. <?php
  2. namespace Bitrix\Pull;
  3. use Bitrix\Main;
  4. use Bitrix\Main\Localization\Loc;
  5. class Event
  6. {
  7. const SHARED_CHANNEL = 0;
  8. private static $backgroundContext = false;
  9. private static $messages = array();
  10. private static $deferredMessages = array();
  11. private static $push = array();
  12. private static $error = false;
  13. public static function add($recipient, array $parameters, $channelType = \CPullChannel::TYPE_PRIVATE)
  14. {
  15. if (!isset($parameters['module_id']))
  16. {
  17. self::$error = new Error(__METHOD__, 'EVENT_PARAMETERS_FORMAT', Loc::getMessage('PULL_EVENT_PARAMETERS_FORMAT_ERROR'), $parameters);
  18. return false;
  19. }
  20. $badUnicodeSymbolsPath = Common::findInvalidUnicodeSymbols($parameters);
  21. if($badUnicodeSymbolsPath)
  22. {
  23. $warning = 'Parameters array contains invalid UTF-8 characters by the path ' . $badUnicodeSymbolsPath;
  24. self::$error = new Error(__METHOD__, 'EVENT_BAD_ENCODING', $warning, $parameters);
  25. return false;
  26. }
  27. if (isset($parameters['command']) && !empty($parameters['command']))
  28. {
  29. $result = self::addEvent($recipient, $parameters, $channelType);
  30. }
  31. else if (isset($parameters['push']) || isset($parameters['pushParamsCallback']))
  32. {
  33. $result = self::addPush($recipient, $parameters);
  34. }
  35. else
  36. {
  37. self::$error = new Error(__METHOD__, 'EVENT_PARAMETERS_FORMAT', Loc::getMessage('PULL_EVENT_PARAMETERS_FORMAT_ERROR'), $parameters);
  38. return false;
  39. }
  40. return $result;
  41. }
  42. private static function addEvent($recipient, $parameters, $channelType = \CPullChannel::TYPE_PRIVATE)
  43. {
  44. if (!is_array($recipient))
  45. {
  46. $recipient = [$recipient];
  47. }
  48. $entities = self::getEntitiesByType($recipient);
  49. if (!$entities)
  50. {
  51. self::$error = new Error(__METHOD__, 'RECIPIENT_FORMAT', Loc::getMessage('PULL_EVENT_RECIPIENT_FORMAT_ERROR'), Array(
  52. 'recipient' => $recipient,
  53. 'eventParameters' => $parameters
  54. ));
  55. return false;
  56. }
  57. $parameters = self::prepareParameters($parameters);
  58. if (!$parameters)
  59. {
  60. return false;
  61. }
  62. $request = Array('users'=>array(), 'channels'=>array());
  63. if (!empty($entities['users']))
  64. {
  65. $request['users'] = self::getChannelIds($entities['users'], $channelType);
  66. }
  67. if (!empty($entities['channels']))
  68. {
  69. $request['channels'] = self::getUserIds($entities['channels']);
  70. }
  71. $channels = array_merge($request['users'], $request['channels']);
  72. if (empty($channels))
  73. {
  74. return true;
  75. }
  76. if (isset($parameters['push']))
  77. {
  78. $pushParameters = $parameters['push'];
  79. unset($parameters['push']);
  80. }
  81. else
  82. {
  83. $pushParameters = null;
  84. }
  85. if (isset($parameters['pushParamsCallback']))
  86. {
  87. $pushParametersCallback = $parameters['pushParamsCallback'];
  88. unset($parameters['pushParamsCallback']);
  89. }
  90. else
  91. {
  92. $pushParametersCallback = null;
  93. }
  94. if($parameters['hasCallback'])
  95. {
  96. self::addMessage(self::$deferredMessages, $channels, $parameters);
  97. }
  98. else
  99. {
  100. self::addMessage(self::$messages, $channels, $parameters);
  101. }
  102. if (
  103. self::$backgroundContext
  104. || defined('BX_CHECK_AGENT_START') && !defined('BX_WITH_ON_AFTER_EPILOG')
  105. )
  106. {
  107. self::send();
  108. }
  109. if ($pushParameters || $pushParametersCallback)
  110. {
  111. if ($pushParameters)
  112. {
  113. $parameters['push'] = $pushParameters;
  114. }
  115. if ($pushParametersCallback)
  116. {
  117. $parameters['pushParamsCallback'] = $pushParametersCallback;
  118. }
  119. self::addPush(array_values($request['users']), $parameters);
  120. }
  121. return true;
  122. }
  123. private static function addMessage(array &$destination, array $channels, array $parameters)
  124. {
  125. $eventCode = self::getParamsCode($parameters);
  126. unset($parameters['hasCallback']);
  127. if ($destination[$eventCode])
  128. {
  129. $destination[$eventCode]['users'] = array_unique(array_merge($destination[$eventCode]['users'], array_values($channels)));
  130. $destination[$eventCode]['channels'] = array_unique(array_merge($destination[$eventCode]['channels'], array_keys($channels)));
  131. }
  132. else
  133. {
  134. $destination[$eventCode]['event'] = $parameters;
  135. $destination[$eventCode]['users'] = array_unique(array_values($channels));
  136. $destination[$eventCode]['channels'] = array_unique(array_keys($channels));
  137. }
  138. }
  139. private static function addPush($users, $parameters)
  140. {
  141. if (!\CPullOptions::GetPushStatus())
  142. {
  143. self::$error = new Error(__METHOD__, 'PUSH_DISABLED', Loc::getMessage('PULL_EVENT_PUSH_DISABLED_ERROR'), Array(
  144. 'recipient' => $users,
  145. 'eventParameters' => $parameters
  146. ));
  147. return false;
  148. }
  149. if (!is_array($users))
  150. {
  151. $users = Array($users);
  152. }
  153. foreach ($users as $id => $entity)
  154. {
  155. $entity = intval($entity);
  156. if ($entity <= 0)
  157. {
  158. unset($users[$id]);
  159. }
  160. }
  161. if (empty($users))
  162. {
  163. self::$error = new Error(__METHOD__, 'RECIPIENT_FORMAT', Loc::getMessage('PULL_EVENT_RECIPIENT_FORMAT_ERROR'), Array(
  164. 'recipient' => $users,
  165. 'eventParameters' => $parameters
  166. ));
  167. return false;
  168. }
  169. if (isset($parameters['skip_users']))
  170. {
  171. if (!isset($parameters['push']['skip_users']))
  172. {
  173. $parameters['push']['skip_users'] = Array();
  174. }
  175. $parameters['push']['skip_users'] = array_merge($parameters['skip_users'], $parameters['push']['skip_users']);
  176. }
  177. if (!empty($parameters['push']['type']))
  178. {
  179. foreach ($users as $userId)
  180. {
  181. if (!\Bitrix\Pull\Push::getConfigTypeStatus($parameters['module_id'], $parameters['push']['type'], $userId))
  182. {
  183. $parameters['push']['skip_users'][] = $userId;
  184. }
  185. }
  186. }
  187. $parameters = self::preparePushParameters($parameters);
  188. if (!$parameters)
  189. {
  190. return false;
  191. }
  192. $pushCode = self::getParamsCode($parameters['push']);
  193. if (self::$push[$pushCode])
  194. {
  195. self::$push[$pushCode]['users'] = array_unique(array_merge(self::$push[$pushCode]['users'], array_values($users)));
  196. }
  197. else
  198. {
  199. $hasPushCallback = $parameters['hasPushCallback'];
  200. unset($parameters['hasPushCallback']);
  201. self::$push[$pushCode]['push'] = $parameters['push'];
  202. self::$push[$pushCode]['extra'] = $parameters['extra'];
  203. self::$push[$pushCode]['hasPushCallback'] = $hasPushCallback;
  204. self::$push[$pushCode]['users'] = array_unique(array_values($users));
  205. }
  206. if (
  207. self::$backgroundContext
  208. || defined('BX_CHECK_AGENT_START') && !defined('BX_WITH_ON_AFTER_EPILOG')
  209. )
  210. {
  211. self::send();
  212. }
  213. return true;
  214. }
  215. private static function processDeferredMessages()
  216. {
  217. foreach (self::$deferredMessages as $eventCode => $message)
  218. {
  219. $callback = $message['event']['paramsCallback'];
  220. if (Main\Loader::includeModule($callback['module_id']) && method_exists($callback['class'], $callback['method']))
  221. {
  222. $messageParameters = call_user_func_array([$callback['class'], $callback['method']], [$callback['params']]);
  223. self::addMessage(self::$messages, $message['channels'], $messageParameters);
  224. }
  225. }
  226. self::$deferredMessages = [];
  227. }
  228. private static function executeEvent($parameters)
  229. {
  230. if (empty($parameters['channels']))
  231. {
  232. return true;
  233. }
  234. return \CPullStack::AddByChannel($parameters['channels'], $parameters['event']);
  235. }
  236. private static function executePushEvent($parameters)
  237. {
  238. if (!self::$backgroundContext && $parameters['hasPushCallback'])
  239. {
  240. return null;
  241. }
  242. $data = Array();
  243. if ($parameters['hasPushCallback'])
  244. {
  245. Main\Loader::includeModule($parameters['push']['pushParamsCallback']['module_id']);
  246. if (method_exists($parameters['push']['pushParamsCallback']['class'], $parameters['push']['pushParamsCallback']['method']))
  247. {
  248. $data = call_user_func_array(
  249. array(
  250. $parameters['push']['pushParamsCallback']['class'],
  251. $parameters['push']['pushParamsCallback']['method']
  252. ),
  253. Array(
  254. $parameters['push']['pushParamsCallback']['params']
  255. )
  256. );
  257. }
  258. }
  259. else
  260. {
  261. $data = $parameters['push'];
  262. }
  263. $data['message'] = str_replace("\n", " ", trim($data['message']));
  264. $data['params'] = isset($data['params'])? $data['params']: Array();
  265. $data['advanced_params'] = isset($data['advanced_params'])? $data['advanced_params']: Array();
  266. $data['advanced_params']['extra'] = $parameters['extra']? $parameters['extra']: Array();
  267. $data['badge'] = isset($data['badge'])? intval($data['badge']): '';
  268. $data['sound'] = isset($data['sound'])? $data['sound']: '';
  269. $data['tag'] = isset($data['tag'])? $data['tag']: '';
  270. $data['sub_tag'] = isset($data['sub_tag'])? $data['sub_tag']: '';
  271. $data['app_id'] = isset($data['app_id'])? $data['app_id']: '';
  272. $data['send_immediately'] = $data['send_immediately'] == 'Y'? 'Y': 'N';
  273. $data['important'] = $data['important'] == 'Y'? 'Y': 'N';
  274. $users = Array();
  275. foreach ($parameters['users'] as $userId)
  276. {
  277. $users[] = $userId;
  278. }
  279. if (empty($users))
  280. {
  281. return true;
  282. }
  283. $manager = new \CPushManager();
  284. $manager->AddQueue(Array(
  285. 'USER_ID' => $users,
  286. 'SKIP_USERS' => is_array($data['skip_users'])? $data['skip_users']: Array(),
  287. 'MESSAGE' => $data['message'],
  288. 'EXPIRY' => $data['expiry'],
  289. 'PARAMS' => $data['params'],
  290. 'ADVANCED_PARAMS' => $data['advanced_params'],
  291. 'BADGE' => $data['badge'],
  292. 'SOUND' => $data['sound'],
  293. 'TAG' => $data['tag'],
  294. 'SUB_TAG' => $data['sub_tag'],
  295. 'APP_ID' => $data['app_id'],
  296. 'SEND_IMMEDIATELY' => $data['send_immediately'],
  297. 'IMPORTANT' => $data['important'],
  298. ));
  299. return true;
  300. }
  301. public static function send()
  302. {
  303. if (self::$backgroundContext)
  304. {
  305. self::processDeferredMessages();
  306. }
  307. static::executeEvents();
  308. static::executePushEvents();
  309. return true;
  310. }
  311. public static function executeEvents()
  312. {
  313. $hitCount = 0;
  314. $channelCount = 0;
  315. $messagesCount = count(self::$messages);
  316. $messagesBytes = 0;
  317. $logs = Array();
  318. if(count(self::$messages) === 0)
  319. {
  320. return true;
  321. }
  322. if (!\CPullOptions::GetQueueServerStatus())
  323. {
  324. self::$messages = [];
  325. return true;
  326. }
  327. if(Config::isProtobufUsed())
  328. {
  329. if(ProtobufTransport::sendMessages(self::$messages))
  330. {
  331. self::$messages = [];
  332. }
  333. }
  334. else
  335. {
  336. foreach (self::$messages as $eventCode => $event)
  337. {
  338. if (\Bitrix\Pull\Log::isEnabled())
  339. {
  340. // TODO change code after release - $parameters['hasCallback']
  341. $currentHits = ceil(count($event['channels'])/\CPullOptions::GetCommandPerHit());
  342. $hitCount += $currentHits;
  343. $currentChannelCount = count($event['channels']);
  344. $channelCount += $currentChannelCount;
  345. $currentMessagesBytes = self::getBytes($event['event'])+self::getBytes($event['channels']);
  346. $messagesBytes += $currentMessagesBytes;
  347. $logs[] = 'Command: '.$event['event']['module_id'].'/'.$event['event']['command'].'; Hits: '.$currentHits.'; Channel: '.$currentChannelCount.'; Bytes: '.$currentMessagesBytes.'';
  348. }
  349. if (empty($event['channels']))
  350. {
  351. continue;
  352. }
  353. if (\CPullStack::AddByChannel($event['channels'], $event['event']))
  354. {
  355. unset(self::$messages[$eventCode]);
  356. }
  357. }
  358. if ($logs && \Bitrix\Pull\Log::isEnabled())
  359. {
  360. if (count($logs) > 1)
  361. {
  362. $logs[] = 'Total - Hits: '.$hitCount.'; Channel: '.$channelCount.'; Messages: '.$messagesCount.'; Bytes: '.$messagesBytes.'';
  363. }
  364. if (count($logs) > 1 || $hitCount > 1 || $channelCount > 1 || $messagesBytes > 1000)
  365. {
  366. $logTitle = '!! Pull messages stats - important !!';
  367. }
  368. else
  369. {
  370. $logTitle = '-- Pull messages stats --';
  371. }
  372. \Bitrix\Pull\Log::write(implode("\n", $logs), $logTitle);
  373. }
  374. }
  375. }
  376. public static function executePushEvents()
  377. {
  378. foreach (self::$push as $pushCode => $event)
  379. {
  380. $result = self::executePushEvent($event);
  381. if (!is_null($result))
  382. {
  383. unset(self::$push[$pushCode]);
  384. }
  385. }
  386. }
  387. public static function onAfterEpilog()
  388. {
  389. Main\Application::getInstance()->addBackgroundJob([__CLASS__, "sendInBackground"]);
  390. return true;
  391. }
  392. public static function sendInBackground()
  393. {
  394. self::$backgroundContext = true;
  395. self::send();
  396. }
  397. public static function getChannelIds($users, $type = \CPullChannel::TYPE_PRIVATE)
  398. {
  399. if (!is_array($users))
  400. {
  401. $users = Array($users);
  402. }
  403. $result = Array();
  404. foreach ($users as $userId)
  405. {
  406. $data = \CPullChannel::Get($userId, true, false, $type);
  407. if ($data)
  408. {
  409. $result[$data['CHANNEL_ID']] = $userId;
  410. }
  411. }
  412. return $result;
  413. }
  414. public static function getUserIds(array $channels)
  415. {
  416. $result = array_fill_keys($channels, null);
  417. $orm = \Bitrix\Pull\Model\ChannelTable::getList([
  418. 'select' => ['USER_ID', 'CHANNEL_ID', 'USER_ACTIVE' => 'USER.ACTIVE'],
  419. 'filter' => [
  420. '=CHANNEL_ID' => $channels
  421. ]
  422. ]);
  423. while ($row = $orm->fetch())
  424. {
  425. if ($row['USER_ID'] > 0 && $row['USER_ACTIVE'] !== 'N')
  426. {
  427. $result[$row['CHANNEL_ID']] = $row['USER_ID'];
  428. }
  429. else
  430. {
  431. unset($result[$row['CHANNEL_ID']]);
  432. }
  433. }
  434. return $result;
  435. }
  436. private static function prepareParameters($parameters)
  437. {
  438. if (
  439. !isset($parameters['command']) || empty($parameters['command'])
  440. )
  441. {
  442. self::$error = new Error(__METHOD__, 'EVENT_PARAMETERS_FORMAT', Loc::getMessage('PULL_EVENT_PARAMETERS_FORMAT_ERROR'), $parameters);
  443. return false;
  444. }
  445. $parameters['module_id'] = mb_strtolower($parameters['module_id']);
  446. $parameters['expiry'] = (int)($parameters['expiry'] ?? 86400);
  447. if (isset($parameters['paramsCallback']))
  448. {
  449. if (
  450. empty($parameters['paramsCallback']['class'])
  451. || empty($parameters['paramsCallback']['method'])
  452. )
  453. {
  454. self::$error = new Error(__METHOD__, 'EVENT_CALLBACK_FORMAT', Loc::getMessage('PULL_EVENT_CALLBACK_FORMAT_ERROR'), $parameters);
  455. return false;
  456. }
  457. if (empty($parameters['paramsCallback']['module_id']))
  458. {
  459. $parameters['paramsCallback']['module_id'] = $parameters['module_id'];
  460. }
  461. Main\Loader::includeModule($parameters['paramsCallback']['module_id']);
  462. if (!method_exists($parameters['paramsCallback']['class'], $parameters['paramsCallback']['method']))
  463. {
  464. self::$error = new Error(__METHOD__, 'EVENT_CALLBACK_NOT_FOUND', Loc::getMessage('PULL_EVENT_CALLBACK_FORMAT_ERROR'), $parameters);
  465. return false;
  466. }
  467. if (!isset($parameters['paramsCallback']['params']))
  468. {
  469. $parameters['paramsCallback']['params'] = array();
  470. }
  471. $parameters['params'] = Array();
  472. $parameters['hasCallback'] = true;
  473. }
  474. else
  475. {
  476. $parameters['hasCallback'] = false;
  477. $parameters['paramsCallback'] = Array();
  478. if (
  479. !isset($parameters['params'])
  480. || !is_array($parameters['params'])
  481. )
  482. {
  483. $parameters['params'] = Array();
  484. }
  485. }
  486. if (!isset($parameters['extra']['server_time']))
  487. {
  488. $parameters['extra']['server_time'] = date('c');
  489. }
  490. if (!$parameters['extra']['server_time_unix'])
  491. {
  492. $parameters['extra']['server_time_unix'] = microtime(true);
  493. }
  494. return $parameters;
  495. }
  496. private static function preparePushParameters($parameters)
  497. {
  498. $parameters['module_id'] = mb_strtolower($parameters['module_id']);
  499. if (isset($parameters['pushParamsCallback']))
  500. {
  501. if (
  502. empty($parameters['pushParamsCallback']['class'])
  503. || empty($parameters['pushParamsCallback']['method'])
  504. )
  505. {
  506. self::$error = new Error(__METHOD__, 'EVENT_PUSH_CALLBACK_FORMAT', Loc::getMessage('PULL_EVENT_PUSH_CALLBACK_FORMAT_ERROR'), $parameters);
  507. return false;
  508. }
  509. if (empty($parameters['pushParamsCallback']['module_id']))
  510. {
  511. $parameters['pushParamsCallback']['module_id'] = $parameters['module_id'];
  512. }
  513. Main\Loader::includeModule($parameters['pushParamsCallback']['module_id']);
  514. if (!method_exists($parameters['pushParamsCallback']['class'], $parameters['pushParamsCallback']['method']))
  515. {
  516. self::$error = new Error(__METHOD__, 'EVENT_PUSH_CALLBACK_NOT_FOUND', Loc::getMessage('PULL_EVENT_PUSH_CALLBACK_FORMAT_ERROR'), $parameters);
  517. return false;
  518. }
  519. if (!isset($parameters['pushParamsCallback']['params']))
  520. {
  521. $parameters['pushParamsCallback']['params'] = array();
  522. }
  523. $parameters['push']['pushParamsCallback'] = $parameters['pushParamsCallback'];
  524. $parameters['hasPushCallback'] = true;
  525. }
  526. else
  527. {
  528. $parameters['hasPushCallback'] = false;
  529. $parameters['pushParamsCallback'] = Array();
  530. if (isset($parameters['badge']) && $parameters['badge'] == 'Y')
  531. {
  532. $parameters['send_immediately'] = 'Y';
  533. unset($parameters['badge']);
  534. }
  535. if (empty($parameters['push']))
  536. {
  537. self::$error = new Error(__METHOD__, 'EVENT_PUSH_PARAMETERS_FORMAT', Loc::getMessage('PULL_EVENT_PUSH_PARAMETERS_FORMAT_ERROR'), $parameters);
  538. return false;
  539. }
  540. }
  541. if (!isset($parameters['extra']['server_time']))
  542. {
  543. $parameters['extra']['server_time'] = date('c');
  544. }
  545. if (!$parameters['extra']['server_time_unix'])
  546. {
  547. $parameters['extra']['server_time_unix'] = microtime(true);
  548. }
  549. return $parameters;
  550. }
  551. public static function getParamsCode($params)
  552. {
  553. if (isset($params['groupId']) && !empty($params['groupId']))
  554. {
  555. return md5($params['groupId']);
  556. }
  557. else
  558. {
  559. $paramsWithoutTime = $params;
  560. unset($paramsWithoutTime['extra']['server_time']);
  561. unset($paramsWithoutTime['extra']['server_time_unix']);
  562. unset($paramsWithoutTime['advanced_params']['filterCallback']);
  563. return serialize($paramsWithoutTime);
  564. }
  565. }
  566. private static function getEntitiesByType($recipient)
  567. {
  568. $result = Array(
  569. 'users' => Array(),
  570. 'channels' => Array(),
  571. 'count' => 0,
  572. );
  573. foreach ($recipient as $entity)
  574. {
  575. if ($entity instanceof \Bitrix\Pull\Model\Channel)
  576. {
  577. $result['channels'][] = $entity->getPrivateId();
  578. $result['count']++;
  579. }
  580. else if (self::isChannelEntity($entity))
  581. {
  582. $result['channels'][] = $entity;
  583. $result['count']++;
  584. }
  585. else
  586. {
  587. $result['users'][] = intval($entity);
  588. $result['count']++;
  589. }
  590. }
  591. return $result['count'] > 0? $result: false;
  592. }
  593. private static function getBytes($variable)
  594. {
  595. $bytes = 0;
  596. if (is_string($variable))
  597. {
  598. $bytes += mb_strlen($variable);
  599. }
  600. else if (is_array($variable))
  601. {
  602. foreach ($variable as $value)
  603. {
  604. $bytes += self::getBytes($value);
  605. }
  606. }
  607. else
  608. {
  609. $bytes += mb_strlen((string)$variable);
  610. }
  611. return $bytes;
  612. }
  613. private static function isChannelEntity($entity)
  614. {
  615. return is_string($entity) && mb_strlen($entity) == 32;
  616. }
  617. public static function getLastError()
  618. {
  619. return self::$error;
  620. }
  621. }