Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/modules/sender/lib/posting/threadstrategy/
Upload File :
Current File : /var/www/homesaver/www/bitrix/modules/sender/lib/posting/threadstrategy/abstractthreadstrategy.php

<?php

namespace Bitrix\Sender\Posting\ThreadStrategy;

use Bitrix\Main\Application;
use Bitrix\Main\DB;
use Bitrix\Main\DB\SqlExpression;
use Bitrix\Main\DB\SqlQueryException;
use Bitrix\Main\ORM\Query\Result;
use Bitrix\Main\Type\DateTime;
use Bitrix\Sender\Internals\Model\PostingThreadTable;
use Bitrix\Sender\Internals\SqlBatch;
use Bitrix\Sender\PostingRecipientTable;

abstract class AbstractThreadStrategy implements IThreadStrategy
{
	/**
	 * @var int
	 */
	protected $threadId;

	protected $postingId;

	protected $select;

	protected $filter;
	protected $runtime;

	public const THREAD_UNAVAILABLE = -1;
	public const THREAD_LOCKED = -2;
	public const THREAD_NEEDED = 1;

	/**
	 * Insert new posting threads with ignore of conflicts
	 *
	 * @return void
	 */
	public function fillThreads(): void
	{
		$insertData = [];

		\CTimeZone::Disable();
		for ($thread = 0; $thread < static::THREADS_COUNT; $thread++)
		{
			$insertData[] = [
				'THREAD_ID' => $thread,
				'POSTING_ID' => $this->postingId,
				'THREAD_TYPE' => static::THREADS_COUNT,
				'EXPIRE_AT' => new DateTime(),
			];
		}

		SqlBatch::insert(PostingThreadTable::getTableName(), $insertData);
		\CTimeZone::Enable();
	}

	/**
	 * @param $limit
	 *
	 * @return Result
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function getRecipients(int $limit): Result
	{
		static::setRuntime();
		static::setFilter();
		static::setSelect();

		// select all recipients of posting, only not processed
		$recipients = PostingRecipientTable::getList(
			[
				'select'  => $this->select,
				'filter'  => $this->filter,
				'runtime' => $this->runtime,
				'order' => ['STATUS' => 'DESC'],
				'limit'   => $limit
			]
		);

		$recipients->addFetchDataModifier(
			function($row)
			{
				$row['FIELDS'] = is_array($row['FIELDS']) ? $row['FIELDS'] : [];

				return $row;
			}
		);

		return $recipients;
	}

	abstract protected function setRuntime(): void;

	protected function setFilter() : void
	{
		$this->filter = ['=IS_UNSUB' => 'N'];
	}
	protected function setSelect(): void
	{
		$this->select = [
			'*',
			'NAME'                     => 'CONTACT.NAME',
			'CONTACT_CODE'             => 'CONTACT.CODE',
			'CONTACT_TYPE_ID'          => 'CONTACT.TYPE_ID',
			'CONTACT_IS_SEND_SUCCESS'  => 'CONTACT.IS_SEND_SUCCESS',
			'CONTACT_BLACKLISTED'      => 'CONTACT.BLACKLISTED',
			'CONTACT_UNSUBSCRIBED'      => 'CONTACT.IS_UNSUB',
			'CONTACT_CONSENT_STATUS'   => 'CONTACT.CONSENT_STATUS',
			'CONTACT_MAILING_UNSUBSCRIBED'     => 'MAILING_SUB.IS_UNSUB',
			'CONTACT_CONSENT_REQUEST'  => 'CONTACT.CONSENT_REQUEST',
			'CAMPAIGN_ID'              => 'POSTING.MAILING_ID',
		];
	}

	/**
	 * lock thread for duplicate select
	 * @return int|null
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function lockThread(): void
	{
		if(!static::checkLock())
		{
			return;
		}

		\CTimeZone::Disable();
		$thread = PostingThreadTable::getList(
			[
				"select" => ["THREAD_ID"],
				"filter" => [
					'=POSTING_ID' => $this->postingId,
					[
						'LOGIC' => 'OR',
						[
							'=STATUS' => PostingThreadTable::STATUS_NEW,
						],
						[
							'=STATUS'    => PostingThreadTable::STATUS_IN_PROGRESS,
							'<EXPIRE_AT' => new DateTime()
						]
					]
				],
				"limit"  => 1
			]
		)->fetchAll();
		\CTimeZone::enable();

		if (!isset($thread[0]) && !isset($thread[0]["THREAD_ID"]))
		{
			return;
		}
		$this->threadId = $thread[0]["THREAD_ID"];
		$this->updateStatus(PostingThreadTable::STATUS_IN_PROGRESS);
		$this->unlock();
	}

	/**
	 * Check threads is available and not need to insert
	 * @return int|null
	 * @throws \Bitrix\Main\ArgumentException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function checkThreads(): ?int
	{
		$thread = PostingThreadTable::getList(
			[
				"select" => ["THREAD_ID"],
				"filter" => [
					'=POSTING_ID' => $this->postingId,
				],
				"limit"  => 1
			]
		)->fetchAll();

		if (isset($thread[0]) && isset($thread[0]["THREAD_ID"]))
		{
			return self::THREAD_UNAVAILABLE;
		}

		return self::THREAD_NEEDED;
	}

	/**
	 * Lock table from selecting of the thread
	 *
	 * @return bool
	 */
	protected function lock()
	{
		$connection = Application::getInstance()->getConnection();

		return $connection->lock($this->getLockName());
	}

