Your IP : 18.117.8.233


Current Path : /var/www/axolotl/data/www/novosibirsk.axolotls.ru/bitrix/modules/crm/lib/webform/
Upload File :
Current File : /var/www/axolotl/data/www/novosibirsk.axolotls.ru/bitrix/modules/crm/lib/webform/form.php

<?php
/**
 * Bitrix Framework
 * @package bitrix
 * @subpackage crm
 * @copyright 2001-2016 Bitrix
 */
namespace Bitrix\Crm\WebForm;

use Bitrix\Main;
use Bitrix\Main\Context;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Config\Option;
use Bitrix\SalesCenter;
use Bitrix\Crm\UI\Webpack;
use Bitrix\Crm\SiteButton;

Loc::loadMessages(__FILE__);

class Form
{
	const REDIRECT_DELAY = 5;

	protected $id = null;
	protected static $defaultParams = array(
		'ACTIVE' => 'N',
		'IS_SYSTEM' => 'N',
		'COPYRIGHT_REMOVED' => 'N',
		'LANGUAGE_ID' => '',
		'USE_CAPTCHA' => 'N',
		'USE_LICENCE' => 'N',
		'LICENCE_BUTTON_IS_CHECKED' => 'Y',
		'FIELDS' => array(),
		'PRESET_FIELDS' => array(),
		'INVOICE_SETTINGS' => array(),
		'FORM_SETTINGS' => array(
			'VIEWS' => [],
			'DESIGN' => [],
		),
		'DEPENDENCIES' => array()
	);
	protected $params = array();
	protected $errors = array();
	protected $isEmbeddedAvailableChanged = false;

	public function __construct($id = null, array $params = null)
	{
		$this->params = self::$defaultParams;

		if($id)
		{
			$this->load($id);
		}

		if($params)
		{
			$this->set($params);
		}
	}

	public function isSystem()
	{
		return $this->params['IS_SYSTEM'] == 'Y';
	}

	public function setSystem()
	{
		$this->params['IS_SYSTEM'] = 'Y';
	}

	public function set(array $params)
	{
		$this->params = $params;
	}

	public function get()
	{
		return $this->params;
	}

	public function merge($params)
	{
		$oldData = $this->get();
		$params['FORM_SETTINGS'] = isset($params['FORM_SETTINGS']) ? $params['FORM_SETTINGS'] : [];
		$params['FORM_SETTINGS'] = $params['FORM_SETTINGS'] + $oldData['FORM_SETTINGS'];
		$this->set($params + $oldData);
	}

	public static function getIdByCode($formCode)
	{
		$idByCode = array();

		$formCodePieces = explode('_', $formCode);
		if(is_numeric($formCodePieces[0]))
		{
			return (int) $formCodePieces[0];
		}

		$cacheId = 'crm_webform_getIdByCode_' . serialize($formCode);
		$cache = \Bitrix\Main\Data\Cache::createInstance();
		if($cache->startDataCache(36000, $cacheId))
		{
			$formDb = Internals\FormTable::getList(array(
				'select' => array('ID', 'CODE'),
				'filter' => array('=CODE' => $formCode),
				'limit' => 1
			));
			while($form = $formDb->fetch())
			{
				$idByCode[$form['CODE']] = $form['ID'];
			}

			if(isset($idByCode[$formCode]))
			{
				if(defined("BX_COMP_MANAGED_CACHE"))
				{
					global $CACHE_MANAGER;
					$CACHE_MANAGER->startTagCache($cache->getPath($cacheId));
					$CACHE_MANAGER->RegisterTag(Form::getCacheTag($idByCode[$formCode]));
				}
			}

			$cache->endDataCache(array('CODE_BY_ID' => $idByCode));

			if(isset($idByCode[$formCode]))
			{
				if(defined("BX_COMP_MANAGED_CACHE"))
				{
					global $CACHE_MANAGER;
					$CACHE_MANAGER->endTagCache();
				}
			}
		}
		else
		{
			$cacheVars = $cache->getVars();
			if(isset($cacheVars['CODE_BY_ID']))
			{
				$idByCode = $cacheVars['CODE_BY_ID'];
			}
		}

		return isset($idByCode[$formCode]) ? $idByCode[$formCode] : null;
	}

	public static function updateBackgroundImage($formId, $fileId)
	{
		$updateResult = Internals\FormTable::update($formId, array('BACKGROUND_IMAGE' => $fileId));
		return $updateResult->isSuccess();
	}

	public static function delete($formId, $forceSystem = false)
	{
		$form = Internals\FormTable::getRowById($formId);
		if(!$form || (!$forceSystem && $form['IS_SYSTEM'] == 'Y'))
		{
			return false;
		}

		$deleteResult = Internals\FormTable::delete($formId);
		if($deleteResult->isSuccess())
		{
			Webpack\Form::instance($formId)->delete();
			static::cleanCacheByTag($formId);
			return true;
		}
		else
		{
			return false;
		}
	}

	public function isActive()
	{
		return $this->params['ACTIVE'] == 'Y';
	}

	public function isUsedCaptcha()
	{
		return $this->params['USE_CAPTCHA'] == 'Y';
	}

	public function isCallback()
	{
		return $this->params['IS_CALLBACK_FORM'] == 'Y';
	}

	public function checkSecurityCode($code)
	{
		return $this->params['SECURITY_CODE'] === $code;
	}

