Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
builder.php
1<?php
9
31
32Loc::loadMessages(__FILE__);
33
39{
41 protected $checkDuplicates = true;
42
44 protected $groupCount = array();
45
47 protected $postingId;
48
50 protected $postingData;
51
53 protected $typeId;
54
58 private $result;
59
63 private $groupQueueService;
64
68 private $messageConfiguration;
69
75 public static function create()
76 {
77 return new static();
78 }
79
83 public function isResult(): bool
84 {
85 return $this->result;
86 }
87
93 public function setResult(bool $result): Builder
94 {
95 $this->result = $result;
96
97 return $this;
98 }
99
100
107 public function __construct($postingId = null, $checkDuplicates = true)
108 {
109 $this->groupQueueService = new GroupQueueService();
110 if ($postingId)
111 {
112 $this->setResult($this->run($postingId, $checkDuplicates));
113 }
114 }
115
125 public function run($postingId, $checkDuplicates = true): bool
126 {
127 $postingData = PostingTable::getList(array(
128 'select' => array(
129 '*',
130 'MESSAGE_TYPE' => 'MAILING_CHAIN.MESSAGE_CODE',
131 'WAITING_RECIPIENT' => 'MAILING_CHAIN.WAITING_RECIPIENT',
132 'MAILING_STATUS' => 'MAILING_CHAIN.STATUS',
133 'MESSAGE_ID' => 'MAILING_CHAIN.MESSAGE_ID'
134 ),
135 'filter' => array('ID' => $postingId),
136 'limit' => 1
137 ))->fetch();
138
139 if(!$postingData)
140 {
141 return true;
142 }
143
144 if ($postingData['MAILING_STATUS'] === Model\LetterTable::STATUS_END)
145 {
146 Model\LetterTable::update($postingData['MAILING_CHAIN_ID'], [
147 'WAITING_RECIPIENT' => 'N'
148 ]);
149
150 return true;
151 }
152
153 $entityProcessed = $this->groupQueueService->isEntityProcessed(Model\GroupQueueTable::TYPE['POSTING'], $postingId);
154 if (
155 $postingData['MAILING_STATUS'] === Model\LetterTable::STATUS_SEND && $postingData['WAITING_RECIPIENT'] === 'N'
156 && !$entityProcessed
157 )
158 {
159 return true;
160 }
161
162 $this->postingData = $postingData;
163 $this->checkDuplicates = $checkDuplicates;
164 $this->postingId = $postingId;
165 $this->groupCount = array();
166
167 try
168 {
169 $this->messageConfiguration = Message\Adapter::getInstance($postingData['MESSAGE_TYPE'])
170 ->loadConfiguration($postingData['MESSAGE_ID']);
171 }
172 catch (ArgumentException $e)
173 {
174 return true;
175 }
176
178 {
179 if($this->postingData['STATUS'] === PostingTable::STATUS_NEW)
180 {
182 $this->checkDuplicates = false;
183 }
184 }
185
186 $messageFields = Model\MessageFieldTable::getList(
187 ['filter' => ['=MESSAGE_ID' => $postingData['MESSAGE_ID']]]
188 )->fetchAll();
189
190 $personalizeFields = [];
191 foreach ($messageFields as $messageField)
192 {
193 if (!in_array(
194 $messageField['CODE'],
195 [
196 'MESSAGE_PERSONALIZE',
197 'SUBJECT_PERSONALIZE',
198 'TITLE_PERSONALIZE'
199 ]
200 ))
201 {
202 continue;
203 }
204
205 $personalizeFields[$messageField['CODE']] =
206 json_decode($messageField['VALUE'], true)[1];
207 }
208
209 try
210 {
211 $groups = $this->prepareGroups();
212 $message = Message\Adapter::create($this->postingData['MESSAGE_TYPE']);
213 foreach ($message->getSupportedRecipientTypes() as $typeId)
214 {
215 if (!Recipient\Type::getCode($typeId))
216 {
217 continue;
218 }
219
220 $this->typeId = $typeId;
221 $this->runForRecipientType($personalizeFields, $groups);
222 }
223 } catch (NotCompletedException $e)
224 {
225 return false;
226 }
227
228
229 Model\PostingTable::update(
231 array(
232 'COUNT_SEND_ALL' => PostingRecipientTable::getCount(array('POSTING_ID' => $postingId))
233 )
234 );
235
236 $usedGroups = [];
237 foreach ($groups as $group)
238 {
239 if ($group['GROUP_ID'] && !isset($usedGroups[$group['GROUP_ID']]))
240 {
242
243 $this->groupQueueService->releaseGroup(
244 Model\GroupQueueTable::TYPE['POSTING'],
245 $this->postingId,
246 $group['GROUP_ID']
247 );
248 $usedGroups[$group['GROUP_ID']] = $group['GROUP_ID'];
249 }
250 }
251
252 $this->postingData['WAITING_RECIPIENT'] = 'N';
253 Model\LetterTable::update($this->postingData['MAILING_CHAIN_ID'], [
254 'WAITING_RECIPIENT' => $this->postingData['WAITING_RECIPIENT']
255 ]);
256
257 return true;
258 }
259
260 protected function prepareGroups()
261 {
262
263 $groups = [];
264 $groups = array_merge($groups, $this->getLetterConnectors($this->postingData['MAILING_CHAIN_ID']));
265 $groups = array_merge($groups, $this->getSubscriptionConnectors($this->postingData['MAILING_ID']));
266 Model\LetterTable::update($this->postingData['MAILING_CHAIN_ID'], [
267 'WAITING_RECIPIENT' => 'N'
268 ]);
269
270 foreach ($groups as $group)
271 {
272 if ($group['GROUP_ID'] && !GroupTable::getById($group['GROUP_ID'])->fetch())
273 {
274 continue;
275 }
276
277 if ($group['GROUP_ID'])
278 {
279 $this->groupQueueService
280 ->addToDB(Model\GroupQueueTable::TYPE['POSTING'], $this->postingId, $group['GROUP_ID']);
281 }
282
283 if (in_array($group['STATUS'], [GroupTable::STATUS_NEW, GroupTable::STATUS_DONE]))
284 {
285 SegmentDataBuilder::actualize($group['GROUP_ID'], true);
287 }
288
289 if ($group['STATUS'] !== GroupTable::STATUS_READY_TO_USE)
290 {
293 }
294 }
295
296 // fetch all connectors for getting emails
297 array_walk($groups,
298 function(&$group)
299 {
300 $group['INCLUDE'] = (bool)$group['INCLUDE'];
301 }
302 );
303
304 // sort groups by include value
305 usort(
306 $groups,
307 function ($a, $b)
308 {
309 if ($a['INCLUDE'] == $b['INCLUDE'])
310 {
311 return 0;
312 }
313
314 return ($a['INCLUDE'] > $b['INCLUDE']) ? -1 : 1;
315 }
316 );
317
318 return $groups;
319 }
320
321 protected function runForRecipientType($usedPersonalizeFields = [], $groups = [])
322 {
323 // import recipients
324 foreach($groups as $group)
325 {
326 if (is_array($group['ENDPOINT']) && !(isset($group['CONNECTOR']) && $group['CONNECTOR'] instanceof Connector\Base))
327 {
328 $group['CONNECTOR'] = Connector\Manager::getConnector($group['ENDPOINT']);
329 }
330
331 if(empty($group['CONNECTOR']))
332 {
333 continue;
334 }
335
336 $connector = $group['CONNECTOR'];
337 $connector->setDataTypeId($this->typeId);
338 if (is_array($group['ENDPOINT']['FIELDS']))
339 {
340 $connector->setCheckAccessRights(false);
341 $connector->setFieldValues($group['ENDPOINT']['FIELDS']);
342 }
343
344 $this->fill(
346 $group,
347 $usedPersonalizeFields
348 );
349 }
350 }
351
352 protected function stopRecipientListBuilding()
353 {
354 RecipientBuilderJob::removeAgentFromDB($this->postingData['ID']);
355 RecipientBuilderJob::addEventAgent($this->postingData['ID']);
356
357 Model\LetterTable::update($this->postingData['MAILING_CHAIN_ID'], [
358 'WAITING_RECIPIENT' => $this->postingData['MAILING_STATUS'] !== Model\LetterTable::STATUS_END ? 'Y' : 'N'
359 ]);
360
361 throw new NotCompletedException();
362 }
363 protected static function clean($postingId)
364 {
365 $primary = array('POSTING_ID' => $postingId);
367 Model\PostingTable::update(
369 array(
370 'COUNT_SEND_ALL' => 0,
371 'COUNT_SEND_NONE' => 0,
372 'COUNT_SEND_ERROR' => 0,
373 'COUNT_SEND_SUCCESS' => 0,
374 )
375 );
376 }
377
378 protected function getTypeCode()
379 {
380 return Recipient\Type::getCode($this->typeId);
381 }
382
383 protected function getSubscriptionConnectors($campaignId)
384 {
385 $groups = array();
386 $groups[] = array(
387 'INCLUDE' => true,
388 'ENDPOINT' => array('FIELDS' => array('MAILING_ID' => $campaignId)),
389 'GROUP_ID' => null,
391 'FILTER_ID' => null,
392 'CONNECTOR' => new Integration\Sender\Connectors\Subscriber
393 );
394 $groups[] = array(
395 'INCLUDE' => false,
396 'ENDPOINT' => array('FIELDS' => array('MAILING_ID' => $campaignId)),
397 'GROUP_ID' => null,
399 'FILTER_ID' => null,
400 'CONNECTOR' => new Integration\Sender\Connectors\UnSubscribers
401 );
402
403 return $groups;
404 }
405
411 public function isCorrectData(array $data, ?string $typeCode): bool
412 {
413 return (!isset($data[$typeCode]) || !$data[$typeCode])
414 && !(
415 isset($data['FIELDS'])
416 && (
417 (int)$data['FIELDS']['CRM_ENTITY_TYPE_ID'] === \CCrmOwnerType::Lead
418 || ($data['FIELDS']['CRM_ENTITY_TYPE'] === \CCrmOwnerType::LeadName)
419 )
420 && !empty($data['FIELDS']['CRM_ENTITY_ID'])
421 && ((int)$this->typeId === \Bitrix\Sender\Recipient\Type::CRM_LEAD_ID)
422 );
423 }
424
425 protected function getCampaignGroups($campaignId)
426 {
427 $groups = array();
428 $groupConnectorDb = MailingGroupTable::getList(array(
429 'select' => array(
430 'INCLUDE',
431 'CONNECTOR_ENDPOINT' => 'GROUP.GROUP_CONNECTOR.ENDPOINT',
432 'GROUP_ID'
433 ),
434 'filter' => array(
435 '=MAILING_ID' => $campaignId,
436 ),
437 'order' => array('INCLUDE' => 'DESC', 'GROUP_ID' => 'ASC')
438 ));
439 while($group = $groupConnectorDb->fetch())
440 {
441 $groups[] = array(
442 'INCLUDE' => $group['INCLUDE'],
443 'ENDPOINT' => $group['CONNECTOR_ENDPOINT'],
444 'GROUP_ID' => $group['GROUP_ID'],
445 'CONNECTOR' => null
446 );
447 }
448
449 return $groups;
450 }
451
452 protected function getLetterConnectors($letterId)
453 {
454 $groups = array();
455 $groupConnectors = Model\LetterSegmentTable::getList(array(
456 'select' => array(
457 'INCLUDE',
458 'STATUS' => 'SEGMENT.STATUS',
459 'FILTER_ID' => 'SEGMENT.GROUP_CONNECTOR.FILTER_ID',
460 'CONNECTOR_ENDPOINT' => 'SEGMENT.GROUP_CONNECTOR.ENDPOINT',
461 'SEGMENT_ID'
462 ),
463 'filter' => array(
464 '=LETTER_ID' => $letterId,
465 ),
466 'order' => array('INCLUDE' => 'DESC', 'LETTER_ID' => 'ASC')
467 ));
468 while($group = $groupConnectors->fetch())
469 {
470 $groups[] = array(
471 'INCLUDE' => $group['INCLUDE'],
472 'ENDPOINT' => $group['CONNECTOR_ENDPOINT'],
473 'GROUP_ID' => $group['SEGMENT_ID'],
474 'FILTER_ID' => $group['FILTER_ID'],
475 'STATUS' => $group['STATUS'],
476 'CONNECTOR' => null
477 );
478 }
479
480 return $groups;
481 }
482
483 private function isExcluded(bool $include, $row): bool
484 {
485 return $include
486 && (
487 $row['BLACKLISTED'] === 'Y' ||
488 $row['IS_UNSUB'] === 'Y' ||
489 $row['IS_MAILING_UNSUB'] === 'Y' ||
490 (
491 $this->messageConfiguration->get('APPROVE_CONFIRMATION', 'N') === 'Y' &&
492 Consent::isUnsub(
493 $row['CONSENT_STATUS'],
494 $row['CONSENT_REQUEST'],
495 $this->postingData['MESSAGE_TYPE']
496 )
497 )
498 );
499 }
500
501 protected function setRecipientIdentificators(array &$dataList, bool $include = true)
502 {
503 if (count($dataList) === 0)
504 {
505 return;
506 }
507
508 $codes = array_keys($dataList);
509 $tableName = ContactTable::getTableName();
511
512 $existed = [];
513 $contactCodeFilter = [];
514
515 $connection = Application::getConnection();
516 $primariesString = SqlBatch::getInString($codes);
517
518 $recipientDb = $connection->query(
519 "select
520 c.ID,
521 c.NAME,
522 c.CODE,
523 c.BLACKLISTED,
524 c.CONSENT_STATUS,
525 c.CONSENT_REQUEST,
526 c.IS_UNSUB,
527 s.IS_UNSUB as IS_MAILING_UNSUB " .
528 "from $tableName c " .
529 "left join $subsTableName s on " .
530 "c.ID = s.CONTACT_ID " .
531 "and s.MAILING_ID=" . (int) $this->postingData['MAILING_ID'] . " " .
532 "where c.TYPE_ID = " . (int) $this->typeId . " and c.CODE in ($primariesString)"
533 );
534 while ($row = $recipientDb->fetch())
535 {
536 $existed[] = $row['CODE'];
537 $dataList[$row['CODE']]['CONTACT_ID'] = $row['ID'];
538 $dataList[$row['CODE']]['EXCLUDED'] = $this->isExcluded($include, $row);
539
540 $name = $dataList[$row['CODE']]['NAME'] ?? null;
541 if ($name && $name !== $row['NAME'])
542 {
543 $contactCodeFilter[] = $row['CODE'];
544 }
545 }
546
547 // update existed contact names
548 $this->updateContacts($dataList, $contactCodeFilter);
549
550 // exit if no new contacts
551 if (count($existed) === count($codes))
552 {
553 return;
554 }
555
556 // add new contacts
557 $list = array_diff($codes, $existed);
558 $batch = [];
559 $insertDate = new DateTime();
560 $updateFieldsOnDuplicate = ['DATE_UPDATE'];
561 foreach ($list as $code)
562 {
563 $batchItem = [
564 'TYPE_ID' => $this->typeId,
565 'CODE' => $code,
566 'DATE_INSERT' => $insertDate,
567 'DATE_UPDATE' => $insertDate,
568 ];
569
570 $key = 'NAME';
571 if (isset($dataList[$key]) && $dataList[$key])
572 {
573 $batchItem[$key] = $dataList[$key];
574 if (!in_array($key, $updateFieldsOnDuplicate))
575 {
576 $updateFieldsOnDuplicate[] = $key;
577 }
578 }
579
580 $batch[] = $batchItem;
581 }
582
583
584 SqlBatch::insert($tableName, $batch, $updateFieldsOnDuplicate, ContactTable::getConflictFields());
585
586
587 $recipientDb = $connection->query(
588 "select ID, CODE " .
589 "from $tableName " .
590 "where TYPE_ID = " . (int) $this->typeId . " and CODE in ($primariesString)"
591 );
592 while ($row = $recipientDb->fetch())
593 {
594 $dataList[$row['CODE']]['CONTACT_ID'] = $row['ID'];
595 }
596 }
597
598 protected function checkUsedFields($entityType, $ids, $usedPersonalizeFields, &$dataList)
599 {
600 $usedFields = [];
601 foreach ($usedPersonalizeFields as $personalizeField)
602 {
603 foreach ($personalizeField as $usedField)
604 {
605 $usedFieldExploded = explode('.', $usedField);
606 if (
607 $entityType == $usedFieldExploded[0] &&
608 isset
609 (
610 $usedFieldExploded[1]
611 ))
612 {
613 unset($usedFieldExploded[0]);
614 $usedFields[$usedField] = implode('.', $usedFieldExploded);
615 }
616 }
617 }
618 $fields = Integration\Crm\Connectors\Helper::getData(
619 $entityType, $ids, $usedFields
620 );
621
622 foreach ($fields as &$entity)
623 {
624 foreach ($entity as $key => $field)
625 {
626 $entity[$entityType.'.'.$key] = $field;
627 unset($entity[$key]);
628 }
629 }
630
631 foreach($dataList as &$data)
632 {
633 if(
634 isset($fields[(int)$data['FIELDS']['CRM_ENTITY_ID']])
635 && $data['FIELDS']['CRM_ENTITY_TYPE'] === $entityType
636 )
637 {
638 $data['FIELDS'] = array_merge(
639 $data['FIELDS'],
640 $fields[$data['FIELDS']['CRM_ENTITY_ID']]
641 );
642 }
643 }
644
645 return $usedFields;
646 }
647
648 protected function fill(Connector\Base $connector, $group, $usedPersonalizeFields = [])
649 {
650 $count = 0;
651
652 $typeCode = $this->getTypeCode();
653
654 $isIncrementally = $connector instanceof Connector\IncrementallyConnector && $group['FILTER_ID'];
655 if ($isIncrementally)
656 {
657 $segmentBuilder = new SegmentDataBuilder($group['GROUP_ID'], $group['FILTER_ID'], $group['ENDPOINT']);
658
659 if (!$segmentBuilder->isBuildingCompleted())
660 {
661 throw new NotCompletedException();
662 }
663 }
664
665 $result = $isIncrementally
666 ? $segmentBuilder->getPreparedData()
667 : $connector->getResult();
668
669 while (true)
670 {
671 $dataList = array();
672 $maxPart = 500;
673
674 while ($data = $result->fetch())
675 {
676 if ($this->isCorrectData($data, $typeCode))
677 {
678 continue;
679 }
680
681 if (!isset($data[$typeCode]) && ((int)$this->typeId === Recipient\Type::CRM_LEAD_ID))
682 {
683 $data[$typeCode] = $data['FIELDS']['CRM_ENTITY_ID'];
684 }
685
686 $primary = Recipient\Normalizer::normalize($data[$typeCode], $this->typeId);
687 if ($primary == '')
688 {
689 continue;
690 }
691 $dataList[$primary] = $data;
692
693 $count++;
694
695 $maxPart--;
696 if ($maxPart == 0)
697 {
698 break;
699 }
700 }
701
702 if (count($dataList) === 0)
703 {
704 break;
705 }
706 $this->setRecipientIdentificators($dataList, $group['INCLUDE']);
707
708 if ($group['INCLUDE'])
709 {
710 // add address if not exists
711 if ($this->checkDuplicates)
712 {
713 $primariesString = SqlBatch::getInString(array_keys($dataList));
714 $connection = Application::getConnection();
715 $rowDb = $connection->query(
716 "select r.CODE " .
717 "from b_sender_posting_recipient pr, b_sender_contact r " .
718 "where pr.CONTACT_ID = r.ID " .
719 "and pr.POSTING_ID = " . (int) $this->postingId . " " .
720 "and r.TYPE_ID = " . (int) $this->typeId . " " .
721 "and r.CODE in ($primariesString)"
722 );
723 while ($row = $rowDb->fetch())
724 {
725 unset($dataList[$row['CODE']]);
726 }
727 }
728
729 if (empty($dataList))
730 {
731 continue;
732 }
733
734 if(
735 count($usedPersonalizeFields) > 0
736 )
737 {
738 $preparedFields = [];
739
740 foreach($dataList as $data)
741 {
742 if(!isset($data['FIELDS']))
743 {
744 continue;
745 }
746
747 $field = $data['FIELDS'];
748 if(!isset($preparedFields[$field['CRM_ENTITY_TYPE']]))
749 {
750 $preparedFields[$field['CRM_ENTITY_TYPE']] = [];
751 }
752 $preparedFields[$field['CRM_ENTITY_TYPE']][] = $field['CRM_ENTITY_ID'];
753 }
754
755 foreach ($preparedFields as $entityType => $ids)
756 {
757 $this->checkUsedFields(
758 $entityType, $ids, $usedPersonalizeFields, $dataList
759 );
760 }
761 }
762
763 $this->addPostingRecipients($dataList);
764 }
765 else
766 {
767 $this->removePostingRecipients($dataList);
768 }
769 }
770
771 if (!$group['GROUP_ID'])
772 {
773 return;
774 }
775
776 $this->incGroupCounters($group['GROUP_ID'], $count);
777 }
778
779 protected function removePostingRecipients(array &$list)
780 {
781 $primaries = array();
782 foreach($list as $code => $data)
783 {
784 if (!isset($data['CONTACT_ID']) || !$data['CONTACT_ID'])
785 {
786 continue;
787 }
788 $primaries[] = (int) $data['CONTACT_ID'];
789 }
790
791 if (count($primaries) === 0)
792 {
793 return;
794 }
795
796 $connection = Application::getConnection();
797 $primariesString = implode(',', $primaries);
798 $connection->query(
799 "delete " .
800 "from b_sender_posting_recipient " .
801 "where POSTING_ID = " . (int) $this->postingId . " " .
802 "and CONTACT_ID in (" . $primariesString . ")"
803 );
804 }
805
806 protected function updateContacts(array &$list, array $codeFilter)
807 {
808 $fields = [];
809 foreach ($codeFilter as $code)
810 {
811 if (!isset($list[$code]))
812 {
813 continue;
814 }
815
816 $item = $list[$code];
817 $fields[] = ['ID' => $item['CONTACT_ID'], 'NAME' => $item['NAME']];
818 }
819
820 SqlBatch::update(ContactTable::getTableName(), $fields);
821 }
822
823 protected function addPostingRecipients(array &$list)
824 {
825 $dataList = array();
826 foreach($list as $code => $data)
827 {
828 if (!isset($data['EXCLUDED']) || $data['EXCLUDED'])
829 {
830 continue;
831 }
832
833 $recipientInsert = array(
834 'CONTACT_ID' => (int) $data['CONTACT_ID'],
836 'POSTING_ID' => (int) $this->postingId,
837 'USER_ID' => null,
838 'FIELDS' => null
839 );
840
841 if (array_key_exists('USER_ID', $data) && intval($data['USER_ID']) > 0)
842 {
843 $recipientInsert['USER_ID'] = intval($data['USER_ID']);
844 }
845
846 if (array_key_exists('FIELDS', $data) && count($data['FIELDS']) > 0)
847 {
848 $recipientInsert['FIELDS'] = serialize($data['FIELDS']);
849 }
850
851 $dataList[] = $recipientInsert;
852 }
853
854 if(count($dataList) == 0)
855 {
856 return;
857 }
858
859 SqlBatch::insert(
861 $dataList,
862 ['USER_ID', 'FIELDS'],
864 );
865 }
866
867 protected function incGroupCounters($groupId = null, $count = 0)
868 {
869 if (!$groupId)
870 {
871 return;
872 }
873
874 if (array_key_exists($groupId, $this->groupCount))
875 {
876 $this->groupCount[$groupId] += $count;
877 }
878 else
879 {
880 $this->groupCount[$groupId] = $count;
881 }
882
883 }
884}
static getConnection($name="")
static loadMessages($file)
Definition loc.php:64
setRecipientIdentificators(array &$dataList, bool $include=true)
Definition builder.php:501
fill(Connector\Base $connector, $group, $usedPersonalizeFields=[])
Definition builder.php:648
static clean($postingId)
Definition builder.php:363
__construct($postingId=null, $checkDuplicates=true)
Definition builder.php:107
isCorrectData(array $data, ?string $typeCode)
Definition builder.php:411
incGroupCounters($groupId=null, $count=0)
Definition builder.php:867
updateContacts(array &$list, array $codeFilter)
Definition builder.php:806
getSubscriptionConnectors($campaignId)
Definition builder.php:383
removePostingRecipients(array &$list)
Definition builder.php:779
addPostingRecipients(array &$list)
Definition builder.php:823
run($postingId, $checkDuplicates=true)
Definition builder.php:125
checkUsedFields($entityType, $ids, $usedPersonalizeFields, &$dataList)
Definition builder.php:598
runForRecipientType($usedPersonalizeFields=[], $groups=[])
Definition builder.php:321
static actualize(int $groupId, bool $rebuild=false)
static deleteList(array $filter)
Definition posting.php:828