%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/sender/lib/entity/ |
Current File : //home/bitrix/www/bitrix/modules/sender/lib/entity/letter.php |
<?php /** * Bitrix Framework * @package bitrix * @subpackage sender * @copyright 2001-2012 Bitrix */ namespace Bitrix\Sender\Entity; use Bitrix\Main\DB; use Bitrix\Main\Error; use Bitrix\Main\InvalidOperationException; use Bitrix\Main\Localization\Loc; use Bitrix\Main\Result; use Bitrix\Main\Type\Date; use Bitrix\Sender\Message as MainMessage; use Bitrix\Sender\Dispatch; use Bitrix\Sender\Posting; use Bitrix\Sender\Templates; use Bitrix\Sender\TemplateTable; use Bitrix\Sender\Recipient; use Bitrix\Sender\Security; use Bitrix\Sender\Integration; use Bitrix\Sender\Internals\Model\LetterTable; use Bitrix\Sender\Internals\Model\LetterSegmentTable; Loc::loadMessages(__FILE__); class Letter extends Base { /** @var null|array $postingData Posting data. */ protected $postingData = null; /** @var MainMessage\Adapter $message Message. */ protected $message; /** @var Dispatch\Duration $duration Duration. */ protected $duration; /** @var Dispatch\Method $method Method. */ protected $method; /** @var Dispatch\State $state State. */ protected $state; /** @var Posting\Counter $counter Counter. */ protected $counter; /** * Get filter fields. * * @return array */ protected static function getFilterFields() { return array( array( 'CODE' => 'IS_ADS', 'VALUE' => 'N', 'FILTER' => '=IS_ADS' ), ); } /** * Get data manager. * * @return \Bitrix\Main\Entity\DataManager */ public static function getDataClass() { return LetterTable::getEntity()->getDataClass(); } /** * Get list. * * @param array $parameters Parameters. * @return DB\Result */ public static function getList(array $parameters = array()) { if (!isset($parameters['select'])) { $parameters['select'] = array( '*', 'SITE_ID' => 'CAMPAIGN.SITE_ID', 'DATE_SEND' => 'CURRENT_POSTING.DATE_SEND', 'DATE_PAUSE' => 'CURRENT_POSTING.DATE_PAUSE', 'DATE_SENT' => 'CURRENT_POSTING.DATE_SENT', 'COUNT_SEND_ALL' => 'CURRENT_POSTING.COUNT_SEND_ALL', 'COUNT_SEND_NONE' => 'CURRENT_POSTING.COUNT_SEND_NONE', 'COUNT_SEND_ERROR' => 'CURRENT_POSTING.COUNT_SEND_ERROR', 'COUNT_SEND_SUCCESS' => 'CURRENT_POSTING.COUNT_SEND_SUCCESS', 'COUNT_SEND_DENY' => 'CURRENT_POSTING.COUNT_SEND_DENY', 'USER_NAME' => 'CREATED_BY_USER.NAME', 'USER_LAST_NAME' => 'CREATED_BY_USER.LAST_NAME', 'USER_ID' => 'CREATED_BY', ); } if (!isset($parameters['filter'])) { $parameters['filter'] = array(); } foreach (static::getFilterFields() as $field) { if (!$field['FILTER']) { continue; } if (isset($parameters['filter'][$field['FILTER']])) { $current = $parameters['filter'][$field['FILTER']]; if (is_array($field['VALUE'])) { if (!is_array($current) && in_array($current, $field['VALUE'])) { continue; } } } $parameters['filter'][$field['FILTER']] = $field['VALUE']; } return LetterTable::getList($parameters); } /** * Create instance by ID. * * @param integer|null $id ID. * @param array $messageCodes Message codes. * @return static|Letter|Ad|Rc|null */ public static function createInstanceById($id = null, array $messageCodes = []) { $code = null; if ($id) { $row = LetterTable::getRow([ 'select' => ['MESSAGE_CODE'], 'filter' => ['=ID' => $id], ]); if ($row) { $code = $row['MESSAGE_CODE']; } else { $id = null; } } $instance = self::createInstanceByCode($code, $messageCodes); if (!$instance) { return null; } if ($id) { $instance->load($id); } elseif ($instance) { $instance->set('MESSAGE_CODE', $code); } return $instance; } /** * Create instance by array data. * * @param array $data Data. * @param array $messageCodes Message codes. * @return static|Letter|Ad|Rc|null */ public static function createInstanceByArray(array $data, array $messageCodes = []) { $code = empty($data['MESSAGE_CODE']) ? null : $data['MESSAGE_CODE']; $instance = self::createInstanceByCode($code, $messageCodes); $instance->loadByArray($data); return $instance; } /** * Create instance by posting ID. * * @param integer $postingId Posting ID. * @return static|Ad|null */ public static function createInstanceByPostingId($postingId) { $row = LetterTable::getList(array( 'select' => array('ID', 'IS_ADS'), 'filter' => array('=POSTING_ID' => $postingId), 'limit' => 1 ))->fetch(); if (!$row) { return new static(); } if ($row['IS_ADS'] === 'Y') { return new Ad($row['ID']); } else { return new static($row['ID']); } } /** * Create instance by contact ID. * * @param integer $contactId Contact ID. * @param array $messageCodes Message codes. * @return static|Ad|null */ public static function createInstanceByContactId($contactId, array $messageCodes = []) { $typeId = Contact::create($contactId)->get('TYPE_ID') ?: Recipient\Type::EMAIL; switch ($typeId) { case Recipient\Type::EMAIL: $code = MainMessage\iBase::CODE_MAIL; break; case Recipient\Type::PHONE: $code = MainMessage\iBase::CODE_SMS; break; default: return null; } return self::createInstanceByCode($code, $messageCodes); } protected static function createInstanceByCode($code = null, array $messageCodes = []) { if (!$code && empty($messageCodes)) { return null; } if (!$code) { $code = current($messageCodes); } if (empty($messageCodes)) { $messageCodes = [$code]; } if (!in_array($code, $messageCodes)) { return null; } $message = MainMessage\Adapter::create($code); if ($message->isAds()) { $instance = new Ad(); } elseif ($message->isReturnCustomer()) { $instance = new Rc(); } else { $instance = new Letter(); } return $instance; } /** * Get default data. * * @return array */ protected function getDefaultData() { return array( 'TITLE' => '', 'MESSAGE_ID' => '', 'MESSAGE_CODE' => MainMessage\Adapter::CODE_MAIL, 'SEGMENTS_INCLUDE' => array(), 'SEGMENTS_EXCLUDE' => array(), ); } /** * Save data. * * @param integer|null $id ID. * @param array $data Data. * @return integer|null */ protected function saveData($id = null, array $data) { if(!$this->getMessage()->isAvailable()) { $this->addError(Loc::getMessage('SENDER_ENTITY_LETTER_ERROR_NOT_AVAILABLE')); return $id; } $segmentsInclude = $data['SEGMENTS_INCLUDE']; $segmentsExclude = $data['SEGMENTS_EXCLUDE']; foreach (static::getFilterFields() as $field) { if (!$field['CODE']) { continue; } if (is_array($field['VALUE'])) { if (empty($data[$field['CODE']]) || !in_array($data[$field['CODE']], $field['VALUE'])) { $data[$field['CODE']] = current($field['VALUE']); } } else { $data[$field['CODE']] = $field['VALUE']; } } $this->filterDataByEntityFields(LetterTable::getEntity(), $data); $initialId = $id; $previousData = $id ? LetterTable::getRowById($id) : null; $previousData = $previousData ?: array(); // segment check if(!is_array($segmentsInclude) || count($segmentsInclude) == 0) { if ($data['IS_TRIGGER'] <> 'Y' && $previousData['IS_TRIGGER'] <> 'Y') { $this->addError(Loc::getMessage('SENDER_ENTITY_LETTER_ERROR_NO_SEGMENTS')); return $id; } } $segmentsExclude = is_array($segmentsExclude) ? $segmentsExclude : array(); // campaign setting if (!isset($data['CAMPAIGN_ID'])) { $data['CAMPAIGN_ID'] = Campaign::getDefaultId(); $this->set('CAMPAIGN_ID', $data['CAMPAIGN_ID']); } // parent letter setting for triggers if (!$id && $data['IS_TRIGGER'] === 'Y') { if (empty($data['PARENT_ID'])) { $previousLetter = (new Chain)->load($data['CAMPAIGN_ID'])->getLast(); if ($previousLetter && $previousLetter->getId() != $this->getId()) { $data['PARENT_ID'] = $previousLetter->getId(); } } if (!isset($data['TIME_SHIFT'])) { $data['TIME_SHIFT'] = 1440; } $data['STATUS'] = Dispatch\State::WAITING; $data['REITERATE'] = 'Y'; } if ($this->filterDataByChanging($data, $previousData)) { $id = $this->saveByEntity(LetterTable::getEntity(), $id, $data); } if ($this->hasErrors()) { return $id; } if ($this->canChangeSegments()) { $this->saveDataSegments($id, $segmentsInclude, $segmentsExclude); } // update template use count $this->updateTemplateUseCount($data, $previousData); // change status for init recipients if (!$initialId && !$this->isTrigger()) { $this->setId($id)->getState()->init(); } return $id; } protected function prepareSearchContent() { $content = $this->getSearchBuilder()->getContent(); $content->addUserById($this->get('CREATED_BY')); $content->addText($this->get('TITLE')); $config = $this->getMessage()->getConfiguration(); foreach ($config->getOptions() as $option) { $value = $option->getValue(); if (!$value) { continue; } switch ($option->getType()) { case $option::TYPE_EMAIL: $content->addEmail($value); break; case $option::TYPE_HTML: case $option::TYPE_MAIL_EDITOR: $content->addHtmlLayout($value); continue; case $option::TYPE_TEXT: case $option::TYPE_STRING: case $option::TYPE_PRESET_STRING: case $option::TYPE_SMS_EDITOR: $content->addText($value); break; default: continue; } } return $this; } protected function saveDataSegments($id, array $segmentsInclude, array $segmentsExclude) { $segmentsExclude = array_unique($segmentsExclude); $segmentsInclude = array_unique($segmentsInclude); $segmentsInclude = array_diff($segmentsInclude, $segmentsExclude); $segmentsList = array( array( 'list' => $segmentsExclude, 'include' => false ), array( 'list' => $segmentsInclude, 'include' => true ), ); $oldSegments = $this->loadDataSegments($id); LetterSegmentTable::delete(array('LETTER_ID' => $id)); $isChanged = false; foreach ($segmentsList as $segments) { foreach ($segments['list'] as $segmentId) { $result = LetterSegmentTable::add(array( 'LETTER_ID' => $id, 'SEGMENT_ID' => $segmentId, 'INCLUDE' => $segments['include'], )); $result->isSuccess(); } $typeCode = $segments['include'] ? 'INCLUDE' : 'EXCLUDE'; $newest = self::getArrayDiffNewest($segments['list'], $oldSegments[$typeCode]); $removed = self::getArrayDiffRemoved($segments['list'], $oldSegments[$typeCode]); if (count($newest) === 0 && count($removed) === 0) { continue; } if (count($newest) > 0) { Segment::updateUseCounters($newest, $segments['include']); } $isChanged = true; } if ($isChanged && $this->getId() && $this->get('POSTING_ID')) { Posting\Builder::create()->run($this->get('POSTING_ID'), false); } } private static function getArrayDiffNewest(array $current, array $old) { return array_diff($current, $old); } private static function getArrayDiffRemoved(array $current, array $old) { return array_diff($old, $current); } protected function updateTemplateUseCount(array $data, array $previousData) { if (!$data['TEMPLATE_TYPE'] || !$data['TEMPLATE_ID']) { return false; } if (Templates\Type::getCode(Templates\Type::USER) !== $data['TEMPLATE_TYPE']) { return false; } if ($data['TEMPLATE_ID'] === $previousData['TEMPLATE_ID'] && $data['TEMPLATE_TYPE'] === $previousData['TEMPLATE_TYPE']) { return false; } return TemplateTable::incUseCount($data['TEMPLATE_ID']); } /** * Load data. * * @param integer $id ID. * @return array|null */ public function loadData($id) { $data = static::getList(array( 'filter' => array( '=ID' => $id ) ))->fetch(); if (!is_array($data)) { return null; } $segments = $this->loadDataSegments($id); foreach ($segments as $typeCode => $list) { $data["SEGMENTS_$typeCode"] = $list; } return $data; } /** * Return true if it have statistics. * * @return bool */ public function hasStatistics() { return ( $this->getState()->wasStartedSending() && !$this->getState()->isSendingPlanned() && $this->getMessage()->hasStatistics() ); } /** * Return true if can change segments. * * @return bool */ public function canChangeSegments() { return !$this->getState()->wasPostingBuilt(); } /** * Load segments data. * * @param integer $id ID. * @return array */ public function loadDataSegments($id) { $data = array('INCLUDE' => array(), 'EXCLUDE' => array()); $segments = LetterSegmentTable::getList(array( 'filter'=>array( '=LETTER_ID'=> $id ) )); foreach($segments as $segment) { if ($segment['INCLUDE']) { $data['INCLUDE'][] = $segment['SEGMENT_ID']; } else { $data['EXCLUDE'][] = $segment['SEGMENT_ID']; } } return $data; } /** * Return true if can change template. * * @return bool */ public function canChangeTemplate() { if ($this->getState()->isFinished()) { return false; } //return $this->getMessage()->getCode() !== MainMessage\Adapter::CODE_MAIL; return true; } /** * Get Message instance. * * @return MainMessage\Adapter */ public function getMessage() { $messageCode = $this->get('MESSAGE_CODE') ?: MainMessage\Adapter::CODE_MAIL; $messageId = $this->get('MESSAGE_ID') ?: null; if ($this->message && $this->message->getCode() === $messageCode) { return $this->message; } $this->message = MainMessage\Adapter::create($messageCode); $createdById = $this->get('CREATED_BY') ?: Security\User::current()->getId(); $this->message->getConfiguration()->set('LETTER_CREATED_BY_ID', $createdById); $this->message->setSiteId($this->get('SITE_ID')); $this->message->loadConfiguration($messageId); return $this->message; } /** * Is support heat map. * * @return bool */ public function isSupportHeatMap() { return $this->getMessage()->getCode() == MainMessage\Adapter::CODE_MAIL; } /** * Is support reiterate run. * * @return bool */ public function isSupportReiterate() { if (in_array($this->getMessage()->getCode(), ['rc_lead', 'rc_deal'])) { return true; } return !Integration\Bitrix24\Service::isPortal(); } /** * Get campaign ID. * * @return mixed */ public function getCampaignId() { return $this->get('CAMPAIGN_ID'); } /** * Get duration instance. * * @return Dispatch\Duration */ public function getDuration() { if ($this->duration) { return $this->duration; } $this->duration = new Dispatch\Duration($this); return $this->duration; } /** * Get state instance. * * @return Dispatch\State */ public function getState() { if ($this->state) { return $this->state; } $this->state = new Dispatch\State($this); return $this->state; } /** * Get method instance. * * @return Dispatch\Method */ public function getMethod() { if ($this->method) { return $this->method; } $this->method = new Dispatch\Method($this); return $this->method; } /** * Get counter instance. * * @return Posting\Counter */ public function getCounter() { if ($this->counter) { return $this->counter; } $this->counter = new Posting\Counter($this); return $this->counter; } /** * Get Tester instance. * * @return MainMessage\Tester */ protected function getTester() { return $this->getMessage()->getTester(); } /** * Is reiterate letter. * * @return bool */ public function isReiterate() { return $this->get('REITERATE') === 'Y'; } /** * Is trigger letter. * * @return bool */ public function isTrigger() { return $this->get('IS_TRIGGER') === 'Y'; } /** * Is support testing. * * @return bool */ public function isSupportTesting() { return $this->getTester()->isSupport(); } /** * Remove. * * @return bool */ public function remove() { return $this->removeByEntity(LetterTable::getEntity(), $this->getId()); } /** * Remove by letter ID. * * @param integer $id Letter ID. * @return bool */ public static function removeById($id) { return static::create()->removeByEntity(LetterTable::getEntity(), $id); } /** * Copy. * * @return integer|null */ public function copy() { $configurationId = $this->getMessage()->getConfiguration()->getId(); if (!$configurationId) { return null; } $result = $this->getMessage()->copyConfiguration($configurationId); if (!$result->isSuccess() || !$result->getId()) { return null; } $data = array( 'CAMPAIGN_ID' => $this->get('CAMPAIGN_ID'), 'MESSAGE_CODE' => $this->get('MESSAGE_CODE'), 'MESSAGE_ID' => $result->getId(), 'REITERATE' => $this->get('REITERATE'), 'TEMPLATE_TYPE' => $this->get('TEMPLATE_TYPE'), 'TEMPLATE_ID' => $this->get('TEMPLATE_ID'), 'CREATED_BY' => $this->getUser()->getId(), 'IS_TRIGGER' => $this->get('IS_TRIGGER'), 'TITLE' => Loc::getMessage('SENDER_ENTITY_LETTER_COPY_PREFIX') . ' ' . $this->get('TITLE'), 'SEGMENTS_INCLUDE' => $this->get('SEGMENTS_INCLUDE'), 'SEGMENTS_EXCLUDE' => $this->get('SEGMENTS_EXCLUDE'), ); $instance = static::create()->mergeData($data); $instance->save(); $this->getErrorCollection()->add($instance->getErrors()); return $instance->getId(); } /** * Send test message to recipient. * * @param array $codes Recipient codes. * @param array $parameters Parameters. * @return Result */ public function test(array $codes, array $parameters = array()) { return $this->getTester()->send($codes, $parameters); } /** * Plan sending. * * @param Date $date Date. * @return bool */ public function plan(Date $date) { try { return $this->getState()->plan($date); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Wait sending. * * @return bool */ public function wait() { if ($this->isTrigger()) { try { return $this->getState()->wait(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } if (!$this->isReiterate()) { $this->errors->setError(new Error('Entity is not reiterate.')); return false; } try { $scheduleTime = Dispatch\MethodSchedule::parseTimesOfDay($this->get('TIMES_OF_DAY')); $scheduleMonths = Dispatch\MethodSchedule::parseMonthsOfYear($this->get('MONTHS_OF_YEAR')); $scheduleWeekDays = Dispatch\MethodSchedule::parseDaysOfWeek($this->get('DAYS_OF_WEEK')); $scheduleMonthDays = Dispatch\MethodSchedule::parseDaysOfMonth($this->get('DAYS_OF_MONTH')); $method = (new Dispatch\MethodSchedule($this)) ->setMonthsOfYear($scheduleMonths) ->setDaysOfMonth($scheduleMonthDays) ->setDaysOfWeek($scheduleWeekDays) ->setTime($scheduleTime[0], $scheduleTime[1]); $this->getState()->wait($method); return true; } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Send. * * @return bool */ public function send() { try { return $this->getState()->send(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Send errors. * * @return bool */ public function sendErrors() { try { return $this->getState()->sendErrors(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Stop. * * @return bool */ public function stop() { try { return $this->getState()->stop(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Halt. * * @return bool */ public function halt() { try { return $this->getState()->halt(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Resume. * * @return bool */ public function resume() { try { return $this->getState()->resume(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Pause. * * @return bool */ public function pause() { try { return $this->getState()->pause(); } catch (InvalidOperationException $exception) { $this->errors->setError(new Error($exception->getMessage())); return false; } } /** * Get last posting data. * * @return array */ public function getLastPostingData() { $defaults = array(); if (!$this->getId()) { return $defaults; } if ($this->postingData !== null) { return $this->postingData; } $this->postingData = $defaults; $postingFilter = array( '=ID' => $this->getId(), //'!POSTING.DATE_SENT' => null ); $postings = static::getList(array( 'select' => array( 'POSTING_ID', 'LETTER_ID' => 'ID', 'CAMPAIGN_ID', 'TITLE' => 'TITLE', 'MAILING_NAME' => 'CAMPAIGN.NAME', 'DATE_SENT' => 'POSTING.DATE_SENT', 'COUNT_SEND_ERROR' => 'POSTING.COUNT_SEND_ERROR', 'CREATED_BY' => 'CREATED_BY', 'CREATED_BY_NAME' => 'CREATED_BY_USER.NAME', 'CREATED_BY_LAST_NAME' => 'CREATED_BY_USER.LAST_NAME', 'CREATED_BY_SECOND_NAME' => 'CREATED_BY_USER.SECOND_NAME', 'CREATED_BY_LOGIN' => 'CREATED_BY_USER.LOGIN', 'CREATED_BY_TITLE' => 'CREATED_BY_USER.TITLE', ), 'filter' => $postingFilter, 'limit' => 1, 'order' => array('POSTING.DATE_SENT' => 'DESC', 'POSTING.DATE_CREATE' => 'DESC'), )); if ($postingData = $postings->fetch()) { $this->postingData = $postingData + $this->postingData; } return $this->postingData; } }