	public function loadOnlyForm($id)
	{
		$this->setId($id);
		$result = Internals\FormTable::getRowById($id);
		if(!$result)
		{
			return false;
		}

		if(!is_array($result['FORM_SETTINGS']))
		{
			$result['FORM_SETTINGS'] = [];
		}
		if (empty($result['FORM_SETTINGS']['VIEWS']))
		{
			$result['FORM_SETTINGS']['VIEWS'] = [
				'click' => [
					'type' => 'panel',
					'position' => 'right',
					'vertical' => 'bottom',
				],
				'auto' => [
					'type' => 'popup',
					'position' => 'center',
					'vertical' => 'bottom',
					'delay' => 5,
				]
			];
		}
		if (empty($result['FORM_SETTINGS']['DESIGN']))
		{
			$result['FORM_SETTINGS']['DESIGN'] = [];
		}

		$this->params = $result;
		return true;
	}

	public function load($id)
	{
		$this->setId($id);
		if(count($this->params) == count(self::$defaultParams) && !$this->loadOnlyForm($this->id))
		{
			return false;
		}

		$this->params['PRESET_FIELDS'] = array();
		$dbPresetField = Internals\PresetFieldTable::getList(array(
			'select' => array('ENTITY_NAME', 'FIELD_NAME', 'VALUE'),
			'filter' => array('=FORM_ID' => $id)
		));
		while($presetField = $dbPresetField->fetch())
		{
			$this->params['PRESET_FIELDS'][] = $presetField;
		}

		$fieldResult = Internals\FieldTable::getList(array(
			'filter' => array('=FORM_ID' => $id),
			'order' => array('SORT' => 'ASC', 'CAPTION')
		));
		$fieldResult->addFetchDataModifier(
			function ($data)
			{
				$data['ITEMS'] = is_array($data['ITEMS']) ? $data['ITEMS'] : [];
				return $data;
			}
		);
		$this->params['FIELDS'] = $fieldResult->fetchAll();


		$this->params['DEPENDENCIES'] = Internals\FieldDependenceTable::getList(array(
			'filter' => array('=FORM_ID' => $id)
		))->fetchAll();

		return true;
	}

	public function save($onlyCheck = false)
	{
		$this->errors = array();
		$result = $this->params;

		$scripts = $result['SCRIPTS'];
		unset($result['SCRIPTS']);

		$fields = $result['FIELDS'];
		unset($result['FIELDS']);

		$dependencies = $result['DEPENDENCIES'];
		unset($result['DEPENDENCIES']);

		$presetFields = $result['PRESET_FIELDS'];
		unset($result['PRESET_FIELDS']);

		$assignedById = $result['ASSIGNED_BY_ID'];
		$assignedWorkTime = $result['ASSIGNED_WORK_TIME'];
		unset($result['ASSIGNED_BY_ID']);
		unset($result['ASSIGNED_WORK_TIME']);

		// captcha
		$captchaKey = isset($result['CAPTCHA_KEY']) ? $result['CAPTCHA_KEY'] : null;
		$captchaSecret = isset($result['CAPTCHA_SECRET']) ? $result['CAPTCHA_SECRET'] : null;
		if ($captchaKey !== null && $captchaSecret !== null)
		{
			ReCaptcha::setKey($captchaKey, $captchaSecret);
		}
		unset($result['CAPTCHA_KEY']);
		unset($result['CAPTCHA_SECRET']);

		if($onlyCheck)
		{
			if(!in_array($result['ENTITY_SCHEME'], $this->getAllowedEntitySchemes()))
			{
				$this->errors[] = Loc::getMessage('CRM_WEBFORM_FORM_ERROR_SCHEME');
			}

			// captcha
			if($result['USE_CAPTCHA'] == 'Y')
			{
				$hasCaptchaKey = ((strlen(ReCaptcha::getKey()) > 0) ? 1 : 0) + ((strlen(ReCaptcha::getSecret()) > 0) ? 1 : 0);
				$hasCaptchaDefaultKey = strlen(ReCaptcha::getDefaultKey()) > 0 && strlen(ReCaptcha::getDefaultSecret()) > 0;
				if ($hasCaptchaKey == 1 || ($hasCaptchaKey == 0 && !$hasCaptchaDefaultKey))
				{
					$this->errors[] = Loc::getMessage('CRM_WEBFORM_FORM_ERROR_CAPTCHA_KEY');
				}
			}

			$formResult = new Main\Entity\Result;
			$result['DATE_CREATE'] = new Main\Type\DateTime();
			Internals\FormTable::checkFields($formResult, $this->id, $result);
			$this->prepareResult('FIELDS', $formResult);

			foreach($presetFields as $presetField)
			{
				$presetField['FORM_ID'] = (int) $this->id;
				$presetFieldResult = new Main\Entity\Result;
				Internals\PresetFieldTable::checkFields($presetFieldResult, null, $presetField);
				$replaceList = null;
				if(!$presetFieldResult->isSuccess())
				{
					$field = EntityFieldProvider::getField($presetField['ENTITY_NAME'] . '_' . $presetField['FIELD_NAME']);
					if($field)
					{
						$replaceList = array('VALUE' => $field['caption']);
					}
				}
				$this->prepareResult('PRESET_FIELDS', $presetFieldResult, $replaceList);
			}

			$fieldCodeList = array();
			foreach($fields as $field)
			{
				$field['FORM_ID'] = (int) $this->id;

				$fieldResult = new Main\Entity\Result;
				Internals\FieldTable::checkFields($fieldResult, null, $field);
				$this->prepareResult('FIELDS', $fieldResult);

				$fieldCodeList[] = $field['CODE'];
			}

			foreach($dependencies as $dependency)
			{
				$dependency['FORM_ID'] = (int) $this->id;

				if(!in_array($dependency['IF_FIELD_CODE'], $fieldCodeList))
				{
					continue;
				}
				if(!in_array($dependency['DO_FIELD_CODE'], $fieldCodeList))
				{
					continue;
				}

				$dependencyResult = new Main\Entity\Result;
				Internals\FieldDependenceTable::checkFields($dependencyResult, null, $dependency);
				$this->prepareResult('DEPENDENCIES', $dependencyResult);
			}

			return;
		}

		if(!$this->check())
		{
			return;
		}

		if($this->id)
		{
			unset($result['ID']);
			$formResult = Internals\FormTable::update($this->id, $result);
		}
		else
		{
			$result['DATE_CREATE'] = new Main\Type\DateTime();
			$formResult = Internals\FormTable::add($result);
			$this->id = $formResult->getId();
		}

		if(!$formResult->isSuccess())
		{
			return;
		}

		/* RESPONSIBLE QUEUE */
		$assignedById = is_array($assignedById) ? $assignedById : array($assignedById);
		$responsibleQueue = new ResponsibleQueue($this->id);
		$responsibleQueue->setList($assignedById, $assignedWorkTime == 'Y');

		/* PRESET FIELDS */
		Internals\PresetFieldTable::delete(array('FORM_ID' => $this->id));
		foreach($presetFields as $presetField)
		{
			$presetFieldResult = Internals\PresetFieldTable::add(array(
				'ENTITY_NAME' => $presetField['ENTITY_NAME'],
				'FIELD_NAME' => $presetField['FIELD_NAME'],
				'VALUE' => $presetField['VALUE'],
				'FORM_ID' => $this->id
			));
			$this->prepareResult('PRESET_FIELDS', $presetFieldResult);
		}


		/* FIELDS */
		$existedFieldList = array();
		$existedFieldDb = Internals\FieldTable::getList(array(
			'select' => array('ID'),
			'filter' => array('=FORM_ID' => $this->id)
		));
		while($existedField = $existedFieldDb->fetch())
		{
			$existedFieldList[] = $existedField['ID'];
		}

		$newFieldList = array();
		foreach($fields as $field)
		{
			$field['FORM_ID'] = $this->id;

			if($field['ID'] > 0)
			{
				$fieldId = $field['ID'];
				unset($field['ID']);
				$fieldResult = Internals\FieldTable::update($fieldId, $field);
				$newFieldList[] = $fieldId;
			}
			else
			{
				$fieldResult = Internals\FieldTable::add($field);
			}

			$this->prepareResult('FIELDS', $fieldResult);
		}

		$deleteFieldList = array_diff($existedFieldList, $newFieldList);
		foreach($deleteFieldList as $deleteFieldId)
		{
			Internals\FieldTable::delete($deleteFieldId);
		}

		/* DEPENDENCIES */
		$fieldCodeList = array();
		$fieldCodeDb = Internals\FieldTable::getList(array(
			'select' => array('CODE'),
			'filter' => array('=FORM_ID' => $this->id)
		));
		while($fieldCode = $fieldCodeDb->fetch())
		{
			$fieldCodeList[] = $fieldCode['CODE'];
		}

		$fieldDepDb = Internals\FieldDependenceTable::getList(array('filter' => array('=FORM_ID' => $this->id)));
		while($fieldDep = $fieldDepDb->fetch())
		{
			Internals\FieldDependenceTable::delete($fieldDep['ID']);
		}
		foreach($dependencies as $dependency)
		{
			if(!in_array($dependency['IF_FIELD_CODE'], $fieldCodeList))
			{
				continue;
			}
			if(!in_array($dependency['DO_FIELD_CODE'], $fieldCodeList))
			{
				continue;
			}

			$dependency['FORM_ID'] = $this->id;

			$dependencyResult = Internals\FieldDependenceTable::add($dependency);
			$this->prepareResult('DEPENDENCIES', $dependencyResult);
		}

		$this->buildScript();
	}

