Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/mail/callback/ |
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/mail/callback/controller.php |
<?php namespace Bitrix\Main\Mail\Callback; use Bitrix\Main\ArgumentException; use Bitrix\Main\Context; use Bitrix\Main\Loader; use Bitrix\Main\Localization\Loc; use Bitrix\Main\Mail\Address; use Bitrix\Main\SystemException; use Bitrix\Main\Web\Json; use Bitrix\Main\Mail\Internal; use Bitrix\Main\Mail\SenderSendCounter; use Bitrix\Main\Mail\Sender; use CIMNotify; use CModule; /** * Class Controller * * @package Bitrix\Main\Mail\Callback */ class Controller { public const STATUS_DEFERED = 'defered'; public const STATUS_BOUNCED = 'bounced'; public const STATUS_DELIVERED = 'delivered'; public const DESC_AUTH = 'AUTH_ERROR'; public const DESC_UNKNOWN_USER = 'UNKNOWN_USER'; public const DESC_UNROUTEABLE = 'UNROUTEABLE'; private const DESC_SMTP_LIMITED = 'SMTP_LIMITED'; /** @var string $id ID of mail. */ protected $id; /** @var Result $result Result instance. */ protected $result; /** @var Config $config Config instance. */ protected $config; /** @var Address $address Address instance. */ protected $address; /** @var string[] $blacklist Black list of emails. */ protected $blacklist = []; /** @var string[] $smtpLimited List of emails which out of limit. */ protected $smtpLimited = []; /** @var bool $answerExceptions Flush exceptions in answer. */ protected static $answerExceptions = true; /** @var bool $enableItemErrors Ignore item errors. */ protected $enableItemErrors = false; /** @var int $countItems. */ protected $countItems = 0; /** @var int $countItems. */ protected $countItemsProcessed = 0; /** @var int $countItems. */ protected $countItemsError = 0; /** * Run controller. * * @param string $data Data. * @param array $parameters Parameters. * @return void */ public static function run($data = null, array $parameters = []) { $request = Context::getCurrent()->getRequest(); if ($data === null) { $data = $request->getPostList()->getRaw('data'); } if (!isset($parameters['IGNORE_ITEM_ERRORS'])) { $parameters['ENABLE_ITEM_ERRORS'] = mb_strtoupper($request->get('enableItemErrors')) === 'Y'; } $instance = new self(); if ($parameters['ENABLE_ITEM_ERRORS']) { $instance->enableItemErrors(); } try { if (empty($data)) { self::giveAnswer(true, 'No input data.'); } try { $data = Json::decode($data); } catch (\Exception $exception) { } if (!is_array($data)) { self::giveAnswer(true, 'Wrong data.'); } if (!isset($data['list']) || !is_array($data['list'])) { self::giveAnswer(true, 'Parameter `list` required.'); } $instance->processList($data['list']); self::giveAnswer(false, ['list' => $instance->getCounters()]); } catch (SystemException $exception) { self::giveAnswer( true, [ 'text' => self::$answerExceptions ? $exception->getMessage() : null, 'list' => $instance->getCounters() ] ); } } /** * Give answer. * * @param bool $isError Data. * @param string|array|null $answer Answer. * @return void */ public static function giveAnswer($isError = false, $answer = null) { $response = Context::getCurrent()->getResponse(); $response->addHeader('Status', $isError ? '422' : '200'); $response->addHeader('Content-Type', 'application/json'); if (!is_array($answer)) { $answer = [ 'text' => $answer ?: null ]; } $answer['error'] = $isError; if (empty($answer['text'])) { $answer['text'] = $isError ? 'Unknown error' : 'Success'; } $answer = Json::encode($answer); \CMain::FinalActions($answer); exit; } /** * Controller constructor. */ public function __construct() { $this->config = new Config(); $this->result = new Result(); $this->address = new Address(); } /** * Enable item errors. * * @return $this */ public function enableItemErrors() { $this->enableItemErrors = true; return $this; } protected function validateItem($item) { if (empty($item['id'])) { throw new ArgumentException('Field `id` is required for item.'); } if(!preg_match("/[a-zA-Z0-1=]{3,}/", $item['id'])) { throw new ArgumentException('Field `id` has disallowed chars.'); } if (empty($item['sign'])) { throw new ArgumentException("Field `sign` is required for item with id `{$item['id']}`."); } if (empty($item['status'])) { throw new ArgumentException("Field `status` is required for item with id `{$item['id']}`."); } if (empty($item['email'])) { throw new ArgumentException("Field `email` is required for item with id `{$item['id']}`."); } } /** * Process list. * * @param array $list List of items. * @return void * @throws SystemException */ public function processList($list) { $this->countItems = count($list); $this->blacklist = []; $this->smtpLimited = []; foreach ($list as $index => $item) { $this->countItemsProcessed++; try { $result = $this->processItem($item); if (!$result) { $this->countItemsError++; } } catch (SystemException $exception) { $this->countItemsError++; if ($this->enableItemErrors) { throw $exception; } } } Internal\BlacklistTable::insertBatch($this->blacklist); $this->decreaseLimit(); } /** * Decrease limits. * * @return void */ public function decreaseLimit() { if (!$this->smtpLimited) { return; } foreach ($this->smtpLimited as $email) { Sender::setEmailLimit( $email, SenderSendCounter::DEFAULT_LIMIT, false, ); } } /** * Process item. * * @param array $item Item data. * @return bool * @throws SystemException */ public function processItem($item) { $this->validateItem($item); $this->config->unpackId($item['id']); if (!$this->config->verifySignature($item['sign'])) { throw new SystemException('Item parameter `sign` is invalid.'); } if (!$this->config->getEntityId()) { return false; } $email = $this->address->set($item['email'])->getEmail(); if (!$email) { return false; } if (!empty($item['sender']) && self::isSmtpLimited($item['statusDescription']) ) { $this->smtpLimited[] = $this->address->set($item['sender'])->getEmail(); } $this->result ->setModuleId($this->config->getModuleId()) ->setEntityType($this->config->getEntityType()) ->setEntityId($this->config->getEntityId()) ->setEmail($email) ->setDateSent((int) $item['completedAt']) ->setError(self::isStatusError($item['status'])) ->setPermanentError(self::isStatusPermanentError($item['status'])) ->setBlacklistable(self::isBlacklistable($item['statusDescription'])) ->setDescription($item['statusDescription']) ->setMessage($item['message']); if ($this->result->isPermanentError() && $this->result->isBlacklistable()) { $this->blacklist[] = $this->result->getEmail(); } $this->result->sendEvent(); return true; } /** * Get counters * * @return array */ public function getCounters() { return [ 'all' => $this->countItems, 'processed' => $this->countItemsProcessed, 'errors' => $this->countItemsError, ]; } /** * Return true if status is error. * * @param string $status Status. * @return bool */ public static function isStatusError($status) { return in_array($status, [self::STATUS_DEFERED, self::STATUS_BOUNCED]); } /** * Return true if status is permanent error. * * @param string $status Status. * @return bool */ public static function isStatusPermanentError($status) { return $status === self::STATUS_BOUNCED; } /** * Return true if status descriptions is blacklistable. * * @param string $description Description. * @return bool */ public static function isBlacklistable($description) { return $description && in_array($description, [self::DESC_UNKNOWN_USER, self::DESC_UNROUTEABLE]); } /** * Return true if status descriptions is available for smtp. * * @param string $description Description. * @return bool */ private static function isSmtpLimited(string $description) { return $description && in_array($description, [self::DESC_SMTP_LIMITED], true); } }