Your IP : 18.224.38.170


Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/vote/lib/
Upload File :
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/vote/lib/vote.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage vote
 * @copyright 2001-2016 Bitrix
 */
namespace Bitrix\Vote;
use \Bitrix\Main\AccessDeniedException;
use Bitrix\Main\Context;
use \Bitrix\Main\Entity;
use Bitrix\Main\ErrorCollection;
use \Bitrix\Main\ORM\Data\AddResult;
use \Bitrix\Main\ORM\Data\Result;
use \Bitrix\Main\Application;
use \Bitrix\Main\Error;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\ArgumentException;
use \Bitrix\Main\ORM\Data\UpdateResult;
use \Bitrix\Main\ORM\Event;
use \Bitrix\Main\ORM\Fields\DatetimeField;
use \Bitrix\Main\ORM\Fields\ExpressionField;
use \Bitrix\Main\ORM\Fields\TextField;
use \Bitrix\Main\ORM\Fields\Validators\DateValidator;
use \Bitrix\Main\ORM\Fields\IntegerField;
use \Bitrix\Main\ORM\Fields\EnumField;
use \Bitrix\Main\ORM\Fields\StringField;
use \Bitrix\Main\ORM\Fields\BooleanField;
use \Bitrix\Main\ORM\Fields\Relations\Reference;
use \Bitrix\Main\ORM\Fields\FieldError;
use \Bitrix\Main\ORM\EntityError;
use \Bitrix\Main\ORM\Query\Join;
use \Bitrix\Main\Text\HtmlFilter;
use Bitrix\Main\Web\MimeType;
use \Bitrix\Vote\Base\BaseObject;
use \Bitrix\Vote\Vote\Option;
use \Bitrix\Vote\Vote\EventLimits;
use \Bitrix\Vote\Vote\Anonymity;
use \Bitrix\Main\Type\DateTime;
use \Bitrix\Main\FileTable;


Loc::loadMessages(__FILE__);

/**
 * Class VoteTable
 * Fields:
 * <ul>
 * <li> ID int mandatory
 * <li> CHANNEL_ID int,
 * <li> C_SORT int,
 * <li> ACTIVE bool mandatory default "Y",
 * <li> ANONYMITY int,
 * <li> NOTIFY bool mandatory default "N",
 * <li> AUTHOR_ID int,
 * <li> AUTHOR reference,
 * <li> TIMESTAMP_X datetime,
 * <li> DATE_START datetime,
 * <li> DATE_END datetime,
 * <li> URL string(255) NULL,
 * <li> COUNTER int,
 * <li> TITLE string(255),
 * <li> DESCRIPTION text,
 * <li> DESCRIPTION_TYPE string(4),
 * <li> IMAGE_ID int,
 * <li> EVENT1 string(255),
 * <li> EVENT2 string(255),
 * <li> EVENT3 string(255),
 * <li> UNIQUE_TYPE int (coded in binary system:
 * UNIQUE_TYPE_SESSION - 1 (00001)
 * UNIQUE_TYPE_COOKIE - 2 (00010)
 * UNIQUE_TYPE_IP - 4 (00100)
 * UNIQUE_TYPE_USER_ID - 8 (01000)
 * UNIQUE_TYPE_USER_ID_NEW - 16 (10000)
 * UNIQUE_TYPE = (UNIQUE_TYPE_SESSION | UNIQUE_TYPE_COOKIE | UNIQUE_TYPE_IP | UNIQUE_TYPE_USER_ID | UNIQUE_TYPE_USER_ID_NEW)
 * <li> KEEP_IP_SEC int,
 * <li> OPTIONS int,
 * </ul>
 *
 *
 * DO NOT WRITE ANYTHING BELOW THIS
 *
 * <<< ORMENTITYANNOTATION
 * @method static EO_Vote_Query query()
 * @method static EO_Vote_Result getByPrimary($primary, array $parameters = array())
 * @method static EO_Vote_Result getById($id)
 * @method static EO_Vote_Result getList(array $parameters = array())
 * @method static EO_Vote_Entity getEntity()
 * @method static \Bitrix\Vote\EO_Vote createObject($setDefaultValues = true)
 * @method static \Bitrix\Vote\EO_Vote_Collection createCollection()
 * @method static \Bitrix\Vote\EO_Vote wakeUpObject($row)
 * @method static \Bitrix\Vote\EO_Vote_Collection wakeUpCollection($rows)
 */
class VoteTable extends Entity\DataManager
{
	/**
	 * Returns DB table name for entity
	 *
	 * @return string
	 */
	public static function getTableName()
	{
		return "b_vote";
	}

	/**
	 * Returns entity map definition.
	 * @return array
	 * @throws ArgumentException
	 * @throws \Bitrix\Main\SystemException
	 */
	public static function getMap()
	{
		$now = Application::getInstance()->getConnection()->getSqlHelper()->getCurrentDateTimeFunction();
		return array(
			(new IntegerField("ID", ["primary" => true, "autocomplete" => true])),
			(new IntegerField("CHANNEL_ID", ["required" => true])),
			(new IntegerField("C_SORT", ["default_value" => 100])),
			(new BooleanField("ACTIVE", ["values" => ["N", "Y"], "default_value" => "Y"])),
			(new IntegerField("ANONYMITY", ["default_value" => Anonymity::PUBLICLY])),
			(new EnumField("NOTIFY", ["values" => ["N", "Y", "I"], "default_value" => "N"])),
			(new IntegerField("AUTHOR_ID")),
			(new Reference("AUTHOR", \Bitrix\Main\UserTable::class, Join::on("this.AUTHOR_ID", "ref.ID"))),
			(new DatetimeField("TIMESTAMP_X", ["default_value" => function(){return new DateTime();}])),
			(new DatetimeField("DATE_START", ["default_value" => function(){return new DateTime();}, "required" => true, "validation" => function() {
				return [
					new DateValidator,
					[__CLASS__, "validateActivityDate"]
				];
			}])),
			(new DatetimeField("DATE_END", ["default_value" => function(){
				$time = new DateTime();
				$time->add("1D");
				return $time;
			}, "required" => true, "validation" => function() {
				return [
					new DateValidator,
					[__CLASS__, "validateActivityDate"]
				];
			}])),
			(new StringField("URL", ["size" => 255])),
			(new IntegerField("COUNTER")),
			(new StringField("TITLE", ["size" => 255])),
			(new TextField("DESCRIPTION")),
			(new EnumField("DESCRIPTION_TYPE", ["values" => ["text", "html"], "default_value" => "text"])),
			(new IntegerField("IMAGE_ID")),
			(new Reference("IMAGE", FileTable::class, Join::on("this.IMAGE_ID", "ref.ID"))),
			(new StringField("EVENT1", ["size" => 255])),
			(new StringField("EVENT2", ["size" => 255])),
			(new StringField("EVENT3", ["size" => 255])),
			(new IntegerField("UNIQUE_TYPE", ["default_value" => EventLimits::BY_IP|EventLimits::BY_USER_ID])),
			(new IntegerField("KEEP_IP_SEC", ["default_value" => 604800])), // one week
			(new IntegerField("OPTIONS", ["default_value" => Option::ALLOW_REVOTE])),
			(new ExpressionField("LAMP",
				"CASE ".
					"WHEN (%s='Y' AND %s='Y' AND %s <= {$now} AND {$now} <= %s AND %s='Y') THEN 'yellow' ".
					"WHEN (%s='Y' AND %s='Y' AND %s <= {$now} AND {$now} <= %s AND %s!='Y') THEN 'green' ".
					"ELSE 'red' ".
				"END",
				["CHANNEL.ACTIVE", "ACTIVE", "DATE_START", "DATE_END", "CHANNEL.VOTE_SINGLE",
				"CHANNEL.ACTIVE", "ACTIVE", "DATE_START", "DATE_END", "CHANNEL.VOTE_SINGLE"])),
			(new Reference("CHANNEL", ChannelTable::class, Join::on("this.CHANNEL_ID", "ref.ID"))),
			(new Reference("QUESTION", QuestionTable::class, Join::on("this.ID", "ref.VOTE_ID"))),
			(new Reference("USER", \Bitrix\Main\UserTable::class, Join::on("this.AUTHOR_ID", "ref.ID"))),
		);
	}
	/**
	 * @param mixed $value   Value to check.
	 * @param array $primary Has no use in this function.
	 * @param array $row     Has no use in this function.
	 * @param DateTimeField $field   Field metadata.
	 * @return FieldError|bool
	 */
	public static function validateActivityDate($value, $primary, $row, $field)
	{
		/**@var $field */
		if (empty($value))
			return new FieldError(
				$field, Loc::getMessage("VOTE_ERROR_DATE_VOTE_IS_EMPTY"), $field->getName()
			);

		return true;
	}