	public function buildScript()
	{
		Webpack\Form::instance($this->id)->build();
		if (Manager::isEmbeddingAvailable() && $this->isEmbeddingAvailable()
			&& ($this->isEmbeddingEnabled() || $this->isEmbeddedAvailableChanged))
		{
			SiteButton\Manager::updateScriptCacheWithForm($this->getId());
		}

		self::cleanCacheByTag($this->id);
	}

	public function getErrors()
	{
		return $this->errors;
	}

	public function hasErrors()
	{
		return count($this->errors) > 0;
	}

	/**
	 * Set design options.
	 *
	 * @param bool $mode Mode.
	 * @return $this
	 */
	public function setEmbeddingEnabled($mode)
	{
		$old = $this->isEmbeddingEnabled();
		$this->params['FORM_SETTINGS']['EMBEDDING_ENABLED'] = $mode ? 'Y' : 'N';
		$new = $this->isEmbeddingEnabled();
		if ($old !== $new)
		{
			$this->isEmbeddedAvailableChanged = true;
		}

		return $this;
	}

	/**
	 * Set design options.
	 *
	 * @param array $options Options.
	 * @return $this
	 */
	public function setDesignOptions(array $options = [])
	{
		$designOptions = (new Design($options))->getOptions();
		unset($designOptions['backgroundImage']);
		$this->params['FORM_SETTINGS']['DESIGN'] = $designOptions;

		$this->params['BUTTON_COLOR_BG'] = $options['color']['primary']
			?: $this->params['BUTTON_COLOR_BG'];

		$this->params['BUTTON_COLOR_FONT'] = $options['color']['primaryText']
			?: $this->params['BUTTON_COLOR_FONT'];

		return $this;
	}

	/**
	 * Get design options.
	 *
	 * @param bool $asTyped Return as typed.
	 * @return array
	 */
	public function getDesignOptions($asTyped = false)
	{
		$options = $this->params['FORM_SETTINGS']['DESIGN'];
		$options = new Design($options);
		$options = $asTyped ? $options->toTypedArray() : $options->getOptions();
		$options['color']['primary'] = $this->params['BUTTON_COLOR_BG']
			?: $options['color']['primary'];
		$options['color']['primaryText'] = $this->params['BUTTON_COLOR_FONT']
			?: $options['color']['primaryText'];

		$options['backgroundImage'] = '';
		$bgImageId = $this->params['BACKGROUND_IMAGE'];
		if ($bgImageId && $file = \CFile::getByID($bgImageId)->fetch())
		{
			if ($file['~src'])
			{
				$options['backgroundImage'] = $file['~src'];
			}
			else
			{
				$options['backgroundImage'] = Main\Web\WebPacker\Builder::getDefaultSiteUri()
					. \CFile::GetPath($bgImageId);
			}
		}

		return $options;
	}