	/**
	 * update status with expire date
	 * @param $threadId
	 * @param $status
	 */
	public function updateStatus(string $status): bool
	{
		if($status === PostingThreadTable::STATUS_DONE && !$this->checkToFinalizeStatus())
		{
			$status = PostingThreadTable::STATUS_NEW;
		}

		try
		{
			\CTimeZone::Disable();

			$tableName   = PostingThreadTable::getTableName();
			$expireAt    = (new \DateTime())->modify("+10 minutes")->format('Y-m-d H:i:s');
			$updateQuery = 'UPDATE '.$tableName.' 
			SET 
			STATUS = \''.$status.'\',
			EXPIRE_AT = \''.$expireAt.'\'
			WHERE 
			THREAD_ID = '.$this->threadId.' 
			AND POSTING_ID = '.$this->postingId;
			Application::getConnection()->query($updateQuery);
		}
		catch (\Exception $e)
		{
			return false;
		}
		finally
		{
			\CTimeZone::Enable();
		}

		return true;
	}

	/**
	 * Unlock table for select
	 *
	 * @return bool
	 */
	protected function unlock()
	{
		$connection = Application::getInstance()->getConnection();

		return $connection->unlock($this->getLockName());
	}

	/**
	 * Get lock name
	 *
	 * @return string
	 */
	private function getLockName(): string
	{
		return "posting_thread_$this->postingId";
	}

	/**
	 * checking that all threads are completed
	 * @return bool
	 */
	public function hasUnprocessedThreads(): bool
	{
		try
		{
			$filter = [
				'@STATUS' => [PostingThreadTable::STATUS_NEW, PostingThreadTable::STATUS_IN_PROGRESS],
				'=POSTING_ID' => $this->postingId,
			];

			if ($this->threadId !== null)
			{
				$filter['!=THREAD_ID'] = $this->threadId;
			}

			$threads = PostingThreadTable::getList(
				[
					"select" => ["THREAD_ID"],
					"filter" => $filter,
				]
			)->fetchAll();
		}
		catch (\Exception $e)
		{
		}

		return !empty($threads);
	}

	/**
	 * get current thread id
	 * @return int
	 */
	public function getThreadId(): ?int
	{
		return $this->threadId;
	}

	/**
	 * get last thread number
	 * @return int
	 */
	public function lastThreadId(): int
	{
		return static::THREADS_COUNT - 1;
	}

	/**
	 * @param int $postingId
	 *
	 * @return TenThreadsStrategy
	 */
	public function setPostingId(int $postingId): IThreadStrategy
	{
		$this->postingId = $postingId;

		return $this;
	}

	/**
	 * wait while threads are calculating
	 * @return bool
	 */
	protected function checkLock()
	{
		for($i = 0; $i <= static::lastThreadId(); $i++)
		{
			if ($this->lock())
			{
				return true;
			}
			sleep(rand(1,7));
		}
		return false;
	}

	/**
	 * Finalize thread activity
	 */
	public function finalize()
	{
		if(!$this->checkToFinalizeStatus())
		{
			return false;
		}

		$tableName = PostingThreadTable::getTableName();
		$sqlHelper = Application::getConnection()->getSqlHelper();
		$query = 'DELETE FROM ' . $sqlHelper->quote($tableName) . ' WHERE POSTING_ID=' . intval($this->postingId);
		try
		{
			Application::getConnection()->query($query);
		}
		catch (SqlQueryException $e)
		{
			return false;
		}

		return true;
	}

	private function checkToFinalizeStatus()
	{
		if($this->threadId < static::lastThreadId())
		{
			return true;
		}

		return !static::hasUnprocessedThreads();
	}

	/**
	 * Returns true if sending not available
	 * @return bool
	 */
	public function isProcessLimited(): bool
	{
		return false;
	}

}