	/**
	 * @param \Bitrix\Main\ORM\Event $event
	 * @throws \Bitrix\Main\ObjectException
	 * @return \Bitrix\Main\ORM\EventResult
	 */
	public static function onBeforeAdd(\Bitrix\Main\ORM\Event $event)
	{
		$result = new \Bitrix\Main\ORM\EventResult();
		if (($events = GetModuleEvents("vote", "onBeforeVoteAdd", true)) && !empty($events))
		{
			/** @var array $data */
			$data = $event->getParameter("fields");
			foreach ($events as $ev)
			{
				if (ExecuteModuleEventEx($ev, array(&$data)) === false)
				{
					$result->addError(new EntityError("Error: ".serialize($ev), "event"));
					return $result;
				}
			}
			if ($data != $event->getParameter("fields"))
			{
				$result->modifyFields($data);
			}
		}
		return self::modifyData($event, $result);
	}
	/**
	 * @param \Bitrix\Main\ORM\Event $event
	 * @return void
	 */
	public static function onAfterAdd(\Bitrix\Main\ORM\Event $event)
	{
		$id = $event->getParameter("id");
		$id = is_array($id) && array_key_exists("ID", $id) ? $id["ID"] : $id;
		$fields = $event->getParameter("fields");
		/***************** Event onAfterVoteAdd ****************************/
		foreach (GetModuleEvents("vote", "onAfterVoteAdd", true) as $event)
			ExecuteModuleEventEx($event, [$id, $fields]);
		/***************** /Event ******************************************/
	}
	/**
	 * @param \Bitrix\Main\ORM\Event $event
	 * @return \Bitrix\Main\ORM\EventResult|void
	 * @throws \Bitrix\Main\ObjectException
	 */
	public static function onBeforeUpdate(\Bitrix\Main\ORM\Event $event)
	{
		$result = new \Bitrix\Main\ORM\EventResult();
		if (($events = GetModuleEvents("vote", "onBeforeVoteUpdate", true)) && !empty($events))
		{
			/** @var array $data */
			$data = $event->getParameter("fields");
			$id = $event->getParameter("id");
			$id = is_array($id) && array_key_exists("ID", $id) ? $id["ID"] : $id;
			foreach ($events as $ev)
			{
				if (ExecuteModuleEventEx($ev, array($id, &$data)) === false)
				{
					$result->addError(new EntityError("Error: ".serialize($ev), "event"));
					return $result;
				}
			}
			if ($data != $event->getParameter("fields"))
			{
				$result->modifyFields($data);
			}
		}
		return self::modifyData($event, $result);
	}

	/**
	 * @param \Bitrix\Main\ORM\Event $event
	 * @return void
	 */
	public static function onAfterUpdate(\Bitrix\Main\ORM\Event $event)
	{
		$id = $event->getParameter("id");
		$id = is_array($id) && array_key_exists("ID", $id) ? $id["ID"] : $id;
		$fields = $event->getParameter("fields");
		/***************** Event onAfterVoteAdd ****************************/
		foreach (GetModuleEvents("vote", "onAfterVoteUpdate", true) as $event)
			ExecuteModuleEventEx($event, [$id, $fields]);
		/***************** /Event ******************************************/
	}
	/**
	 * @param \Bitrix\Main\ORM\Event $event
	 * @param \Bitrix\Main\ORM\EventResult $result
	 * @return \Bitrix\Main\ORM\EventResult
	 * @throws \Bitrix\Main\ObjectException
	 */
	private static function modifyData(\Bitrix\Main\ORM\Event $event, \Bitrix\Main\ORM\EventResult $result)
	{
		$data = array_merge($event->getParameter("fields"), $result->getModified());
		$fields = [];

		if (isset($data["UNIQUE_TYPE"]) && (
				!($data["UNIQUE_TYPE"] & \Bitrix\Vote\Vote\EventLimits::BY_USER_AUTH) &&
				($data["UNIQUE_TYPE"] & \Bitrix\Vote\Vote\EventLimits::BY_USER_DATE_REGISTER ||
					$data["UNIQUE_TYPE"] & \Bitrix\Vote\Vote\EventLimits::BY_USER_ID)
			))
			$fields["UNIQUE_TYPE"] = $data["UNIQUE_TYPE"] | \Bitrix\Vote\Vote\EventLimits::BY_USER_AUTH;

		foreach (["TIMESTAMP_X", "DATE_START", "DATE_END"] as $key)
		{
			if (isset($data[$key]) && !($data[$key] instanceof DateTime))
				$fields[$key] = DateTime::createFromUserTime($data[$key]);
		}

		//region check image
		if (array_key_exists("IMAGE_ID", $data))
		{
			if ($str = \CFile::CheckImageFile($data["IMAGE_ID"]))
			{
				$result->addError(new FieldError(static::getEntity()->getField("IMAGE_ID"), $str));
			}
			else
			{
				$fields["IMAGE_ID"] = $data["IMAGE_ID"];
				$fields["IMAGE_ID"]["MODULE_ID"] = "vote";
				if ($id = $event->getParameter("id"))
				{
					$id = is_integer($id) ? $id : $id["ID"];
					if ($id > 0 && ($vote = VoteTable::getById($id)->fetch()) && ($vote["IMAGE_ID"] > 0))
					{
						$fields["IMAGE_ID"]["old_file"] = $vote["IMAGE_ID"];
					}
				}
				if (\CFile::SaveForDB($fields, "IMAGE_ID", "") === false)
				{
					$result->unsetField("IMAGE_ID");
				}
			}
		}
		//endregion
		if (!empty($fields))
		{
			$result->modifyFields(array_merge($result->getModified(), $fields));
		}
		return $result;
	}
	/**
	 * @param Result $result
	 * @param mixed $primary
	 * @param array $data
	 * @throws ArgumentException
	 * @throws \Bitrix\Main\SystemException
	 */
	public static function checkFields(Result $result, $primary, array $data)
	{
		parent::checkFields($result, $primary, $data);
		if ($result->isSuccess())
		{
			try
			{
				$vote = null;
				//region check activity dates
				/**@var $date["start"] \Bitrix\Main\Type\DateTime */
				/**@var $date["end"] \Bitrix\Main\Type\DateTime */
				$params = null;
				if ($result instanceof AddResult)
				{
					$params = [
						"ID" => null,
						"ACTIVE" => (array_key_exists("ACTIVE", $data) ? $data["ACTIVE"] : "Y"),
						"CHANNEL_ID" => $data["CHANNEL_ID"],
						"DATE_START" => $data["DATE_START"],
						"DATE_END" => $data["DATE_END"]
					];
				}
				else if (array_key_exists("CHANNEL_ID", $data) ||
					array_key_exists("ACTIVE", $data) && $data["ACTIVE"] == "Y" ||
					array_key_exists("DATE_START", $data) ||
					array_key_exists("DATE_END", $data)
				)
				{
					// if it is need to move to other channel or activate or change the dates
					$vote = Vote::loadFromId($primary["ID"]);
					$params = [
						"ID" => $primary["ID"],
						"ACTIVE" => (array_key_exists("ACTIVE", $data) ? $data["ACTIVE"] : $vote["ACTIVE"]),
						"CHANNEL_ID" => (array_key_exists("CHANNEL_ID", $data) ? $data["CHANNEL_ID"] : $vote["CHANNEL_ID"]),
						"DATE_START" => (array_key_exists("DATE_START", $data) ? $data["DATE_START"] : $vote["DATE_START"]),
						"DATE_END" => (array_key_exists("DATE_END", $data) ? $data["DATE_END"] : $vote["DATE_END"])
					];
				}
				if (!is_null($params))
				{
					$params["DATE_START"] = static::getEntity()->getField("DATE_START")->cast($params["DATE_START"]);
					$params["DATE_END"] = static::getEntity()->getField("DATE_END")->cast($params["DATE_END"]);
					if (array_key_exists("DATE_END", $data))
						$data["DATE_END"] = $params["DATE_END"];
					if (!($params["DATE_START"] instanceof DateTime) || !($params["DATE_END"] instanceof DateTime))
						$result->addError(new FieldError(
							static::getEntity()->getField("DATE_START"),
							Loc::getMessage("VOTE_ERROR_DATE_VOTE_IS_WRONG")
						));
					else if ($params["DATE_START"]->getTimestamp() > $params["DATE_END"]->getTimeStamp())
					{
						$result->addError(new FieldError(
							static::getEntity()->getField("DATE_START"),
							Loc::getMessage("VOTE_ERROR_DATE_START_LATER_THAN_END")
						));
					}
					else if ($params["ACTIVE"] == "Y")
					{
						/**@var $channel Channel */
						$channel = Channel::loadFromId($params["CHANNEL_ID"]);
						if ($channel["VOTE_SINGLE"] == "Y")
						{
							$dbRes = VoteTable::getList([
								"select" => ["ID", "TITLE", "DATE_START", "DATE_END"],
								"filter" => (is_null($params["ID"]) ? [] : [
										"!ID" => $params["ID"]]) + [
										"CHANNEL_ID" => $channel["ID"],
										[
											"LOGIC" => "OR",
											"><DATE_START" =>  [$params["DATE_START"], $params["DATE_END"]],
											"><DATE_END" =>  [$params["DATE_START"], $params["DATE_END"]],
											[
												"<=DATE_START" =>  $params["DATE_START"],
												">=DATE_END" => $params["DATE_END"]
											]
										]
									]
							]);
							if ($res = $dbRes->fetch())
							{
								$field = static::getEntity()->getField("DATE_START");
								$result->addError(new FieldError(
									$field,
									Loc::getMessage("VOTE_ERROR_SAME_DATE_VOTE_IS_ALREADY_EXISTS", ["#VOTE#" => $res["TITLE"]." [".$res["ID"]."]"])
								));
							}
						}
					}
				}
				//endregion
			}
			catch (\Exception $e)
			{
				$result->addError(new Error(
					$e->getMessage()
				));
			}
		}
	}