	protected function prepareResult($sect, Main\Entity\Result $entityResult, $replaceList = null)
	{
		if($entityResult->isSuccess())
		{
			return;
		}

		$errors = $entityResult->getErrors();
		foreach($errors as $error)
		{
			$errorMessage = $error->getMessage();
			if($replaceList)
			{
				$errorMessage = str_replace(array_keys($replaceList), array_values($replaceList), $errorMessage);
			}

			switch ($sect)
			{
				case 'PRESET_FIELDS':
					$errorMessage = Loc::getMessage('CRM_WEBFORM_FORM_PRESET_FIELDS') . ": " . $errorMessage;
					break;
			}

			$this->errors[] = $errorMessage;
		}
	}

	public function check()
	{
		$this->save(true);

		return count($this->errors) === 0;
	}

	public function setId($id)
	{
		$this->id = $id;
	}

	public function getId()
	{
		return $this->id;
	}

	public function getButtonCaption()
	{
		return $this->params['BUTTON_CAPTION'] ? $this->params['BUTTON_CAPTION'] : Loc::getMessage('CRM_WEBFORM_FORM_BUTTON_CAPTION_DEFAULT');
	}

	public function getAgreementUrl()
	{
		return Script::getAgreementUrl($this->get());
	}

	public function getSuccessPageUrl()
	{
		return $this->params['RESULT_SUCCESS_URL'] ?: Script::getSuccessPageUrl($this->get());
	}

	public function getFields()
	{
		return is_array($this->params['FIELDS']) ? $this->params['FIELDS'] : [];
	}

	public function getAllowedEntitySchemes()
	{
		//TODO: fields checker
		return array_keys(Entity::getSchemes());
		//return array(Helper::ENUM_ENTITY_SCHEME_CONTACT);
	}

	public function getPresetFields()
	{
		return $this->params['PRESET_FIELDS'];
	}

	public function getDependencies($opposites = true)
	{
		$dependencyList = array();
		foreach($this->params['DEPENDENCIES'] as $dependency)
		{
			$dependencyList[$dependency['DO_FIELD_CODE']][] = array(
				'if' => array(
					'fieldname' => $dependency['IF_FIELD_CODE'],
					'action' => $dependency['IF_ACTION'],
					'value' => $dependency['IF_VALUE'],
				),
				'do' => array(
					'action' => $dependency['DO_ACTION'],
					'value' => $dependency['DO_VALUE'],
				),
			);

			if (!$opposites)
			{
				continue;
			}

			// add mirror dependency
			if($dependency['IF_ACTION'] != 'change')
			{
				continue;
			}

			if(!in_array($dependency['DO_ACTION'], array('show', 'hide')))
			{
				continue;
			}

			$mirror = $dependency['DO_ACTION'] == 'hide' ? 'show' : 'hide';
			$dependencyList[$dependency['DO_FIELD_CODE']][] = array(
				'if' => array(
					'fieldname' => $dependency['IF_FIELD_CODE'],
					'action' => $dependency['IF_ACTION'],
					'value' => $dependency['IF_VALUE'],
					'operation' => '!='
				),
				'do' => array(
					'action' => $mirror,
				),
			);
		}

		return $dependencyList;
	}

	public function getFieldsDescription()
	{
		return EntityFieldProvider::getFieldsDescription($this->getFields());
	}

	public function getFieldsMap()
	{
		$fields = $this->getFieldsDescription();
		$dependencyList = $this->getDependencies();
		$currencyId = $this->getCurrencyId();

		$fieldList = array();
		foreach($fields as $field)
		{

			if($field['TYPE'] == 'section')
			{
				$preparedField = array(
					'type' => $field['TYPE'],
					'name' => $field['CODE'],
					'caption' => $field['CAPTION'],
				);
			}
			else
			{
				$preparedField = array(
					'type' => $field['TYPE'],
					'type_original' => $field['TYPE_ORIGINAL'],
					'name' => $field['CODE'], // 'uf_field_' . $field['ID'],
					'entity_name' => $field['ENTITY_NAME'],
					'entity_field_name' => $field['ENTITY_FIELD_NAME'],
					'caption' => $field['CAPTION'] ? $field['CAPTION'] : $field['ENTITY_FIELD_CAPTION'],
					'required' => $field['REQUIRED'] == 'Y' ? true : false,
					'multiple' => $field['MULTIPLE'] == 'Y' ? true : false,
					'multiple_original' => $field['MULTIPLE_ORIGINAL'],
					'hidden' => false,
					'placeholder' => $field['PLACEHOLDER'],
					'value' => $field['VALUE'],
					'value_type' => $field['VALUE_TYPE'],
					'settings_data' => $field['SETTINGS_DATA']
				);

				if(isset($field['ITEMS']) && is_array($field['ITEMS']))
				{
					$preparedField['items'] = array();
					foreach($field['ITEMS'] as $item)
					{
						$price = isset($item['PRICE']) ? $item['PRICE'] : null;
						if ($price !== null && !is_numeric($price))
						{
							$price = 0;
						}
						$discount = isset($item['DISCOUNT']) ? $item['DISCOUNT'] : null;
						if ($discount !== null && !is_numeric($discount))
						{
							$discount = 0;
						}
						$preparedItem = array(
							'title' => $item['VALUE'],
							'value' => $item['ID'],
						);
						if ($price !== null)
						{
							$preparedItem['price'] = $price;
							$preparedItem['changeablePrice'] = isset($item['CUSTOM_PRICE'])
								&& $item['CUSTOM_PRICE'] === 'Y'
								&& Manager::isOrdersAvailable();
							$preparedItem['discount'] = $discount ?: 0;
							$preparedItem['price_formatted'] = \CCrmCurrency::MoneyToString($price, $currencyId);
						}
						$preparedField['items'][] = $preparedItem;
					}
				}
			}

			if(isset($dependencyList[$field['CODE']]))
			{
				$preparedField['dependences'] = $dependencyList[$field['CODE']];
				$preparedField['hidden'] = true;
			}

			$fieldList[] = $preparedField;
		}

		return $fieldList;
	}

