Your IP : 3.146.34.239
<?php
/**
* Bitrix Framework
* @package bitrix
* @subpackage sender
* @copyright 2001-2012 Bitrix
*/
namespace Bitrix\Sender\Stat;
use Bitrix\Main\Context;
use Bitrix\Main\Entity\ExpressionField;
use Bitrix\Main\Type\Date;
use Bitrix\Main\UserTable;
use Bitrix\Main\Localization\Loc;
use Bitrix\Sender\MailingChainTable;
use Bitrix\Sender\PostingTable;
use Bitrix\Sender\PostingClickTable;
use Bitrix\Sender\MailingSubscriptionTable;
use Bitrix\Sender\Entity;
use Bitrix\Sender\Message;
use Bitrix\Main\Web\Uri;
Loc::loadMessages(__FILE__);
class Statistics
{
CONST AVERAGE_EFFICIENCY = 0.15;
CONST USER_OPTION_FILTER_NAME = 'statistics_filter';
/** @var Filter */
protected $filter;
/** @var integer */
protected $cacheTtl = 3600;
/** @var array */
protected $counters = null;
/** @var integer */
protected $userId = null;
/**
* Create instance.
*
* @param Filter $filter Filter.
* @return static
*/
public static function create(Filter $filter = null)
{
return new static($filter);
}
/**
* Constructor.
*
* @param Filter $filter Filter.
*/
public function __construct(Filter $filter = null)
{
if ($filter)
{
$this->filter = $filter;
}
else
{
$this->filter = new Filter();
}
}
/**
* Set cache TTL.
*
* @param integer $cacheTtl Cache ttl.
* @return $this
*/
public function setCacheTtl($cacheTtl)
{
$this->cacheTtl = $cacheTtl;
return $this;
}
/**
* Set user Id.
*
* @param integer $userId User Id.
* @return $this
*/
public function setUserId($userId)
{
$this->userId = $userId;
return $this;
}
/**
* Get cache TTL.
*
* @return integer
*/
public function getCacheTtl()
{
return $this->cacheTtl;
}
/**
* Return filter.
*
* @return Filter
*/
public function getFilter()
{
return $this->filter;
}
/**
* Return filter.
*
* @param string $name Filter name.
* @param string $value Filter value.
* @return $this
*/
public function filter($name, $value = null)
{
$this->filter->set($name, $value);
return $this;
}
/**
* Init filter from request.
*
* @return $this
*/
public function initFilterFromRequest()
{
$request = Context::getCurrent()->getRequest();
if ($request->isPost())
{
$list = $this->filter->getNames();
foreach ($list as $name)
{
$this->filter->set($name, (string) $request->get($name));
}
$this->saveFilterToUserOption();
}
else
{
$this->initFilterFromUserOption();
}
return $this;
}
/**
* Init filter from request.
*
* @return $this
*/
protected function initFilterFromUserOption()
{
$isFilterSet = false;
if ($this->userId)
{
$userOptionFilters = \CUserOptions::getOption(
'sender',
self::USER_OPTION_FILTER_NAME,
array(),
false,
$this->userId
);
$list = $this->filter->getNames();
foreach ($list as $name)
{
if (!isset($userOptionFilters[$name]) || !$userOptionFilters[$name])
{
continue;
}
$this->filter->set($name, (string) $userOptionFilters[$name]);
$isFilterSet = true;
}
}
if (!$isFilterSet && !$this->filter->get('period'))
{
$this->filter->set('period', Filter::PERIOD_MONTH);
}
return $this;
}
protected function saveFilterToUserOption()
{
if (!$this->userId)
{
return;
}
$filter = array();
$list = $this->filter->getNames();
foreach ($list as $name)
{
$value = $this->filter->get($name);
if (!$value)
{
continue;
}
$filter[$name] = $value;
}
\CUserOptions::setOption('sender', self::USER_OPTION_FILTER_NAME, $filter, false, $this->userId);
}
protected static function calculateEfficiency($counters, $maxEfficiency = null)
{
$efficiency = self::div('CLICK', 'READ', $counters);
if (!$maxEfficiency)
{
$maxEfficiency = self::AVERAGE_EFFICIENCY * 2;
}
$efficiency = $efficiency > $maxEfficiency ? $maxEfficiency : $efficiency;
return self::getCounterCalculation('EFFICIENCY', $efficiency, $maxEfficiency);
}
protected static function div($dividendCode, $dividerCode, $items)
{
$divider = 0;
foreach ($items as $item)
{
if ($item['CODE'] == $dividerCode)
{
$divider = (float) $item['VALUE'];
break;
}
}
if ($divider == 0)
{
return 0;
}
$dividend = 0;
foreach ($items as $item)
{
if ($item['CODE'] == $dividendCode)
{
$dividend = (float) $item['VALUE'];
$dividend = $dividend > $divider ? $divider : $dividend;
break;
}
}
return $dividend / $divider;
}
protected static function formatNumber($number, $num = 1)
{
$formatted = number_format($number, $num, '.', ' ');
$formatted = substr($formatted, -($num + 1)) == '.' . str_repeat('0', $num) ? substr($formatted, 0, -2) : $formatted;
return $formatted;
}
protected static function getCounterCalculation($code, $value, $percentBase = 0)
{
$value = (float) $value;
$percentValue = $percentBase > 0 ? $value / $percentBase : 0;
return array(
'CODE' => $code,
'VALUE' => round($value, 3),
'VALUE_DISPLAY' => self::formatNumber($value, 1),
'PERCENT_VALUE' => round($percentValue, $code == 'UNSUB' ? 3 : 3),
'PERCENT_VALUE_DISPLAY' => self::formatNumber($percentValue * 100, $code == 'UNSUB' ? 1 : 1),
);
}
protected function getMappedFilter()
{
$filter = array(
'!=STATUS' => PostingTable::STATUS_NEW,
'=MAILING.IS_TRIGGER' => 'N',
'=MAILING_CHAIN.MESSAGE_CODE' => Message\iBase::CODE_MAIL
);
$fieldsMap = array(
'chainId' => '=MAILING_CHAIN_ID',
'periodFrom' => '>DATE_SENT',
'periodTo' => '<DATE_SENT',
'mailingId' => '=MAILING_ID',
'postingId' => '=ID',
'authorId' => '=MAILING_CHAIN.CREATED_BY',
);
return $this->filter->getMappedArray($fieldsMap, $filter);
}
/**
* Return efficiency.
*
* @return float
*/
public function getEfficiency()
{
return self::calculateEfficiency($this->getCounters());
}
/**
* Return dynamic of counters.
*
* @return array
*/
public function getCountersDynamic()
{
$list = array();
$filter = $this->getMappedFilter();
$select = array(
'SEND_ALL' => 'COUNT_SEND_ALL',
'SEND_ERROR' => 'COUNT_SEND_ERROR',
'SEND_SUCCESS' => 'COUNT_SEND_SUCCESS',
'READ' => 'COUNT_READ',
'CLICK' => 'COUNT_CLICK',
'UNSUB' => 'COUNT_UNSUB'
);
$runtime = array(
new ExpressionField('CNT', 'COUNT(%s)', 'ID'),
new ExpressionField('DATE', 'DATE(%s)', 'DATE_SENT'),
);
foreach ($select as $alias => $fieldName)
{
$runtime[] = new ExpressionField($alias, 'SUM(%s)', $fieldName);
}
$select = array_keys($select);
$select[] = 'DATE';
$select[] = 'CNT';
$listDb = PostingTable::getList(array(
'select' => $select,
'filter' => $filter,
'runtime' => $runtime,
'order' => array('DATE' => 'ASC'),
'cache' => array('ttl' => $this->getCacheTtl(), 'cache_joins' => true)
));
while($item = $listDb->fetch())
{
$date = null;
foreach ($item as $name => $value)
{
if (!in_array($name, array('DATE')))
{
continue;
}
if ($item['DATE'])
{
/** @var Date $date */
$date = $item['DATE']->getTimestamp();
}
}
$counters = array();
foreach ($item as $name => $value)
{
if (!in_array($name, array('READ', 'CLICK', 'UNSUB')))
{
continue;
}
else
{
$base = $item['SEND_SUCCESS'];
}
$counter = self::getCounterCalculation($name, $value, $base);
$counter['DATE'] = $date;
$counters[] = $counter;
$list[$name][] = $counter;
}
$effCounter = self::calculateEfficiency($counters, 1);
$effCounter['DATE'] = $date;
$list['EFFICIENCY'][] = $effCounter;
}
return $list;
}
/**
* Return counters.
*
* @return array
*/
public function getCounters()
{
if ($this->counters !== null)
{
return $this->counters;
}
$list = array();
$filter = $this->getMappedFilter();
$select = array(
'SEND_ALL' => 'COUNT_SEND_ALL',
'SEND_ERROR' => 'COUNT_SEND_ERROR',
'SEND_SUCCESS' => 'COUNT_SEND_SUCCESS',
'READ' => 'COUNT_READ',
'CLICK' => 'COUNT_CLICK',
'UNSUB' => 'COUNT_UNSUB'
);
$runtime = array();
foreach ($select as $alias => $fieldName)
{
$runtime[] = new ExpressionField($alias, 'SUM(%s)', $fieldName);
}
$listDb = PostingTable::getList(array(
'select' => array_keys($select),
'filter' => $filter,
'runtime' => $runtime,
'cache' => array('ttl' => $this->getCacheTtl(), 'cache_joins' => true)
));
while ($item = $listDb->fetch())
{
foreach ($item as $name => $value)
{
if (substr($name, 0, 4) == 'SEND')
{
$base = $item['SEND_ALL'];
}
else
{
$base = $item['SEND_SUCCESS'];
}
$list[] = self::getCounterCalculation($name, $value, $base);
}
}
$this->counters = $list;
return $list;
}
/**
* Return subscribers.
*
* @return array
*/
public function getCounterPostings()
{
$query = PostingTable::query();
$query->addSelect(new ExpressionField('CNT', 'COUNT(1)'));
$query->setFilter($this->getMappedFilter());
$query->setCacheTtl($this->getCacheTtl());
$query->cacheJoins(true);
$result = $query->exec()->fetch();
return self::getCounterCalculation('POSTINGS', $result['CNT']);
}
/**
* Return subscribers.
*
* @return array
*/
public function getCounterSubscribers()
{
$filter = array('=IS_UNSUB' => 'N');
$map = array(
'mailingId' => '=MAILING_ID',
'periodFrom' => '>DATE_INSERT',
'periodTo' => '<DATE_INSERT',
);
$filter = $this->filter->getMappedArray($map, $filter);
$query = MailingSubscriptionTable::query();
$query->addSelect(new ExpressionField('CNT', 'COUNT(1)'));
$query->setFilter($filter);
$query->setCacheTtl($this->getCacheTtl());
$query->cacheJoins(true);
$result = $query->exec()->fetch();
return self::getCounterCalculation('SUBS', $result['CNT']);
}
/**
* Return click links.
*
* @param integer $limit Limit.
* @return array
*/
public function getClickLinks($limit = 15)
{
$list = array();
$clickDb = PostingClickTable::getList(array(
'select' => array('URL', 'CNT'),
'filter' => array(
'=POSTING_ID' => $this->filter->get('postingId'),
),
'runtime' => array(
new ExpressionField('CNT', 'COUNT(%s)', 'ID'),
),
'group' => array('URL'),
'order' => array('CNT' => 'DESC'),
'limit' => $limit
));
while($click = $clickDb->fetch())
{
$list[] = $click;
}
// TODO: temporary block! Remove
if (!empty($list))
{
$letter = Entity\Letter::createInstanceByPostingId($this->filter->get('postingId'));
$linkParams = $letter->getMessage()->getConfiguration()->get('LINK_PARAMS');
if (!$linkParams)
{
return $list;
}
$parametersTmp = [];
parse_str($linkParams, $parametersTmp);
if (!is_array($parametersTmp) || empty($parametersTmp))
{
return $list;
}
$linkParams = array_keys($parametersTmp);
$groupedList = [];
foreach ($list as $index => $item)
{
$item['URL'] = (new Uri($item['URL']))
->deleteParams($linkParams)
->getLocator();
if (!isset($groupedList[$item['URL']]))
{
$groupedList[$item['URL']] = 0;
}
$groupedList[$item['URL']] += $item['CNT'];
}
$list = [];
foreach ($groupedList as $url => $cnt)
{
$list[] = ['URL' => $url, 'CNT' => $cnt];
}
}
return $list;
}
/**
* Return read counter data by day time.
*
* @param int $step Step.
* @return array
*/
public function getReadingByDayTime($step = 2)
{
$list = array();
for ($i = 0; $i < 24; $i++)
{
$list[$i] = array(
'CNT' => 0,
'CNT_DISPLAY' => 0,
'DAY_HOUR' => $i,
'DAY_HOUR_DISPLAY' => (strlen($i) == 1 ? '0' : '') . $i . ':00',
);
}
$filter = $this->getMappedFilter();
$readDb = PostingTable::getList(array(
'select' => array('DAY_HOUR', 'CNT'),
'filter' => $filter,
'runtime' => array(
new ExpressionField('CNT', 'COUNT(%s)', 'POSTING_READ.ID'),
new ExpressionField('DAY_HOUR', 'HOUR(%s)', 'POSTING_READ.DATE_INSERT'),
),
'order' => array('DAY_HOUR' => 'ASC'),
));
while($read = $readDb->fetch())
{
$read['DAY_HOUR'] = intval($read['DAY_HOUR']);
if (array_key_exists($read['DAY_HOUR'], $list))
{
$list[$read['DAY_HOUR']]['CNT'] = $read['CNT'];
$list[$read['DAY_HOUR']]['CNT_DISPLAY'] = self::formatNumber($read['CNT'], 0);
}
}
if ($step > 1)
{
for ($i = 0; $i < 24; $i+=$step)
{
for ($j = 1; $j < $step; $j++)
{
$list[$i]['CNT'] += $list[$i + $j]['CNT'];
unset($list[$i + $j]);
}
$list[$i]['CNT_DISPLAY'] = self::formatNumber($list[$i]['CNT'], 0);
}
}
$list = array_values($list);
return $list;
}
/**
* Return recommended sending time for mailing.
*
* @param integer $chainId Chain Id.
* @return string
*/
public function getRecommendedSendTime($chainId = null)
{
$timeList = $this->getReadingByDayTime(1);
$len = count($timeList);
$weightList = array();
for ($i = 0; $i <= $len; $i++)
{
$j = $i + 1;
if ($j > $len)
{
$j = 0;
}
else if ($j < 0)
{
$j = 23;
}
$weight = $timeList[$i]['CNT'] + $timeList[$j]['CNT'];
$weightList[$i] = $weight;
}
$deliveryTime = 0;
if ($chainId)
{
$listDb = PostingTable::getList(array(
'select' => array('COUNT_SEND_ALL'),
'filter' => array(
'=MAILING_CHAIN_ID' => $chainId
),
'order' => array('DATE_CREATE' => 'DESC'),
));
if ($item = $listDb->fetch())
{
$deliveryTime = intval($item['COUNT_SEND_ALL'] * 1/10 * 1/3600);
}
}
if ($deliveryTime <= 0)
{
$deliveryTime = 1;
}
arsort($weightList);
foreach ($weightList as $i => $weight)
{
$i -= $deliveryTime;
if ($i >= $len)
{
$i = $i - $len;
}
else if ($i < 0)
{
$i = $len + $i;
}
$timeList[$i]['DELIVERY_TIME'] = $deliveryTime;
return $timeList[$i];
}
return null;
}
/**
* Return chain list.
*
* @param integer $limit Limit.
* @return array
*/
public function getChainList($limit = 20)
{
$filter = $this->getMappedFilter();
$listDb = PostingTable::getList(array(
'select' => array(
'MAX_DATE_SENT',
'CHAIN_ID' => 'MAILING_CHAIN_ID',
'TITLE' => 'MAILING_CHAIN.TITLE',
'MAILING_ID',
'MAILING_NAME' => 'MAILING.NAME',
),
'filter' => $filter,
'runtime' => array(
new ExpressionField('MAX_DATE_SENT', 'MAX(%s)', 'DATE_SENT'),
),
//'group' => array('CHAIN_ID', 'TITLE', 'SUBJECT', 'MAILING_ID', 'MAILING_NAME'),
'order' => array('MAX_DATE_SENT' => 'DESC'),
'limit' => $limit,
'cache' => array('ttl' => $this->getCacheTtl(), 'cache_joins' => true)
));
$list = array();
while ($item = $listDb->fetch())
{
$dateSentFormatted = '';
if ($item['MAX_DATE_SENT'])
{
$dateSentFormatted = \FormatDate('x', $item['MAX_DATE_SENT']->getTimestamp());
}
$list[] = array(
'ID' => $item['CHAIN_ID'],
'NAME' => $item['TITLE'] ? $item['TITLE'] : $item['SUBJECT'],
'MAILING_ID' => $item['MAILING_ID'],
'MAILING_NAME' => $item['MAILING_NAME'],
'DATE_SENT' => (string) $item['MAX_DATE_SENT'],
'DATE_SENT_FORMATTED' => $dateSentFormatted,
);
}
return $list;
}
/**
* Return global filter data.
*
* @return array
*/
public function getGlobalFilterData()
{
$period = $this->getFilter()->get('period');
return array(
array(
'name' => 'authorId',
'value' => $this->getFilter()->get('authorId'),
'list' => $this->getAuthorList(),
),
array(
'name' => 'period',
'value' => $period ? $period : Filter::PERIOD_MONTH,
'list' => $this->getPeriodList(),
)
);
}
/**
* Return period list.
*
* @return array
*/
protected function getPeriodList()
{
$list = array(
Filter::PERIOD_WEEK,
Filter::PERIOD_MONTH,
Filter::PERIOD_MONTH_3,
Filter::PERIOD_MONTH_6,
Filter::PERIOD_MONTH_12
);
$result = array();
foreach ($list as $period)
{
$result[] = array(
'ID' => $period,
'NAME' => Loc::getMessage('SENDER_STAT_STATISTICS_FILTER_PERIOD_' . $period)
);
}
return $result;
}
/**
* Return author list.
*
* @return array
*/
protected function getAuthorList()
{
$listDb = MailingChainTable::getList(array(
'select' => array('CREATED_BY'),
'group' => array('CREATED_BY'),
'limit' => 100,
'cache' => array('ttl' => $this->getCacheTtl(), 'cache_joins' => true)
));
$userList = array();
while ($item = $listDb->fetch())
{
if (!$item['CREATED_BY'])
{
continue;
}
$userList[] = $item['CREATED_BY'];
}
$list = array();
$list[] = array(
'ID' => 'all',
'NAME' => Loc::getMessage('SENDER_STAT_STATISTICS_FILTER_AUTHOR_FROM_ALL')
);
/** @var CUser */
global $USER;
if (is_object($USER) && $USER->getID())
{
$list[] = array(
'ID' => $USER->getID(),
'NAME' => Loc::getMessage('SENDER_STAT_STATISTICS_FILTER_AUTHOR_FROM_ME')
);
}
$listDb = UserTable::getList(array(
'select' => array(
'ID',
'TITLE',
'NAME',
'SECOND_NAME',
'LAST_NAME',
'LOGIN',
),
'filter' => array('=ID' => $userList),
'order' => array('NAME' => 'ASC')
));
while ($item = $listDb->fetch())
{
$name = \CUser::formatName(\CSite::getNameFormat(true), $item, true, true);
$list[] = array(
'ID' => $item['ID'],
'NAME' => $name,
);
}
return $list;
}
}