	/**
	 * @param array $id Vote IDs.
	 * @param mixed $increment True - increment, false - decrement, integer - exact value.
	 * @return void
	 */
	public static function setCounter(array $id, $increment = true)
	{
		if (empty($id))
			return;
		$connection = \Bitrix\Main\Application::getInstance()->getConnection();

		$sql = intval($increment);
		if ($increment === true)
			$sql = "COUNTER+1";
		else if ($increment === false)
			$sql = "COUNTER-1";
		$connection->queryExecute("UPDATE ".self::getTableName()." SET COUNTER=".$sql." WHERE ID IN (".implode(", ", $id).")");
	}
}

class Vote extends BaseObject implements \ArrayAccess
{
	protected $vote = array();
	protected $questions = array();
	protected $channel = null;
	/** @var Result[] */
	protected static $canVoteStorage = [];
	public static $storage = array();
	public static $statStorage = array();

	public function __construct($id)
	{
		if (!($id > 0))
			throw new \Bitrix\Main\ArgumentNullException("vote id");
		parent::__construct($id);
	}

	public function init()
	{
		$data = self::getData($this->id);
		if ($data === null)
			throw new ArgumentException("Wrong vote id!");
		$this->vote = array_diff_key($data, array("QUESTIONS" => ""));
		foreach ($data["QUESTIONS"] as $q)
		{
			$this->questions[$q["ID"]] = $q;
		}
		$eventManager = \Bitrix\Main\EventManager::getInstance();
		$eventManager->addEventHandler("vote", "onAfterVoteQuestionAdd", array($this, "clearCache"));
		$eventManager->addEventHandler("vote", "onAfterVoteQuestionUpdate", array($this, "clearCache"));
		$eventManager->addEventHandler("vote", "onAfterVoteQuestionDelete", array($this, "clearCache"));
		$eventManager->addEventHandler("vote", "onVoteQuestionActivate", array($this, "clearCache"));
		$eventManager->addEventHandler("vote", "onVoteReset", array($this, "clearCache"));
	}

	/**
	 * @param integer $id Vote ID.
	 * @return array|null
	 */
	public static function getData($id)
	{
		if (!array_key_exists($id, self::$storage))
		{
			self::$storage[$id] = null;
			/**@var $dbRes \Bitrix\Main\ORM\Query\Result */
			$dbRes = VoteTable::getList(array(
				"select" => array(
					"V_" => "*",
					"V_LAMP" => "LAMP",
					"Q_" => "QUESTION.*",
					"A_" => "QUESTION.ANSWER",
				),
				"order" => array(
					"QUESTION.C_SORT" => "ASC",
					"QUESTION.ID" => "ASC",
					"QUESTION.ANSWER.C_SORT" => "ASC",
					"QUESTION.ANSWER.ID" => "ASC",
				),
				"filter" => array(
					"ID" => $id
				)
			));
			// TODO Remake to a \Bitrix\Main\ORM\Objectify\Collection and its method ->fill()
			if (($row = $dbRes->fetch()) && $row)
			{
				$images = array();
				$vote = array();
				foreach ($row as $key => $val)
					if (mb_strpos($key, "V_") === 0)
						$vote[mb_substr($key, 2)] = $val;
				$vote += array(
						"IMAGE" => null,
						"FIELD_NAME" => \Bitrix\Vote\Event::getExtrasFieldName($vote["ID"], "#ENTITY_ID#"),
						"QUESTIONS" => array());
				if ($vote["IMAGE_ID"] > 0)
					$images[$vote["IMAGE_ID"]] = &$vote["IMAGE"];
				$question = [
					'ID' => null,
					'FIELD_TYPE' => \Bitrix\Vote\QuestionTypes::RADIO,
					'COUNTER' => 0,
				];
				do
				{
					$answer = array();
					foreach ($row as $key => $val)
					{
						if (mb_strpos($key, "A_") === 0)
							$answer[mb_substr($key, 2)] = $val;
					}
					if ($answer["IMAGE_ID"] > 0)
						$images[$answer["IMAGE_ID"]] = &$answer["IMAGE"];
					if ($answer["QUESTION_ID"] != $question["ID"])
					{
						unset($question);
						$question = array();
						foreach ($row as $key => $val)
						{
							if (mb_strpos($key, "Q_") === 0)
								$question[mb_substr($key, 2)] = $val;
						}
						$question += array(
							"IMAGE" => null,
							"FIELD_NAME" => \Bitrix\Vote\Event::getFieldName($vote["ID"], $question["ID"]),
							"ANSWERS" => array()
						);
						if ($question["IMAGE_ID"] > 0)
							$images[$question["IMAGE_ID"]] = &$question["IMAGE"];
						$vote["QUESTIONS"][$question["ID"]] = &$question;
					}
					$answer["FIELD_NAME"] = $answer["~FIELD_NAME"] = \Bitrix\Vote\Event::getFieldName($vote["ID"], $question["ID"]);
					$answer["MESSAGE_FIELD_NAME"] = \Bitrix\Vote\Event::getMessageFieldName($vote["ID"], $question["ID"], $answer["ID"]);
					if (
						$answer["FIELD_TYPE"] == \Bitrix\Vote\AnswerTypes::TEXT ||
						$answer["FIELD_TYPE"] == \Bitrix\Vote\AnswerTypes::TEXTAREA
					)
					{
						if ($question["FIELD_TYPE"] == \Bitrix\Vote\QuestionTypes::COMPATIBILITY)
							$answer["FIELD_NAME"] = $answer["MESSAGE_FIELD_NAME"];
					}
					else if ($question["FIELD_TYPE"] != \Bitrix\Vote\QuestionTypes::COMPATIBILITY)
					{
						$answer["FIELD_TYPE"] = $question["FIELD_TYPE"];
					}
					$answer["~PERCENT"] = ($question["COUNTER"] > 0 ? $answer["COUNTER"] * 100 / $question["COUNTER"] : 0);
					$answer["PERCENT"] = round($answer["~PERCENT"], 2);
					$question["ANSWERS"][$answer["ID"]] = &$answer;
					unset($answer);
				} while (($row = $dbRes->fetch()) && $row);
				unset($question);
				//region Getting images
				if (count($images) > 0)
				{
					$dbRes = \Bitrix\Main\FileTable::getList(array("select" => array("*"), "filter" => array("ID" => array_keys($images))));
					while ($res = $dbRes->fetch())
					{
						$images[$res["ID"]] = $res + array("SRC" => \CFile::GetFileSRC($res));
					}
				}
				//endregion
				//region Setting data into local storages
				foreach ($vote["QUESTIONS"] as $question)
				{
					$questionId = strval($question["ID"]);
					if (!array_key_exists($questionId, Question::$storage))
						Question::$storage[$questionId] = $question;
					foreach ($question["ANSWERS"] as $answer)
					{
						if (!array_key_exists($answer["ID"], Answer::$storage))
							Answer::$storage[$answer["ID"]] = $answer;
					}
				}
				self::$storage[$id] = $vote;
				//endregion
			}
		}
		return self::$storage[$id];
	}