	public function getExternalAnalyticsData()
	{
		$data = Helper::getExternalAnalyticsData($this->params['CAPTION']);
		$steps = array();

		$steps[] = array(
			'NAME' => $data['view']['name'],
			'CODE' => $data['view']['code']
		);
		$steps[] = array(
			'NAME' => $data['start']['name'],
			'CODE' => $data['start']['code']
		);
		foreach($this->getFieldsMap() as $field)
		{
			if(Internals\FieldTable::isUiFieldType($field['type']))
			{
				continue;
			}

			$steps[] = array(
				'NAME' => str_replace('%name%', $field['caption'], $data['field']['name']),
				'CODE' => str_replace('%code%', $field['name'], $data['field']['code']),
			);
		}
		$steps[] = array(
			'NAME' => $data['end']['name'],
			'CODE' => $data['end']['code']
		);

		foreach($steps as $stepIndex => $step)
		{
			$step['NAME'] = str_replace('%name%', $step['NAME'], $data['template']['name']);
			$step['EVENT'] = str_replace(array('%code%', '%form_id%'), array($step['CODE'], (int) $this->getId()), $data['eventTemplate']['code']);
			$step['CODE'] = str_replace('%code%', $step['CODE'], $data['template']['code']);
			$steps[$stepIndex] = $step;
		}

		return array(
			'CATEGORY' => $data['category'],
			'STEPS' => $steps
		);
	}

	public function getCurrencyId()
	{
		return $this->params['CURRENCY_ID'] ? $this->params['CURRENCY_ID'] : \CCrmCurrency::GetBaseCurrencyID();
	}

	/**
	 * Return true if form payable.
	 *
	 * @return bool
	 */
	public function isPayable()
	{
		return $this->params['IS_PAY'] === 'Y';
	}

	/**
	 * Get redirect delay.
	 *
	 * @return int
	 */
	public function getRedirectDelay()
	{
		$delay = Form::REDIRECT_DELAY;
		if (!empty($this->params['FORM_SETTINGS']['REDIRECT_DELAY']))
		{
			$delay = (int) $this->params['FORM_SETTINGS']['REDIRECT_DELAY'];
		}

		return $delay;
	}

	/**
	 * Get success text.
	 *
	 * @return string
	 */
	public function getSuccessText()
	{
		return $this->params['RESULT_SUCCESS_TEXT'];
	}

	public function getLanguageId()
	{
		return ($this->params['LANGUAGE_ID'] ? $this->params['LANGUAGE_ID'] : Context::getCurrent()->getLanguage());
	}

	public static function copy($formId, $userId = null)
	{
		// copy form
		$form = Internals\FormTable::getRowById($formId);
		if(!$form)
		{
			return null;
		}

		unset($form['ID'], $form['DATE_CREATE'], $form['ACTIVE_CHANGE_DATE'], $form['SECURITY_CODE'], $form['XML_ID']);
		$form['NAME'] = Loc::getMessage('CRM_WEBFORM_FORM_COPY_NAME_PREFIX') . ' ' . $form['NAME'];
		$form['ACTIVE'] = 'N';
		$form['IS_SYSTEM'] = 'N';
		$form['ACTIVE_CHANGE_BY'] = $userId;
		$form['DATE_CREATE'] = new Main\Type\DateTime();
		$resultFormAdd = Internals\FormTable::add($form);
		if(!$resultFormAdd->isSuccess())
		{
			return null;
		}
		$newFormId = $resultFormAdd->getId();

		// copy fields
		$fieldDb = Internals\FieldTable::getList(array(
			'filter' => array('=FORM_ID' => $formId)
		));
		while($field = $fieldDb->fetch())
		{
			unset($field['ID']);
			$field['FORM_ID'] = $newFormId;
			Internals\FieldTable::add($field);
		}

		// copy field dependencies
		$fieldDepDb = Internals\FieldDependenceTable::getList(array(
			'filter' => array('=FORM_ID' => $formId)
		));
		while($fieldDep = $fieldDepDb->fetch())
		{
			unset($fieldDep['ID']);
			$fieldDep['FORM_ID'] = $newFormId;
			Internals\FieldDependenceTable::add($fieldDep);
		}

		// copy preset fields
		$presetFieldDb = Internals\PresetFieldTable::getList(array(
			'filter' => array('=FORM_ID' => $formId)
		));
		while($presetField = $presetFieldDb->fetch())
		{
			$presetField['FORM_ID'] = $newFormId;
			Internals\PresetFieldTable::add($presetField);
		}


		return $newFormId;
	}

	public static function activate($formId, $isActivate = true, $changeUserBy = null)
	{
		$updateFields = array('ACTIVE' => $isActivate ? 'Y' : 'N');
		if($changeUserBy)
		{
			$updateFields['ACTIVE_CHANGE_BY'] = $changeUserBy;
		}
		$updateResult = Internals\FormTable::update($formId, $updateFields);
		if($updateResult->isSuccess())
		{
			(new static($formId))->buildScript();
			return true;
		}
		else
		{
			return false;
		}
	}

