%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;
}
}