	/**
	 * @param int $voteId Vote ID.
	 * @param array &$data Array(
			"CHANNEL_ID" => 5,
			"AUTHOR_ID" => 1,
			"DATE_START" => ...,
			"DATE_END" => ...,
			"TITLE" => "ABC...",
			"ACTIVE" => "Y",
			"URL" => "http://",
			"NOTIFY" => "Y" || "N" || "I",
			"UNIQUE_TYPE" => security context,
			"DELAY" => 150, //seconds
			"QUESTIONS" => array(
				1 => array(
					"ID" => 0,
					"QUESTION" => "Question text",
					"QUESTION_TYPE" => "text"||"html",
					"ANSWERS" => array(
						array(
							"ID" => 0,
							"MESSAGE" => "Answer text",
							"MESSAGE_TYPE" => "text"||"html",
							"FIELD_TYPE" => 0||1||2||3||4||
						)
					)
				)
			);.
	 * @return bool
	 * @throws ArgumentException
	 */
	public static function checkData(array &$data, $voteId = 0)
	{
		$result = new AddResult();
		$questionsToRevise = [];
		if ($voteId > 0)
		{
			$result = new UpdateResult();
			$vote = static::getData($voteId);
			if (is_null($vote))
				throw new ArgumentException(Loc::getMessage("VOTE_VOTE_NOT_FOUND", array("#ID#", $voteId)));
			$questionsToRevise = ($vote["QUESTIONS"] ?: []);
		}
		$questionsToSave = isset($data["QUESTIONS"]) && is_array($data["QUESTIONS"]) ? $data["QUESTIONS"] : [];
		unset($data["QUESTIONS"]);
		VoteTable::checkFields($result, ["ID" => $voteId], $data);
		if (!$result->isSuccess())
			throw new ArgumentException(implode("", $result->getErrorMessages()));
		/************** Check Data *****************************************/
		$questions = array();
		foreach ($questionsToSave as $key => $question)
		{
			if (($question["DEL"] ?? null) == "Y")
				continue;

			$question["ID"] = intval($question["ID"] ?? null);
			$question = array(
				"ID" => (array_key_exists($question["ID"], $questionsToRevise) ? $question["ID"] : null),
				"QUESTION" => trim($question["QUESTION"]),
				"QUESTION_TYPE" => trim($question["QUESTION_TYPE"]),
				"FIELD_TYPE" => $question["FIELD_TYPE"],
				"ANSWERS" => (is_array($question["ANSWERS"]) ? $question["ANSWERS"] : array()));

			$savedAnswers = ($question["ID"] > 0 ? $questionsToRevise[$question["ID"]]["ANSWERS"] : array());
			$newAnswers = array();
			foreach ($question["ANSWERS"] as $keya => $answer)
			{
				$answer["ID"] = intval($answer["ID"] ?? null);
				$answer["MESSAGE"] = trim($answer["MESSAGE"]);
				if (($answer["DEL"] ?? null) != "Y" && $answer["MESSAGE"] !== "")
				{
					$answer = array(
						"ID" => $answer["ID"],
						"MESSAGE" => $answer["MESSAGE"],
						"MESSAGE_TYPE" => $answer["MESSAGE_TYPE"],
						"FIELD_TYPE" => $answer["FIELD_TYPE"]);
					if (!array_key_exists($answer["ID"], $savedAnswers))
						unset($answer["ID"]);
					else
						unset($savedAnswers[$answer["ID"]]);
					$newAnswers[] = $answer;
				}
			}
			$question["ANSWERS"] = $newAnswers;

			if ($question["QUESTION"] == "" && empty($question["ANSWERS"]))
				continue;
			else if ($question["QUESTION"] == "")
			{
				$result->addError(new Error(Loc::getMessage("VOTE_QUESTION_EMPTY", array("#NUMBER#" => $key)), "QUESTION_".$key));
			}
			else if (empty($question["ANSWERS"]))
			{
				$result->addError(new Error(Loc::getMessage("VOTE_ANSWERS_EMPTY", array("#QUESTION#" => HtmlFilter::encode($question["QUESTION"]))), "QUESTION_".$key));
			}
			else
			{
				foreach ($savedAnswers as $answer)
				{
					$question["ANSWERS"][] = $answer + array("DEL" => "Y");
				}
				$questions[] = $question;
				unset($questionsToRevise[$question["ID"]]);
			}
		}
		if (!$result->isSuccess())
		{
			throw new ArgumentException(implode("", $result->getErrorMessages()));
		}
		foreach ($questionsToRevise as $question)
		{
			$questions[] = $question + array("DEL" => "Y");
		}
		$data += array("QUESTIONS" => $questions);
		return true;
	}

	/**
	 * @param integer $voteId Vote ID, can be 0.
	 * @param array $data Look at checkData.
	 * @return int
	 * @throws ArgumentException
	 */
	public static function saveData($voteId, array $data)
	{
		if (!($voteId > 0) && empty($data["QUESTIONS"]))
		{
			return 0;
		}
		if ($voteId)
		{
			$result = VoteTable::update($voteId, $data);
		}
		else
		{
			unset($data['ID']);
			$result = VoteTable::add($data);
			if ($result->isSuccess())
				$voteId = $result->getId();
		}
		if (!$result->isSuccess())
			throw new ArgumentException(implode("", $result->getErrorMessages()));
		else if ($result instanceof UpdateResult)
			$vote = static::getData($voteId);
		else
			$vote = \Bitrix\Vote\VoteTable::getById($voteId)->fetch();
		$vote += ["QUESTIONS" => []];
		/************** Check Data *****************************************/
		$iQuestions = 0;
		foreach ($data["QUESTIONS"] as $question)
		{
			$savedAnswers = array();
			if ($question["ID"] > 0 && array_key_exists($question["ID"], $vote["QUESTIONS"]))
			{
				$savedAnswers = $vote["QUESTIONS"][$question["ID"]]["ANSWERS"];
				unset($vote["QUESTIONS"][$question["ID"]]);
				if (isset($question["DEL"]) && $question["DEL"] === "Y")
				{
					\CVoteQuestion::Delete($question["ID"]);
					continue;
				}
				$question["C_SORT"] = (++$iQuestions) * 10;
				\CVoteQuestion::Update($question["ID"], $question);
			}
			else
			{
				$question["C_SORT"] = (++$iQuestions) * 10;
				$question["VOTE_ID"] = $vote["ID"];
				$question["ID"] = \CVoteQuestion::Add($question);
				if ($question["ID"] <= 0)
					continue;
			}
			$iAnswers = 0;
			foreach ($question["ANSWERS"] as $answer)
			{
				if (!empty($answer["ID"]) && array_key_exists($answer["ID"], $savedAnswers))
				{
					unset($savedAnswers[$answer["ID"]]);
					if ($answer["DEL"] == "Y")
					{
						\CVoteAnswer::Delete($answer["ID"]);
						continue;
					}
					$answer["C_SORT"] = (++$iAnswers)* 10;
					\CVoteAnswer::Update($answer["ID"], $answer);
				}
				else
				{
					$answer["QUESTION_ID"] = $question["ID"];
					$answer["C_SORT"] = (++$iAnswers) * 10;
					$answer["ID"] = intval(\CVoteAnswer::Add($answer));
					if ($answer["ID"] <= 0)
						continue;
				}
			}
			if ($iAnswers <= 0)
			{
				\CVoteQuestion::Delete($question["ID"]);
				$iQuestions--;
			}
			else if (!empty($savedAnswers))
			{
				while ($answer = array_pop($savedAnswers))
					\CVoteAnswer::Delete($answer["ID"]);
			}
		}
		if ($iQuestions <= 0)
		{
			Vote::delete($vote["ID"]);
			$vote["ID"] = 0;
		}
		return $vote["ID"];
	}

	/**
	 * Sends notifications to users.
	 * @param array $event Array("ID" => 1, "VOTE_USER_ID" => 45);.
	 * @param array $vote Array(ID => 1, QUESTIONS => array("ID" => 2, ANSWERS => array()));.
	 * @param string $type Can be "im" || "mail".
	 * @return bool
	 */
	public function sendVotingMessage(array $event, $vote, $type = "im")
	{
		if ($type == "im" && \Bitrix\Main\Loader::includeModule("im"))
		{
			$url = "";
			if (!empty($vote["URL"]))
			{
				if (defined("SITE_SERVER_NAME"))
				{
					$url = SITE_SERVER_NAME;
				}
				$url = (!empty($url) ? $url : \COption::GetOptionString("main", "server_name"));
				if (!empty($url))
				{
					$url = (\CMain::IsHTTPS() ? "https" : "http") . "://" . $url . $vote["URL"];
				}
			}

			// send notification
			$gender = "";
			if ($event["VISIBLE"] === "Y" && $this->getUser()->getParam("PERSONAL_GENDER") === "F")
			{
				$gender = "_F";
			}
			$res = array(
				"MESSAGE_TYPE" => IM_MESSAGE_SYSTEM,
				"TO_USER_ID" => $vote["AUTHOR_ID"],
				"FROM_USER_ID" => ( $event["VISIBLE"] === "Y" ? $this->getUser()->getId() : 0),
				"NOTIFY_TYPE" => IM_NOTIFY_FROM,
				"NOTIFY_MODULE" => "vote",
				"NOTIFY_EVENT" => "voting",
				"NOTIFY_TAG" => "VOTING|" . $vote["ID"],
				"NOTIFY_MESSAGE" => (!empty($vote["URL"])
					? fn (?string $languageId = null) => Loc::getMessage(
						"V_NOTIFY_MESSAGE_HREF" . $gender,
						array("#VOTE_TITLE#" => $vote["TITLE"], "#VOTE_URL#" => $vote["URL"]),
						$languageId
					)
					: fn (?string $languageId = null) => Loc::getMessage(
						"V_NOTIFY_MESSAGE" . $gender,
						array("#VOTE_TITLE#" => $vote["TITLE"]),
						$languageId
					)
				),
				"NOTIFY_MESSAGE_OUT" => (!empty($url)
					? fn (?string $languageId = null) => Loc::getMessage(
						"V_NOTIFY_MESSAGE_OUT_HREF" . $gender,
						array("#VOTE_TITLE#" => $vote["TITLE"], "#VOTE_URL#" => $url),
						$languageId
					)
					: fn (?string $languageId = null) => Loc::getMessage(
						"V_NOTIFY_MESSAGE" . $gender,
						array("#VOTE_TITLE#" => $vote["TITLE"]),
						$languageId
					)
				)
			);
			\CIMNotify::Add($res);
		}
		else
		{
			$channel = $this->getChannel();
			// send e-mail
			$dbUser = \CUser::getById($vote["AUTHOR_ID"]);
			if ($dbUser && ($u = $dbUser->Fetch()) && !empty($u["EMAIL"]))
			{
				$eventFields = array(
					"EMAIL_TO" => $u["EMAIL"],
					"VOTE_STATISTIC" => "",
					"ID" => $event["EVENT_ID"],
					"TIME" => GetTime(time(), "FULL"),
					"VOTE_TITLE" => $vote["TITLE"],
					"VOTE_DESCRIPTION" => $vote["DESCRIPTION"],
					"VOTE_ID" => $vote["ID"],
					"VOTE_COUNTER" => $vote["COUNTER"],
					"URL" => $vote["URL"],
					"CHANNEL" => $channel["TITLE"],
					"CHANNEL_ID" => $channel["ID"],
					"VOTER_ID" => $event["VOTE_USER_ID"],
					"USER_NAME" => ($event["VISIBLE"] == "Y" ? $this->getUser()->getFullName() : "Hidden"),
					"LOGIN" => ($event["VISIBLE"] == "Y" ? $this->getUser()->getLogin() : "hidden"),
					"USER_ID" => ($event["VISIBLE"] == "Y" ? $this->getUser()->getID() : 0),
					"STAT_GUEST_ID" => intval($_SESSION["SESS_GUEST_ID"]),
					"SESSION_ID" => intval($_SESSION["SESS_SESSION_ID"]),
					"IP" => \Bitrix\Main\Context::getCurrent()->getServer()->get("REMOTE_ADDR")
				);
				$eventFields["USER_NAME"] = (!!$eventFields["USER_NAME"] && $event["VISIBLE"] == "Y" ? $eventFields["USER_NAME"] : $eventFields["LOGIN"]);
				// VOTE_STATISTIC
				$text = array();
				foreach ($this["QUESTIONS"] as $question)
				{
					if (array_key_exists($question["ID"], $event["BALLOT"]))
					{
						$text[$question["ID"]] = array();
						foreach ($question["ANSWERS"] as $answer)
						{
							if (array_key_exists($answer["ID"], $event["BALLOT"][$question["ID"]]["ANSWERS"]))
							{
								if (($answer["FIELD_TYPE"] == \Bitrix\Vote\AnswerTypes::TEXT || $answer["FIELD_TYPE"] == \Bitrix\Vote\AnswerTypes::TEXTAREA) &&
									$event["BALLOT"][$question["ID"]]["ANSWERS"][$answer["ID"]]["MESSAGE"] !== "")
								{
									$text[$question["ID"]][] = $event["BALLOT"][$question["ID"]]["ANSWERS"][$answer["ID"]]["MESSAGE"];
								}
								else
								{
									$text[$question["ID"]][] = $answer["MESSAGE"];
								}
							}
						}
						if (!empty($text[$question["ID"]]))
						{
							$text[$question["ID"]] = " - " . $question["QUESTION"] . "\n - " . implode(", ", $text[$question["ID"]]);
						}
						else
						{
							$text[$question["ID"]] = " - " . $question["QUESTION"] . "\n - ...\n";
						}
					}
				}
				$eventFields["VOTE_STATISTIC"] = "\n" . implode("\n\n", $text);
				$arrSites = \CVoteChannel::GetSiteArray($channel["ID"]);
				\CEvent::Send("VOTE_FOR", $arrSites, $eventFields, "N");
			}
		}

		return true;
	}