	/*
	 * Return true if has result.
	 *
	 * @param string $originId Origin ID.
	 * @return bool
	 * */
	public function hasResult($originId)
	{
		$webFormResult = Internals\ResultTable::getList(array(
			'select' => array('ID'),
			'filter' => array(
				'=FORM_ID' => $this->getId(),
				'=ORIGIN_ID' => $originId,
			),
			'limit' => 1
		));

		return $webFormResult->getSelectedRowsCount() > 0;
	}

	/*
	 * Fill.
	 *
	 * @return Fill
	 * */
	public function fill()
	{
		return new Fill($this);
	}

	/*
	 * Add result.
	 *
	 * @param array $resultFields Result fields.
	 * @param array $resultParameters Result parameters.
	 * @return Result
	 * */
	public function addResult($resultFields, $resultParameters = array())
	{
		$this->errors = array();

		// get section fields
		$sectionFields = array();
		$currentSection = '';
		$fieldsMap = $this->getFieldsMap();
		foreach($fieldsMap as $field)
		{
			if($field['type'] == 'section')
			{
				$currentSection = $field['name'];
				continue;
			}

			if(!$currentSection)
			{
				continue;
			}

			$sectionFields[$currentSection][] = $field['name'];
		}

		// format fields by name
		$fields = array();
		foreach($resultFields as $fieldKey => $field)
		{
			$fields[$field['name']] = $field;
		}

		$hiddenFieldNames = array();
		// set hidden flag
		foreach($resultFields as $fieldKey => $field)
		{
			if(!isset($field['dependences']))
			{
				$field['hidden'] = false;
				$data['FIELDS'][$fieldKey] = $field;
				continue;
			}

			$isHidden = false;
			foreach($field['dependences'] as $dep)
			{
				if($dep['if']['action'] != 'change')
				{
					continue;
				}

				if(!isset($fields[$dep['if']['fieldname']]))
				{
					continue;
				}

				$valuesA = $fields[$dep['if']['fieldname']]['values'];
				$valueB = $dep['if']['value'];
				switch($dep['if']['operation'])
				{
					case '!=':
						$isSuccess = !in_array($valueB, $valuesA);
						break;

					default:
						$isSuccess = in_array($valueB, $valuesA);
						break;
				}

				if(!$isSuccess)
				{
					continue;
				}

				$isHidden = $dep['do']['action'] == 'hide';
			}

			if($field['type'] == 'section')
			{
				foreach($sectionFields[$field['name']] as $sectionFieldName)
				{
					$hiddenFieldNames[$sectionFieldName][] = $isHidden;
				}
			}
			else
			{
				$field['hidden'] = $isHidden;
				$resultFields[$fieldKey] = $field;
			}
		}

		$fieldEmailValue = null;
		$fieldPhoneValue = null;
		$fieldPhoneEntityTypeName = null;
		foreach($resultFields as $fieldKey => $field)
		{
			if(($field['entity_field_name'] == 'EMAIL' || $field['type'] == 'email') && $field['values'][0])
			{
				$fieldEmailValue = $field['values'][0];
			}
			if(($field['entity_field_name'] == 'PHONE' || $field['type'] == 'phone') && $field['values'][0])
			{
				$fieldPhoneEntityTypeName = $field['entity_name'];
				$fieldPhoneValue = $field['values'][0];
			}

			if(!isset($hiddenFieldNames[$field['name']]))
			{
				continue;
			}

			$field['hidden'] = end($hiddenFieldNames[$field['name']]);
			$resultFields[$fieldKey] = $field;
		}

		$resultProducts = array();
		$activityFields = array();
		foreach($resultFields as $fieldKey => $field)
		{
			if($field['hidden'])
			{
				continue;
			}

			if(!isset($field['values'][0]) || (!$field['values'][0] && $field['values'][0] !== '0'))
			{
				continue;
			}

			$activityFieldValues = array();
			if(is_array($field['items']) && count($field['items']) > 0)
			{
				foreach($field['items'] as $item)
				{
					if(!in_array($item['value'], $field['values']))
					{
						continue;
					}

					$activityFieldValues[] = $item;
				}
			}
			else
			{
				$activityFieldValues = $field['values'];
			}
			$activityFields[] = array(
				'type' => $field['type'],
				'code' => $field['name'],
				'required' => $field['required'],
				'caption' => $field['caption'],
				'value' => $activityFieldValues,
			);


			if($field['type'] != 'product')
			{
				continue;
			}

			$productValues = [];
			foreach ($field['values'] as $value)
			{
				$productValue = [
					'id' => 0,
					'quantity' => 1
				];
				if (is_array($value))
				{
					$productValue['id'] = $value['id'] ?: 0;
					$productValue['quantity'] = $value['quantity'] ?: 1;
					$productValue['price'] = $value['price'] ?: null;
				}
				else
				{
					$productValue['id'] = $value;
				}

				$productValues[$productValue['id']] = $productValue;
			}

			foreach($field['items'] as $item)
			{
				$productValue = $productValues[$item['value']];
				if(!is_array($productValue))
				{
					continue;
				}

				$productId = is_numeric($item['value']) ? $item['value'] : 0;
				$product = [
					'ID' => $productId,
					'NAME' => $item['title'],
					'PRICE' => $item['changeablePrice'] && $productValue['price']
						? $productValue['price']
						: $item['price'],
					'DISCOUNT' => $item['discount'] ?: 0,
					'QUANTITY' => $productValue['quantity'],
				];
				if ($productId && ($productData = \CCrmProduct::GetByID($productId)))
				{
					$product['VAT_INCLUDED'] = $productData['VAT_INCLUDED'];
					if ($productData['VAT_ID'])
					{
						$vatData = \CCrmVat::GetByID($productData['VAT_ID']);
						if ($vatData && $vatData['RATE'])
						{
							$product['VAT_RATE'] = floatval($vatData['RATE']) / 100;
						}
					}
				}
				$resultProducts[] = $product;
			}
		}

		// set responsible
		$responsibleQueue = new ResponsibleQueue($this->id);
		$responsibleId = $responsibleQueue->getNextId();
		$this->params['ASSIGNED_BY_ID'] = $responsibleId ? $responsibleId : 1;

		// add Result
		$data = array(
			'FORM' => $this->params,
			'DUPLICATE_MODE' => $this->params['DUPLICATE_MODE'],
			'PRESET_FIELDS' => $this->params['PRESET_FIELDS'],
			'COMMON_FIELDS' => isset($resultParameters['COMMON_FIELDS']) ? $resultParameters['COMMON_FIELDS'] : array(),
			'COMMON_DATA' => $resultParameters['COMMON_DATA'],
			'PLACEHOLDERS' => isset($resultParameters['PLACEHOLDERS']) ? $resultParameters['PLACEHOLDERS'] : array(),
			'DISABLE_FIELD_CHECKING' => $resultParameters['DISABLE_FIELD_CHECKING'] ?? false,
			'ORIGIN_ID' => isset($resultParameters['ORIGIN_ID']) ? $resultParameters['ORIGIN_ID'] : null,
			'ENTITY_SCHEME' => $this->params['ENTITY_SCHEME'],
			'INVOICE_SETTINGS' => $this->params['INVOICE_SETTINGS'],
			'FORM_ID' => $this->id,
			'FIELDS' => $resultFields,
			'ASSIGNED_BY_ID' => $this->params['ASSIGNED_BY_ID'],
			'PRODUCTS' => $resultProducts,
			'CURRENCY_ID' => $this->getCurrencyId(),
			'ACTIVITY_FIELDS' => $activityFields,
			'IS_CALLBACK' => $this->isCallback(),
			'CALLBACK_PHONE' => $fieldPhoneValue,
			'ENTITIES' => isset($resultParameters['ENTITIES']) ? $resultParameters['ENTITIES'] : [],
		);
		$result = new Result(null, $data);
		$result->save();
		if($result->hasErrors())
		{
			$this->errors = $result->getErrors();
		}
		else
		{
			if($fieldEmailValue)
			{
				self::sendEventFormSent(array(
						'RESULT_SUCCESS_TEXT' => $this->params['RESULT_SUCCESS_TEXT'],
						'RESULT_SUCCESS_URL' => $this->params['RESULT_SUCCESS_URL'],
						'RESULT_FAILURE_TEXT' => $this->params['RESULT_FAILURE_TEXT'],
						'RESULT_FAILURE_URL' => $this->params['RESULT_FAILURE_URL'],
						'FORM_CAPTION' => $this->params['CAPTION'],
						'EMAIL_TO' => $fieldEmailValue,
					)
				);
			}

			$stopCallBack = false;
			if (isset($resultParameters['STOP_CALLBACK']) && $resultParameters['STOP_CALLBACK'])
			{
				$stopCallBack = true;
			}

			if($fieldPhoneValue && $this->isCallback())
			{
				if(Callback::hasPhoneNumbers())
				{
					Callback::sendCallEvent(array(
						'CRM_ENTITY_TYPE' => $fieldPhoneEntityTypeName,
						'CRM_ENTITY_ID' => $result->getResultEntity()->getEntityIdByTypeName($fieldPhoneEntityTypeName),
						'CALL_FROM' => $this->params['CALL_FROM'],
						'CALL_TO' => $fieldPhoneValue,
						'TEXT' => $this->params['CALL_TEXT'],
						'STOP_CALLBACK' => $stopCallBack,
						'CRM_ENTITY_LIST' => $result->getResultEntity()->getResultEntities()
					));
				}
			}

			$redirectUrl = $this->params['RESULT_SUCCESS_URL'];
			if($this->isPayable())
			{
				$resultEntity = $result->getResultEntity();
				if ($resultEntity)
				{
					$resultRedirectUrl = null;
					if ($resultEntity->getOrderId())
					{
						$resultRedirectUrl = SalesCenter\Payment::getUrl($resultEntity->getOrderId());
					}
					elseif($resultEntity->getInvoiceId())
					{
						$resultRedirectUrl = \CAllCrmInvoice::getPublicLink($resultEntity->getInvoiceId());
					}

					if($resultRedirectUrl)
					{
						$redirectUrl = new \Bitrix\Main\Web\Uri($resultRedirectUrl);
						$redirectUrl->addParams(array('form_id' => $this->getId()));
						$redirectUrl = $redirectUrl->getLocator();
					}
				}
			}
			$result->setUrl($redirectUrl);
		}

		return $result;
	}

