PageRenderTime 50ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/sender/lib/posting/threadstrategy/abstractthreadstrategy.php

https://gitlab.com/alexprowars/bitrix
PHP | 367 lines | 259 code | 40 blank | 68 comment | 16 complexity | e32cb23469ea23b1af09e640da18dc04 MD5 | raw file
  1. <?php
  2. namespace Bitrix\Sender\Posting\ThreadStrategy;
  3. use Bitrix\Main\Application;
  4. use Bitrix\Main\DB;
  5. use Bitrix\Main\DB\SqlExpression;
  6. use Bitrix\Main\DB\SqlQueryException;
  7. use Bitrix\Main\ORM\Query\Result;
  8. use Bitrix\Main\Type\DateTime;
  9. use Bitrix\Sender\Internals\Model\PostingThreadTable;
  10. use Bitrix\Sender\PostingRecipientTable;
  11. abstract class AbstractThreadStrategy implements IThreadStrategy
  12. {
  13. /**
  14. * @var int
  15. */
  16. protected $threadId;
  17. protected $postingId;
  18. protected $select;
  19. protected $filter;
  20. protected $runtime;
  21. /**
  22. *
  23. * @return array
  24. */
  25. public function fillThreads(): void
  26. {
  27. $tableName = PostingThreadTable::getTableName();
  28. $insertData = [];
  29. for ($thread = 0; $thread < static::THREADS_COUNT; $thread++)
  30. {
  31. $insertData[] = '('.$thread.', '.$this->postingId.', \''.static::THREADS_COUNT.'\')';
  32. }
  33. $query = '
  34. INSERT INTO `'.$tableName.'`(THREAD_ID, POSTING_ID, THREAD_TYPE)
  35. VALUES '.implode(',', $insertData).'
  36. ON DUPLICATE KEY UPDATE
  37. THREAD_ID = VALUES(THREAD_ID)
  38. ';
  39. try
  40. {
  41. Application::getConnection()->query($query);
  42. }
  43. catch (SqlQueryException $e)
  44. {
  45. }
  46. }
  47. /**
  48. * @param $limit
  49. *
  50. * @return Result
  51. * @throws \Bitrix\Main\ArgumentException
  52. * @throws \Bitrix\Main\ObjectPropertyException
  53. * @throws \Bitrix\Main\SystemException
  54. */
  55. public function getRecipients(int $limit): Result
  56. {
  57. static::setRuntime();
  58. static::setFilter();
  59. static::setSelect();
  60. // select all recipients of posting, only not processed
  61. $recipients = PostingRecipientTable::getList(
  62. [
  63. 'select' => $this->select,
  64. 'filter' => $this->filter,
  65. 'runtime' => $this->runtime,
  66. 'order' => ['STATUS' => 'DESC'],
  67. 'limit' => $limit
  68. ]
  69. );
  70. $recipients->addFetchDataModifier(
  71. function($row)
  72. {
  73. $row['FIELDS'] = is_array($row['FIELDS']) ? $row['FIELDS'] : [];
  74. return $row;
  75. }
  76. );
  77. return $recipients;
  78. }
  79. abstract protected function setRuntime(): void;
  80. protected function setFilter() : void
  81. {
  82. $this->filter = ['IS_UNSUB' => 'N'];
  83. }
  84. protected function setSelect(): void
  85. {
  86. $this->select = [
  87. '*',
  88. 'NAME' => 'CONTACT.NAME',
  89. 'CONTACT_CODE' => 'CONTACT.CODE',
  90. 'CONTACT_TYPE_ID' => 'CONTACT.TYPE_ID',
  91. 'CONTACT_IS_SEND_SUCCESS' => 'CONTACT.IS_SEND_SUCCESS',
  92. 'CONTACT_BLACKLISTED' => 'CONTACT.BLACKLISTED',
  93. 'CONTACT_UNSUBSCRIBED' => 'CONTACT.IS_UNSUB',
  94. 'CONTACT_CONSENT_STATUS' => 'CONTACT.CONSENT_STATUS',
  95. 'CONTACT_MAILING_UNSUBSCRIBED' => 'MAILING_SUB.IS_UNSUB',
  96. 'CONTACT_CONSENT_REQUEST' => 'CONTACT.CONSENT_REQUEST',
  97. 'CAMPAIGN_ID' => 'POSTING.MAILING_ID',
  98. ];
  99. }
  100. /**
  101. * lock thread for duplicate select
  102. * @return int|null
  103. * @throws \Bitrix\Main\ArgumentException
  104. * @throws \Bitrix\Main\ObjectPropertyException
  105. * @throws \Bitrix\Main\SystemException
  106. */
  107. public function lockThread(): void
  108. {
  109. if(!static::checkLock())
  110. {
  111. return;
  112. }
  113. $thread = PostingThreadTable::getList(
  114. [
  115. "select" => ["THREAD_ID"],
  116. "filter" => [
  117. '=POSTING_ID' => $this->postingId,
  118. [
  119. 'LOGIC' => 'OR',
  120. [
  121. '=STATUS' => PostingThreadTable::STATUS_NEW,
  122. ],
  123. [
  124. '=STATUS' => PostingThreadTable::STATUS_IN_PROGRESS,
  125. '<EXPIRE_AT' => new DateTime()
  126. ]
  127. ]
  128. ],
  129. "limit" => 1
  130. ]
  131. )->fetchAll();
  132. if (!isset($thread[0]) && !isset($thread[0]["THREAD_ID"]))
  133. {
  134. return;
  135. }
  136. $this->threadId = $thread[0]["THREAD_ID"];
  137. $this->updateStatus(PostingThreadTable::STATUS_IN_PROGRESS);
  138. $this->unlock();
  139. }
  140. /**
  141. * lock table from selecting of the thread
  142. * @return bool
  143. * @throws SqlQueryException
  144. * @throws \Bitrix\Main\SystemException
  145. */
  146. protected function lock()
  147. {
  148. $connection = Application::getInstance()->getConnection();
  149. if ($connection instanceof DB\MysqlCommonConnection)
  150. {
  151. $lockDb = $connection->query(
  152. sprintf(
  153. "SELECT GET_LOCK('posting_thread_%d', 0) as L",
  154. $this->postingId
  155. ),
  156. false,
  157. "File: ".__FILE__."<br>Line: ".__LINE__
  158. );
  159. $lock = $lockDb->fetch();
  160. if ($lock["L"] == "1")
  161. {
  162. return true;
  163. }
  164. }
  165. return false;
  166. }
  167. /**
  168. * update status with expire date
  169. * @param $threadId
  170. * @param $status
  171. */
  172. public function updateStatus(string $status): bool
  173. {
  174. if($status === PostingThreadTable::STATUS_DONE && !$this->checkToFinalizeStatus())
  175. {
  176. $status = PostingThreadTable::STATUS_NEW;
  177. }
  178. try
  179. {
  180. $tableName = PostingThreadTable::getTableName();
  181. $expireAt = (new \DateTime())->modify("+10 minutes")->format('Y-m-d H:i:s');
  182. $updateQuery = 'UPDATE '.$tableName.'
  183. SET
  184. STATUS = \''.$status.'\',
  185. EXPIRE_AT = \''.$expireAt.'\'
  186. WHERE
  187. THREAD_ID = '.$this->threadId.'
  188. AND POSTING_ID = '.$this->postingId;
  189. Application::getConnection()->query($updateQuery);
  190. }
  191. catch (\Exception $e)
  192. {
  193. return false;
  194. }
  195. return true;
  196. }
  197. /**
  198. * unlock table for select
  199. * @return bool
  200. * @throws SqlQueryException
  201. * @throws \Bitrix\Main\SystemException
  202. */
  203. protected function unlock()
  204. {
  205. $connection = Application::getInstance()->getConnection();
  206. if ($connection instanceof DB\MysqlCommonConnection)
  207. {
  208. $lockDb = $connection->query(
  209. sprintf(
  210. "SELECT RELEASE_LOCK('posting_thread_%d') as L",
  211. $this->postingId
  212. )
  213. );
  214. $lock = $lockDb->fetch();
  215. if ($lock["L"] != "0")
  216. {
  217. return true;
  218. }
  219. }
  220. return false;
  221. }
  222. /**
  223. * checking that all threads are completed
  224. * @return bool
  225. */
  226. public function hasUnprocessedThreads(): bool
  227. {
  228. try
  229. {
  230. $threads = PostingThreadTable::getList(
  231. [
  232. "select" => ["THREAD_ID"],
  233. "filter" => [
  234. '@STATUS' => new SqlExpression(
  235. "?, ?", PostingThreadTable::STATUS_NEW, PostingThreadTable::STATUS_IN_PROGRESS
  236. ),
  237. '=POSTING_ID' => $this->postingId,
  238. '!=THREAD_ID' => $this->threadId
  239. ]
  240. ]
  241. )->fetchAll();
  242. }
  243. catch (\Exception $e)
  244. {
  245. }
  246. return !empty($threads);
  247. }
  248. /**
  249. * get current thread id
  250. * @return int
  251. */
  252. public function getThreadId(): ?int
  253. {
  254. return $this->threadId;
  255. }
  256. /**
  257. * get last thread number
  258. * @return int
  259. */
  260. public function lastThreadId(): int
  261. {
  262. return static::THREADS_COUNT - 1;
  263. }
  264. /**
  265. * @param int $postingId
  266. *
  267. * @return TenThreadsStrategy
  268. */
  269. public function setPostingId(int $postingId): IThreadStrategy
  270. {
  271. $this->postingId = $postingId;
  272. return $this;
  273. }
  274. /**
  275. * wait while threads are calculating
  276. * @return bool
  277. */
  278. protected function checkLock()
  279. {
  280. for($i = 0; $i <= static::lastThreadId(); $i++)
  281. {
  282. if ($this->lock())
  283. {
  284. return true;
  285. }
  286. sleep(rand(1,7));
  287. }
  288. return false;
  289. }
  290. /**
  291. * Finalize thread activity
  292. */
  293. public function finalize()
  294. {
  295. if(!$this->checkToFinalizeStatus())
  296. {
  297. return false;
  298. }
  299. $tableName = PostingThreadTable::getTableName();
  300. $query = 'DELETE FROM `'.$tableName.'` WHERE POSTING_ID='.intval($this->postingId);
  301. try
  302. {
  303. Application::getConnection()->query($query);
  304. }
  305. catch (SqlQueryException $e)
  306. {
  307. return false;
  308. }
  309. return true;
  310. }
  311. private function checkToFinalizeStatus()
  312. {
  313. if($this->threadId < static::lastThreadId())
  314. {
  315. return true;
  316. }
  317. return !static::hasUnprocessedThreads();
  318. }
  319. /**
  320. * Returns true if sending not available
  321. * @return bool
  322. */
  323. public function isProcessLimited(): bool
  324. {
  325. return false;
  326. }
  327. }