	/**
	 * Complete vote array by data from DB.
	 * @return void
	 */
	public function fillStatistic()
	{
		foreach ($this->questions as &$qs)
			foreach ($qs["ANSWERS"] as &$as)
				$as["STAT"] = array();

		$dbRes = \Bitrix\Vote\EventTable::getList(array(
			"select" => array(
				"V_" => "*",
				"Q_" => "QUESTION.*",
				"A_" => "QUESTION.ANSWER.*",
				"U_ID" => "USER.USER.ID",
				"U_NAME" => "USER.USER.NAME",
				"U_LAST_NAME" => "USER.USER.LAST_NAME",
				"U_SECOND_NAME" => "USER.USER.SECOND_NAME",
				"U_LOGIN" => "USER.USER.LOGIN",
				"U_PERSONAL_PHOTO" => "USER.USER.PERSONAL_PHOTO",
			),
			"filter" => array("VOTE_ID" => $this->id, "VALID" => "Y"),
			"order" => array(
				"USER.USER.LAST_NAME" => "ASC",
				"USER.USER.NAME" => "ASC",
				"USER.USER.LOGIN" => "ASC"
			)
		));
		while ($dbRes && ($res = $dbRes->fetch()))
		{
			if (array_key_exists($res["Q_QUESTION_ID"], $this->questions) &&
				array_key_exists($res["A_ANSWER_ID"], $this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"]))
			{
				$stat = &$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["STAT"];
				$result = array(
					"USER" => array(
						"ID" => 0,
					),
					"MESSAGE" => $res["A_MESSAGE"]
				);
				if ($this["ANONYMITY"] !== \Bitrix\Vote\Vote\Anonymity::ANONYMOUSLY &&
					$res["V_VISIBLE"] == "Y" && $res["U_ID"] > 0)
				{
					$result["USER"] = array(
						"ID" => $res["U_ID"],
						"NAME" => $res["U_NAME"],
						"LAST_NAME" => $res["U_LAST_NAME"],
						"SECOND_NAME" => $res["U_SECOND_NAME"],
						"LOGIN" => $res["U_LOGIN"],
						"PERSONAL_PHOTO" => $res["U_PERSONAL_PHOTO"],
					);
				}
				$stat[$res["A_ID"]] = $result;
			}
		}
	}

	public function getStatistic() {
		$dbRes = \Bitrix\Vote\EventTable::getList(array(
			"select" => array(
				"V_" => "*",
				"Q_" => "QUESTION.*",
				"A_" => "QUESTION.ANSWER.*",
				"U_ID" => "USER.USER.ID",
				"U_NAME" => "USER.USER.NAME",
				"U_LAST_NAME" => "USER.USER.LAST_NAME",
				"U_SECOND_NAME" => "USER.USER.SECOND_NAME",
				"U_PERSONAL_PHOTO" => "USER.USER.PERSONAL_PHOTO",
			),
			"filter" => array("VOTE_ID" => $this->id, "VALID" => "Y"),
			"order" => array(
				"USER.USER.LAST_NAME" => "ASC",
				"USER.USER.NAME" => "ASC",
				"USER.USER.LOGIN" => "ASC"
			)
		));
		$result = [];
		while ($dbRes && ($res = $dbRes->fetch()))
		{
			if (!array_key_exists($res["V_ID"], $result))
			{
				$result[$res["V_ID"]] = [
					"ID" => $res["V_ID"],
					"DATE" => $res["V_DATE_VOTE"],
					"VISIBLE" => ($this["ANONYMITY"] !== \Bitrix\Vote\Vote\Anonymity::ANONYMOUSLY &&
						$res["V_VISIBLE"] == "Y" ? "Y" : "N"),
					"BALLOT" => [],
					"USER" => ["ID" => 0]
				];
				if ($result[$res["V_ID"]]["VISIBLE"] == "Y" && $res["U_ID"] > 0)
				{
					$result[$res["V_ID"]]["USER"] = array(
						"ID" => $res["U_ID"],
						"NAME" => $res["U_NAME"],
						"LAST_NAME" => $res["U_LAST_NAME"],
						"SECOND_NAME" => $res["U_SECOND_NAME"],
						"LOGIN" => $res["U_LOGIN"] ?? null,
						"PERSONAL_PHOTO" => $res["U_PERSONAL_PHOTO"],
					);
				}
			}
			$ballot = &$result[$res["V_ID"]]["BALLOT"];
			if (!array_key_exists($res["Q_QUESTION_ID"], $ballot))
			{
				$ballot[$res["Q_QUESTION_ID"]] = [];
			}
			$ballot[$res["Q_QUESTION_ID"]][$res["A_ANSWER_ID"]] = trim($res["A_MESSAGE"]);
		}
		return $result;
	}
	/**
	 * @return array|null
	 */
	public function getChannel()
	{
		if ($this->channel === null)
		{
			$this->channel = array();
			$db = Channel::getList(array());
			while (($res = $db->fetch()) && $res)
			{
				if ($this->vote["CHANNEL_ID"] == $res["ID"])
				{
					$this->channel = $res;
					break;
				}
			}
		}
		return $this->channel;
	}

	/**
	 * @param string $key The name if characteristic that you want to know.
	 * @return mixed
	 */
	public function get($key): mixed
	{
		return $this->vote[$key] ?? null;
	}

	/**
	 * Returns question array.
	 * @param int $id
	 * @return array|null
	 */
	public function getQuestion(int $id)
	{
		if (array_key_exists($id, $this->questions))
			return $this->questions[$id];
		return null;
	}
	/**
	 * @return array
	 */
	public function getQuestions()
	{
		return $this->questions;
	}

	/**
	 * Prolongs the time of voting for a year
	 * @return void
	 */
	public function resume()
	{
		VoteTable::update($this->id, ["DATE_END" => (new DateTime())->add("1Y")]);
		$this->clearCache();
	}

	/**
	 * Sets the finish time for voting by current moment
	 * @return void
	 */
	public function stop()
	{
		VoteTable::update($this->id, ["DATE_END" => new DateTime()]);
		$this->clearCache();
	}

	/**
	 * Deletes Vote by its id.
	 * @param integer $id Vote ID.
	 * @return bool
	 */
	public static function delete($id)
	{
		// @todo delete all attaches
		return \CVote::Delete($id);
	}

	/**
	 * Clears cache
	 * @return void
	 */
	public function clearCache()
	{
		global $VOTE_CACHE;
		unset($VOTE_CACHE["VOTE"][$this->id]);
		unset(self::$storage[$this->id]);
		unset(self::$canVoteStorage[$this->id]);
	}
	/**
	 * Clears vote events cache
	 * @return void
	 */
	private function clearVotingCache()
	{
		global $VOTE_CACHE;
		unset($VOTE_CACHE["VOTE_CACHE_VOTING"][$this->id]);
		unset(self::$canVoteStorage[$this->id]);
	}

	/**
	 * Exports data of voting into excel file
	 * @param string $type html|xls|csv
	 * @return void
	 */
	public function exportExcel($type = "html")
	{
		global $APPLICATION;
		$nameTemplate = Context::getCurrent()->getCulture()->getFormatName();
		$dateTemplate = \Bitrix\Main\Type\DateTime::getFormat();

		$APPLICATION->restartBuffer();
		while(ob_get_clean());
		header("Content-Transfer-Encoding: binary");

		$statistic = $this->getStatistic();
		$table1 = ["body" => []];
		$table2 = [
			"head" => [Loc::getMessage("V_EXPORT_DATE"), Loc::getMessage("V_EXPORT_NAME")],
			"body" => []
		];

		foreach ($statistic as $event)
		{
			$user = Loc::getMessage("VOTE_GUEST");
			if ($event["VISIBLE"] !== "Y")
			{
				$user = Loc::getMessage("VOTE_ANONYMOUSLY");
			}
			else if ($event["USER"]["ID"] > 0)
			{
				$user = \CUser::formatName($nameTemplate, $event["USER"], true, false);
			}
			/*@var \Bitrix\Main\Type\DateTime $event["DATE"] */
			$row = [
				"DATE" => $event["DATE"]->toUserTime()->format($dateTemplate),
				"USER" => $user
			];

			foreach ($this->questions as $questionId => $question)
			{
				$answerMessage = [];
				if (array_key_exists($questionId, $event["BALLOT"]))
				{
					foreach ($question["ANSWERS"] as $answerId => $answer)
					{
						if (array_key_exists($answerId, $event["BALLOT"][$questionId]))
						{
							if (!array_key_exists("STAT", $this->questions[$questionId]["ANSWERS"][$answerId]))
								$this->questions[$questionId]["ANSWERS"][$answerId]["STAT"] = [];
							$stat = &$this->questions[$questionId]["ANSWERS"][$answerId]["STAT"];
							if ($event["BALLOT"][$questionId][$answerId] <> '')
							{
								$stat[$event["ID"]] = $row["USER"]." (".$event["BALLOT"][$questionId][$answerId].")";
								$answerMessage[] = $event["BALLOT"][$questionId][$answerId];
							}
							else
							{
								$answerMessage[] = $answer["MESSAGE"];
								$stat[$event["ID"]] = $row["USER"];
							}
						}
					}
				}
				$row[] = implode(", ", $answerMessage);
			}
			$table2["body"][] = array_values($row);
		}
		foreach ($this->questions as $questionId => $question)
		{
			$table1["body"][] = [$question["QUESTION"], "", "", ""];
			foreach ($question["ANSWERS"] as $answerId => $answer)
			{
				$table1["body"][] = ["", $answer["MESSAGE"], $answer["COUNTER"], (array_key_exists("STAT", $answer) ? implode(", ", $answer["STAT"]) : "")];
			}
			$table2["head"][] = $question["QUESTION"];
		}

		if ($type === "csv")
		{
			Header("Content-Type: ". MimeType::getByFileExtension("csv"));
			header("Content-Disposition: attachment;filename=vote".$this->id.".csv");

			$f = fopen("php://output", "w");
			fputcsv($f, $table2["head"], ';');
			foreach ($table2["body"] as $row) {
				fputcsv($f, $row, ';');
			}
			fclose($f);
		}
		else
		{
			$mess = [
				"GENERAL_INFO" => Loc::getMessage("V_EXPORT_GENERAL_INFO"),
				"STATISTIC" => Loc::getMessage("V_EXPORT_STATISTIC")
			];
			if ($type === "xls")
			{
				$bodyRows = [];
				foreach ($table1["body"] as $row)
				{
					$bodyRows[] = implode("</Data></Cell><Cell ss:StyleID=\"bold\"><Data ss:Type=\"String\">", $row);
				}
				$table1["body"] = implode("</Data></Cell></Row><Row><Cell><Data ss:Type=\"String\">", $bodyRows);

				$table2["head"] = implode("</Data></Cell><Cell ss:StyleID=\"bold\"><Data ss:Type=\"String\">", $table2["head"]);
				$bodyRows = [];
				foreach ($table2["body"] as $row)
				{
					$bodyRows[] = implode("</Data></Cell><Cell ss:StyleID=\"bold\"><Data ss:Type=\"String\">", $row);
				}
				$table2["body"] = implode("</Data></Cell></Row><Row><Cell><Data ss:Type=\"String\">", $bodyRows);
				$LANG_CHARSET = LANG_CHARSET;

				$res = <<<XML
<?xml version="1.0" charset="{$LANG_CHARSET}"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">
	<Styles>
		<Style ss:ID="bold">
			<Font ss:Bold="1"/>
		</Style>
	</Styles>
	<Worksheet ss:Name="{$mess["GENERAL_INFO"]}">
		<Table>
			<Row>
				<Cell><Data ss:Type="String">{$table1["body"]}</Data></Cell>
			</Row>
		</Table>
	</Worksheet>
	<Worksheet ss:Name="{$mess["STATISTIC"]}">
		<Table>
			<Row>
				<Cell ss:StyleID="bold"><Data ss:Type="String">{$table2["head"]}</Data></Cell>
			</Row>
			<Row>
				<Cell><Data ss:Type="String">{$table2["body"]}</Data></Cell>
			</Row>
		</Table>
	</Worksheet>
</Workbook>
XML;
			}
			else
			{
				$LANG_CHARSET = LANG_CHARSET;

				$bodyRows = [];
				foreach ($table1["body"] as $row)
				{
					$bodyRows[] = implode("</td><td>", $row);
				}
				$table1["body"] = implode("</td></tr><tr><td>", $bodyRows);

				$table2["head"] = implode("</th><th>", $table2["head"]);
				$bodyRows = [];
				foreach ($table2["body"] as $row)
				{
					$bodyRows[] = implode("</td><td>", $row);
				}
				$table2["body"] = implode("</td></tr><tr><td>", $bodyRows);

				$res = <<<HTML
<meta http-equiv="Content-type" content="text/html;charset={$LANG_CHARSET}" />
<p>
<table border="1">
	<tbody>
		<tr><td>{$table1["body"]}</td></tr>
	</tbody>
</table>
</p>
<p>
<table border="1">
	<thead><tr><th>{$table2["head"]}</th></tr></thead>
	<tbody><tr><td>{$table2["body"]}</td></tr></tbody>
</table>
</p>
HTML;
			}
			header("Content-Type: ". MimeType::getByFileExtension("xls"));
			header("Content-Disposition: attachment;filename=vote".$this->id.".xls");
			echo $res;
		}
		\CMain::finalActions();
		die();
	}

	private function getDataFromRequest(array $request)
	{
		$res = \Bitrix\Vote\Event::getDataFromRequest($this->getId(), $request);
		if ($res !== null)
		{
			$data = $res;
		}
		else
		{
			$questions = $this->getQuestions();
			$data = ["EXTRAS" => [], "BALLOT" => [], "MESSAGE" => []];

			foreach ($questions as $question)
			{
				$data["BALLOT"][$question["ID"]] = array();
				foreach ($question["ANSWERS"] as $answer)
				{
					$fieldType = (
							$question["FIELD_TYPE"] == \Bitrix\Vote\QuestionTypes::COMPATIBILITY ||
							$answer["FIELD_TYPE"] == AnswerTypes::TEXTAREA || $answer["FIELD_TYPE"] == AnswerTypes::TEXT ?
								$answer["FIELD_TYPE"] : $question["FIELD_TYPE"]);

					switch ($fieldType)
					{
						case AnswerTypes::RADIO :
						case AnswerTypes::DROPDOWN :
							$fieldName = ($fieldType == AnswerTypes::RADIO ? "vote_radio_" : "vote_dropdown_").$question["ID"];
							if ($request[$fieldName] == $answer["ID"])
								$data["BALLOT"][$question["ID"]][$answer["ID"]] = true;
							break;
						case AnswerTypes::CHECKBOX :
						case AnswerTypes::MULTISELECT :
							$fieldName = ($fieldType == AnswerTypes::CHECKBOX ? "vote_checkbox_" : "vote_multiselect_").$question["ID"];
							if (array_key_exists($fieldName, $request) && is_array($request[$fieldName]) && in_array($answer["ID"], $request[$fieldName]))
								$data["BALLOT"][$question["ID"]][$answer["ID"]] = true;
							break;
						default :
							$fieldName = ($answer["FIELD_TYPE"] == AnswerTypes::TEXT ? "vote_field_" : "vote_memo_") . $answer["ID"];
							$value = trim($request[$fieldName]);
							if ($value <> '')
							{
								if (!array_key_exists($question["ID"], $data["MESSAGE"]))
									$data["MESSAGE"][$question["ID"]] = [];
								$data["MESSAGE"][$question["ID"]][$answer["ID"]] = $value;
								$data["BALLOT"][$question["ID"]][$answer["ID"]] = true;
							}
							break;
					}
				}
			}
		}
		return $data;
	}
	/**
	 * Voting for vote  from current user $USER.
	 * @param array $request Old variant Array("
	 * 							vote_checkbox_".$questionId => array(1,2,3,...),
	 * 							"vote_multiselect_".$questionId => array(1,2,3,...),
	 * 							"vote_dropdown_".$questionId => 12 || "12",
	 * 							"vote_radio_".$questionId => 12 || "12",
	 * 							"vote_field_".$answerId => "12").
	 * 	New variant is
	 * [
	 *  EXTRA => [HIDDEN => N],
	 *  871 => [345 => (Does not matter || text from field)],
	 *  QUESTION_ID => [ANSWER_ID => (Y || text from field)],
	 * ]
	 * @param array $params
	 * @return bool
	 * @throws AccessDeniedException
	 */
	public function voteFor(array $request, $params = [])
	{
		return $this->registerEvent($this->getDataFromRequest($request), $params, User::getCurrent());
	}

	public function registerEvent(array $data, array $params, \Bitrix\Vote\User $user)
	{
		if ($this["LAMP"] == "red")
		{
			throw new AccessDeniedException(Loc::getMessage("VOTE_IS_NOT_ACTIVE"));
		}
		$voteId = $this->getId();
		if ($user->lock($voteId) !== true)
		{
			throw new AccessDeniedException(Loc::getMessage("VOTE_IS_OCCUPIED"));
		}

		$userId = $user->getId();

		$this->errorCollection->clear();

		/** @var \Bitrix\Main\Result $result */
		if (!isset($params["revote"]) || $params["revote"] != true)
		{
			$result = $this->canVote($user);
		}
		//region Delete event If It is possible
		else if (
			($result = $this->canRevote($user))
			&& $result->isSuccess()
			&& !empty($result->getData())
			&& ($eventIdsToDelete = array_column($result->getData(), 'ID'))
			&& !empty($eventIdsToDelete)
		)
		{
			$dbRes = \Bitrix\Vote\EventTable::getList([
				"select" => [
					"V_" => "*",
					"Q_" => "QUESTION.*",
					"A_" => "QUESTION.ANSWER.*"],
				"filter" => [
					"VOTE_ID" => $voteId,
					"ID" => $eventIdsToDelete],
				"order" => [
					"ID" => "ASC",
					"QUESTION.ID" => "ASC",
					"QUESTION.ANSWER.ID" => "ASC"]
			]);
			if ($dbRes && ($res = $dbRes->fetch()))
			{
				if (\Bitrix\Main\Loader::includeModule("im"))
				{
					\CIMNotify::DeleteByTag("VOTING|".$voteId, $userId);
				}
				$vEId = 0;
				$qEId = 0;
				do
				{
					if ($vEId < $res["V_ID"])
					{
						$vEId = $res["V_ID"];
						\Bitrix\Vote\Event::deleteEvent(intval($res["V_ID"]));
						$this->vote["COUNTER"] = max($this->vote["COUNTER"] - 1, 0);
					}
					if (array_key_exists($res["Q_QUESTION_ID"], $this->questions) &&
						array_key_exists($res["A_ANSWER_ID"], $this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"]))
					{
						if ($qEId < $res["Q_ID"])
						{
							$qEId = $res["Q_ID"];
							$this->questions[$res["Q_QUESTION_ID"]]["COUNTER"] = max($this->questions[$res["Q_QUESTION_ID"]]["COUNTER"] - 1, 0);
						}

						$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["COUNTER"] = max(
							$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["COUNTER"] - 1,
							0);
						if ($this->questions[$res["Q_QUESTION_ID"]]["COUNTER"] > 0)
						{
							$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["~PERCENT"] =
								$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["COUNTER"] * 100 /
								$this->questions[$res["Q_QUESTION_ID"]]["COUNTER"];
							$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["PERCENT"] = round($this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["~PERCENT"], 2);
						}
						else
						{
							$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["~PERCENT"] = 0;
							$this->questions[$res["Q_QUESTION_ID"]]["ANSWERS"][$res["A_ANSWER_ID"]]["PERCENT"] = 0;
						}
					}
				} while ($dbRes && ($res = $dbRes->fetch()));
				$this->clearCache();
				$this->clearVotingCache();
			}
			$result = $this->canVote($user);
		}
		//endregion

		if (!$result->isSuccess())
		{
			$this->errorCollection->add($result->getErrors());
		}
		else
		{
			$event = new \Bitrix\Vote\Event($this);
			/**
			 * @var \Bitrix\Main\Type\Dictionary $eventResult
			 */
			$eventFields = array(
				"VOTE_USER_ID"		=> \Bitrix\Vote\User::getCurrent()->setVotedUserId(true),
				"DATE_VOTE"			=> (new DateTime()),
				"STAT_SESSION_ID"	=> $_SESSION["SESS_SESSION_ID"] ?? null,
				"IP"				=> \Bitrix\Main\Context::getCurrent()->getServer()->get("REMOTE_ADDR"),
				"VALID"				=> "Y",
				"VISIBLE" 			=> ($this["ANONYMITY"] == \Bitrix\Vote\Vote\Anonymity::ANONYMOUSLY ? "N" : "Y") // can be replaced from $data array ["EXTRAS"]["HIDDEN"] = "Y"
			);
			if (!$event->check($data)
				|| !($eventResult = $event->add($eventFields, $data))
			)
			{
				$this->errorCollection->add($event->getErrors());
			}
			else
			{
				$this->vote["COUNTER"]++;
				foreach ($eventResult->get("BALLOT") as $questionId => $question)
				{
					$this->questions[$questionId]["COUNTER"]++;
					foreach ($question["ANSWERS"] as $answerId => $answerEventParams)
					{
						$this->questions[$questionId]["ANSWERS"][$answerId]["COUNTER"]++;
					}
				}
				foreach ($this->questions as $questionId => $question)
				{
					foreach ($question["ANSWERS"] as $answerId => $answerEventParams)
					{
						if ($this->questions[$questionId]["ANSWERS"][$answerId]["COUNTER"] > 0)
						{
							$this->questions[$questionId]["ANSWERS"][$answerId]["~PERCENT"] =
								$this->questions[$questionId]["ANSWERS"][$answerId]["COUNTER"] * 100 /
								$this->questions[$questionId]["COUNTER"];
							$this->questions[$questionId]["ANSWERS"][$answerId]["PERCENT"] = round($this->questions[$questionId]["ANSWERS"][$answerId]["~PERCENT"], 2);
						}
						else
						{
							$this->questions[$questionId]["ANSWERS"][$answerId]["~PERCENT"] = 0;
							$this->questions[$questionId]["ANSWERS"][$answerId]["PERCENT"] = 0;
						}
					}
				}
				self::$statStorage[] = $voteId;
				$_SESSION["VOTE"]["VOTES"][$voteId] = $eventResult->get("EVENT_ID");
				// statistic module
				if (\Bitrix\Main\Loader::includeModule("statistic"))
				{
					$event3 = $this["EVENT3"];
					if (empty($event3))
					{
						$event3 = (\Bitrix\Main\Context::getCurrent()->getRequest()->isHttps() ? "https://" : "http://") .
							\Bitrix\Main\Context::getCurrent()->getServer()->getHttpHost() .
							"/bitrix/admin/vote_user_results.php?EVENT_ID=" . $eventResult->get("EVENT_ID") . "&lang=" . LANGUAGE_ID;
					}
					\CStatEvent::AddCurrent($this["EVENT1"], $this["EVENT2"], $event3);
				}
				// notification TODO replace this functional into other function
				if ($this["NOTIFY"] !== "N" && $this["AUTHOR_ID"] > 0 && $this["AUTHOR_ID"] != $userId)
				{
					self::sendVotingMessage($eventResult->toArray(), $this, ($this["NOTIFY"] == "I" ? "im" : "mail"));
				}

				/***************** Event onAfterVoting *****************************/
				foreach (GetModuleEvents("vote", "onAfterVoting", true) as $ev)
				{
					ExecuteModuleEventEx($ev, array($voteId, $eventResult->get("EVENT_ID"), $userId));
				}
				/***************** /Event ******************************************/
			}
		}
		$user->unlock($voteId);
		return $this->errorCollection->isEmpty();
	}

	/**
	 * Checks if current user voted for this vote.
	 * @param int|User $userId User ID.
	 * @return bool|int
	 */
	public function isVotedFor($userId)
	{
		$result = false;
		$user = ($userId instanceof User ? $userId : ($userId == $this->getUser()->getId() ? User::getCurrent() : User::loadFromId($userId)));
		$canVoteResult = $this->canVote($user);
		if (!$canVoteResult->isSuccess())
		{
			$result = 0;
			for (
				$canVoteResult->getErrorCollection()->rewind();
				$canVoteResult->getErrorCollection()->valid();
				$canVoteResult->getErrorCollection()->next()
			)
			{
				/** @var Error $error */
				$error = $canVoteResult->getErrorCollection()->current();
				$result |= $error->getCode();
			}
		}
		return $result;
	}
	/**
	 * Checks rights to read current attached object.
	 * @param int $userId Id of user.
	 * @return bool
	 */
	public function canRead($userId)
	{
		if (parent::canEdit($userId))
			return true;
		else if (parent::canRead($userId))
		{
			$groups = parent::loadUserGroups($userId);
			$dbRes = Channel::getList(array(
				"select" => array("*"),
				"filter" => array(
					"ACTIVE" => "Y",
					"HIDDEN" => "N",
					">=PERMISSION.PERMISSION" => 1,
					"PERMISSION.GROUP_ID" => $groups
				),
				"order" => array(
					"TITLE" => "ASC"
				),
				"group" => array("ID")
			));
			while ($res = $dbRes->fetch())
			{
				if ($res["ID"] == $this->get("CHANNEL_ID"))
					return true;
			}
		}
		return false;
	}

	/**
	 * Checks rights to update current attached object.
	 * @param int $userId Id of user.
	 * @return bool
	 */
	public function canEdit($userId)
	{
		if (parent::canEdit($userId))
			return true;
		else if (parent::canRead($userId))
		{
			$groups = parent::loadUserGroups($userId);
			$dbRes = Channel::getList(array(
				"select" => array("*"),
				"filter" => array(
					"ACTIVE" => "Y",
					"HIDDEN" => "N",
					">=PERMISSION.PERMISSION" => 4,
					"PERMISSION.GROUP_ID" => $groups
				),
				"order" => array(
					"TITLE" => "ASC"
				),
				"group" => array("ID")
			));
			while ($res = $dbRes->fetch())
			{
				if ($res["ID"] == $this->get("CHANNEL_ID"))
					return true;
			}
		}
		return false;
	}

	public function canParticipate($userId)
	{
		return $this->canRead($userId) && $this->vote["LAMP"] == "green";
	}

	/**
	 * @param \Bitrix\Vote\User|integer $user
	 * @return \Bitrix\Main\Result
	 * @throws ArgumentException
	 * @throws \Bitrix\Main\ObjectException
	 * @throws \Bitrix\Main\ObjectPropertyException
	 * @throws \Bitrix\Main\SystemException
	 */
	public function canVote($user)
	{
		$vote = $this;
		$voteId = intval($vote["ID"]);
		if (!($user instanceof \Bitrix\Vote\User))
		{
			$user = \Bitrix\Vote\User::loadFromId($user);
		}
		if (!array_key_exists($voteId, self::$canVoteStorage))
		{
			self::$canVoteStorage[$voteId] = [];
		}
		if (array_key_exists($user->getId(), self::$canVoteStorage[$voteId]))
		{
			return self::$canVoteStorage[$voteId][$user->getId()];
		}

		$uniqueType = intval($vote["UNIQUE_TYPE"]);
		$filterCard = 0;

		$filter = ["LOGIC" => "OR"];

		$result = new \Bitrix\Main\Result();

		if ($uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_SESSION && is_array($_SESSION["VOTE"]["VOTES"]) && array_key_exists($voteId, $_SESSION["VOTE"]["VOTES"]))
		{
			$filter["ID"] = $_SESSION["VOTE"]["VOTES"][$voteId];
			$filterCard |= \Bitrix\Vote\Vote\EventLimits::BY_SESSION;
			$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_SESSION"), \Bitrix\Vote\Vote\EventLimits::BY_SESSION));
		}
		if (($uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_COOKIE) && $user->getCookieId() > 0)
		{
			$filter["USER.COOKIE_ID"] = $user->getCookieId();
			$filterCard |= \Bitrix\Vote\Vote\EventLimits::BY_COOKIE;
		}
		if ($uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_IP)
		{
			$delay = intval($vote["KEEP_IP_SEC"]);
			$filter[] = ([
					"IP" => \Bitrix\Main\Context::getCurrent()->getRequest()->getRemoteAddress()] +
				($delay > 0 ? [
					">=DATE_VOTE" => (new \Bitrix\Main\Type\DateTime())->add("-T".$delay."S")] : []));
			$filterCard |= \Bitrix\Vote\Vote\EventLimits::BY_IP;
		}
		if (
				$uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_USER_AUTH ||
				$uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_USER_DATE_REGISTER ||
				$uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_USER_ID)
		{
			if (!$user->getUser()->IsAuthorized())
			{
				$filterCard |= \Bitrix\Vote\Vote\EventLimits::BY_USER_AUTH;
				$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_USER_AUTH"), \Bitrix\Vote\Vote\EventLimits::BY_USER_AUTH));
			}
			else
			{
				if ($uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_USER_DATE_REGISTER)
				{
					$us = \CUser::GetByID($user->getId())->fetch();
					if (MakeTimeStamp($vote["DATE_START"]) < MakeTimeStamp($us["DATE_REGISTER"]))
					{
						$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_USER_DATE_REGISTER"), \Bitrix\Vote\Vote\EventLimits::BY_USER_DATE_REGISTER));
					}
				}
				if ($uniqueType & \Bitrix\Vote\Vote\EventLimits::BY_USER_ID)
				{
					$filter["USER.AUTH_USER_ID"] = $user->getId();
					$filterCard |= \Bitrix\Vote\Vote\EventLimits::BY_USER_ID;
				}
			}
		}

		if ($filterCard > 0)
		{
			$dbRes = \Bitrix\Vote\EventTable::getList([
				"select" => [
					"*",
					"USER_COOKIE_ID" => "USER.COOKIE_ID",
					"USER_AUTH_USER_ID" => "USER.AUTH_USER_ID",
				],
				"filter" => [
					"VOTE_ID" => $voteId,
					$filter
				]
			]);
			$data = $dbRes->fetchAll();
			$result->setData($data);
			foreach ($data as $res)
			{
				if (($filterCard & \Bitrix\Vote\Vote\EventLimits::BY_COOKIE) && $res["USER_COOKIE_ID"] == $user->getCookieId())
				{
					$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_COOKIE"), \Bitrix\Vote\Vote\EventLimits::BY_COOKIE));
					$filterCard &= ~\Bitrix\Vote\Vote\EventLimits::BY_COOKIE;
				}
				if (($filterCard & \Bitrix\Vote\Vote\EventLimits::BY_IP) && ($res["IP"] == \Bitrix\Main\Context::getCurrent()->getRequest()->getRemoteAddress()))
				{
					if ($vote["KEEP_IP_SEC"] > 0)
					{
						/**@var DateTime $res["DATE_VOTE"] */
						$res["DATE_VOTE"]->add("T".$vote["KEEP_IP_SEC"]."S");
						$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_IP_2", ["#DATE#" => $res["DATE_VOTE"]->toString()]), \Bitrix\Vote\Vote\EventLimits::BY_IP));
					}
					else
					{
						$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_IP"), \Bitrix\Vote\Vote\EventLimits::BY_IP));
					}
					$filterCard &= ~\Bitrix\Vote\Vote\EventLimits::BY_IP;
				}
				if (($filterCard & \Bitrix\Vote\Vote\EventLimits::BY_USER_ID) && ($res["USER_AUTH_USER_ID"] == $user->getId()))
				{
					$result->addError(new \Bitrix\Main\Error(Loc::getMessage("VOTE_ERROR_BY_USER_ID"), \Bitrix\Vote\Vote\EventLimits::BY_USER_ID));
					$filterCard &= ~\Bitrix\Vote\Vote\EventLimits::BY_USER_ID;
				}
				if ($filterCard <= 0)
					break;
			}
		}
		self::$canVoteStorage[$voteId][$user->getId()] = $result;
		return $result;
	}

	public function canRevote($user)
	{
		$canVoteResult = $this->canVote($user);
		$result = new \Bitrix\Main\Result();
		if ($canVoteResult->isSuccess() || (
				($this["OPTIONS"] & Vote\Option::ALLOW_REVOTE) &&
				$canVoteResult->getErrorCollection()->getErrorByCode(\Bitrix\Vote\Vote\EventLimits::BY_USER_ID) &&
				$canVoteResult->getErrorCollection()->count() == 1
			))
		{
			$result->setData($canVoteResult->getData());
			return $result;
		}
		return $canVoteResult;
	}

	public function canReadResult($user)
	{
		$result = new \Bitrix\Main\Result();

		if (!($user instanceof \Bitrix\Vote\User))
		{
			$user = \Bitrix\Vote\User::loadFromId($user);
		}

		if ($this["AUTHOR_ID"] != $user->getId())
		{
			if ($this["OPTIONS"] & Vote\Option::HIDE_RESULT)
			{
				$result->addError(new Error("Access denied.", "Hidden results"));
			}
			else if ($this["LAMP"] == "green")
			{
				$canVoteResult = $this->canVote($user);
				if ($canVoteResult->isSuccess())
					$result->addError(new Error("Access denied.", "Hidden results"));
			}
		}
		return $result;
	}

	/**
	 * @param string $offset Key for vote or attach data array.
	 * @return bool
	 */
	public function offsetExists($offset)
	{
		if ($offset == "QUESTIONS")
			return true;
		return array_key_exists($offset, $this->vote);
	}
	/**
	 * @param string $offset Key for vote or attach data array.
	 * @return array|mixed|null
	 */
	public function offsetGet($offset)
	{
		if (array_key_exists($offset, $this->vote))
			return $this->vote[$offset];
		else if ($offset == "QUESTIONS")
			return $this->questions;
		return null;
	}
	/**
	 * Is not supported.
	 * @param string $offset Key for vote or attach data array.
	 * @param mixed $value Value for vote or attach data array.
	 * @return void
	 * @throws \Bitrix\Main\NotSupportedException
	 */
	public function offsetSet($offset, $value)
	{
		throw new \Bitrix\Main\NotSupportedException("Model provide ArrayAccess only for reading");
	}
	/**
	 * Is not supported.
	 * @param string $offset Key for vote or attach data array.
	 * @return void
	 * @throws \Bitrix\Main\NotSupportedException
	 */
	public function offsetUnset($offset)
	{
		throw new \Bitrix\Main\NotSupportedException("Model provide ArrayAccess only for reading");
	}
}