	protected function sendEventFormSent($fields)
	{
		Main\Mail\Event::send(array(
				'EVENT_NAME' => 'CRM_WEB_FORM_FILLED',
				'C_FIELDS' => $fields,
				'LID' => Main\Context::getCurrent()->getSite()
		));

		Main\Mail\Event::send(array(
				'EVENT_NAME' => 'CRM_WEB_FORM_FILLED_' . $this->getId(),
				'C_FIELDS' => $fields,
				'LID' => Main\Context::getCurrent()->getSite()
		));
	}

	public function getScripts($publicFormPath)
	{
		$script = new Script(
			Main\Context::getCurrent()->getServer()->getHttpHost(),
			Main\Context::getCurrent()->getRequest()->isHttps()
		);

		$scriptParams = array(
			'id' => $this->params['ID'],
			'lang' => $this->getLanguageId(),
			'sec' => $this->params['SECURITY_CODE']
		);

		return array(
			'INLINE' => $script->getInline($scriptParams),
			'BUTTON' => $script->getButton($scriptParams + array('button_caption' => Loc::getMessage('CRM_WEBFORM_FORM_SCRIPT_BUTTON_TEXT'))),
			'LINK' => $script->getLink($scriptParams + array('button_caption' => Loc::getMessage('CRM_WEBFORM_FORM_SCRIPT_BUTTON_TEXT'))),
			'DELAY' => $script->getDelay($scriptParams + array('delay' => 5))
		);
	}

	public function sendScriptsEmail($email)
	{

	}

	public function isEmbeddingEnabled()
	{
		return (
			!empty($this->params['FORM_SETTINGS']['EMBEDDING_ENABLED'])
			&& $this->params['FORM_SETTINGS']['EMBEDDING_ENABLED'] === 'Y'
		);
	}

	public function isEmbeddingAvailable()
	{
		foreach ($this->getFields() as $field)
		{
			if ($field['TYPE'] === Internals\FieldTable::TYPE_ENUM_RESOURCEBOOKING)
			{
				return false;
			}
		}

		return true;
	}

	public static function getCacheTag($formId)
	{
		return 'BX_CRM_WEBFORM_ID_' . $formId;
	}

	public static function cleanCacheByTag($formId)
	{
		if(defined("BX_COMP_MANAGED_CACHE"))
		{
			$taggedCache = Main\Application::getInstance()->getTaggedCache();
			$taggedCache->clearByTag(static::getCacheTag($formId));
		}
	}

	public static function getCounters($formId, $formEntityScheme = null)
	{
		$result = array(
			'ENTITY' => array(),
			'COMMON' => array()
		);
		$entityList = Entity::getList();
		if($formEntityScheme)
		{
			$entitySchemes = Entity::getSchemes();
			if($entitySchemes[$formEntityScheme])
			{
				foreach($entityList as $entityName => $entityCaption)
				{
					if(!in_array($entityName, $entitySchemes[$formEntityScheme]['ENTITIES']))
					{
						unset($entityList[$entityName]);
					}
				}
			}
		}

		$entityFieldMap = Internals\FormCounterTable::getEntityFieldsMap();
		$counters = Internals\FormCounterTable::getByFormId($formId);
		foreach($counters as $counter => $value)
		{
			if(isset($entityFieldMap[$counter]))
			{
				$entityName = $entityFieldMap[$counter];
				if(!isset($entityList[$entityName]))
				{
					continue;
				}

				$entityCaption = $entityList[$entityName];
				$result['ENTITY'][] = array(
					'ENTITY_NAME' => $entityName,
					'ENTITY_CAPTION' => $entityCaption,
					'VALUE' => $value,
				);
			}
			else
			{
				$result['COMMON'][$counter] = $value;
			}
		}

		return $result;
	}

	public static function incCounterView($formId)
	{
		Internals\FormViewTable::add(array('FORM_ID' => $formId));
		return Internals\FormCounterTable::incCounters($formId, array('VIEWS'));
	}

	public static function incCounterStartFill($formId)
	{
		Internals\FormStartEditTable::add(array('FORM_ID' => $formId));
		return Internals\FormCounterTable::incCounters($formId, array('START_FILL'));
	}

	public static function incCounterEndFill($formId)
	{
		return Internals\FormCounterTable::incCounters($formId, array('END_FILL'));
	}

	public static function resetCounters($formId)
	{
		$newCounterId = Internals\FormCounterTable::addByFormId($formId);
		// TODO: merge all counters
		return $newCounterId;
	}

	public static function canRemoveCopyright()
	{
		if(!Main\Loader::includeModule('bitrix24'))
		{
			return true;
		}

		return \CBitrix24::IsLicensePaid();
	}

	public static function getMaxActivatedFormLimit()
	{
		return intval(Option::get('crm', '~crm_webform_max_activated', 99999));
	}

	public static function canActivateForm()
	{
		if(!Main\Loader::includeModule("bitrix24"))
		{
			return true;
		}

		$maxActivated = self::getMaxActivatedFormLimit();
		return $maxActivated > Internals\FormTable::getCount(array('=ACTIVE' => 'Y', '=IS_SYSTEM' => 'N'));
	}

	public static function actualizeFormsActiveState($maxActivated = null)
	{
		if(!$maxActivated)
		{
			$maxActivated = self::getMaxActivatedFormLimit();
		}

		$formDb = Internals\FormTable::getList(array(
			'select' => array('ID'),
			'filter' => array('=ACTIVE' => 'Y', '=IS_SYSTEM' => 'N'),
			'order' => array('ID' => 'ASC')
		));
		while($form = $formDb->fetch())
		{
			if($maxActivated > 0)
			{
				--$maxActivated;
				continue;
			}

			static::activate($form['ID'], false);
		}

		if(!self::canRemoveCopyright())
		{
			$connection = Main\Application::getConnection();
			$connection->query("UPDATE b_crm_webform SET COPYRIGHT_REMOVED='N'");
		}
	}

	public static function onAfterSetOptionCrmWebFormMaxActivated(\Bitrix\Main\Event $event)
	{
		self::actualizeFormsActiveState();
	}

	public static function onBitrix24LicenseChange(\Bitrix\Main\Event $event)
	{
		preg_match("/(project|tf|team)$/is", $event->getParameter(0), $matches);
		$licenseType = strtolower($matches[0]);
		if ($licenseType)
		{
			$maxActivated = null;
			switch($licenseType)
			{
				case 'project':
					$maxActivated = 1;
					break;
				case 'tf':
					$maxActivated = 2;
					break;
				case 'team':
					$maxActivated = 4;
					break;
				case 'demo':
					$maxActivated = 9999;
					break;
			}

			self::actualizeFormsActiveState($maxActivated);
		}
	}
}