Your IP : 3.134.247.163


Current Path : /home/bitrix/ext_www/coffe.land/bitrix/modules/landing/lib/
Upload File :
Current File : /home/bitrix/ext_www/coffe.land/bitrix/modules/landing/lib/block.php

<?php
namespace Bitrix\Landing;

use \Bitrix\Main\Web\HttpClient;
use \Bitrix\Main\Web\Json;
use \Bitrix\Main\Page\Asset;
use \Bitrix\Main\Web\DOM;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Landing\Internals;

Loc::loadMessages(__FILE__);

class Block extends \Bitrix\Landing\Internals\BaseTable
{
	/**
	 * Add images from new block to local storage.
	 */
	const ADD_FILES_TO_LOCAL_STORAGE = false;

	/**
	 * Dir of repoitory of blocks.
	 */
	const BLOCKS_DIR = '/bitrix/blocks';

	/**
	 * Tag for managed cache.
	 */
	const BLOCKS_TAG = 'landing_blocks';

	/**
	 * Block preview filename.
	 */
	const PREVIEW_FILE_NAME = 'preview.jpg';

	/**
	 * Local css filename.
	 */
	const CSS_FILE_NAME = 'style.css';

	/**
	 * Local js filename.
	 */
	const JS_FILE_NAME = 'script.js';

	/**
	 * Pattern for repo code.
	 */
	const REPO_MASK = '/^repo_([\d]+)$/';

	/**
	 * Life time for mark new block.
	 */
	const NEW_BLOCK_LT = 1209600;//86400 * 14

	/**
	 * Access level: access deined.
	 */
	const ACCESS_D = 'D';

	/**
	 * Access level: edit only design.
	 */
	const ACCESS_V = 'V';

	/**
	 * Access level: edit content and design (not delete).
	 */
	const ACCESS_W = 'W';

	/**
	 * Access level: full access.
	 */
	const ACCESS_X = 'X';

	/**
	 * Symbolic code of card.
	 */
	const CARD_SYM_CODE = 'card';

	/**
	 * Symbolic code of preset.
	 */
	const PRESET_SYM_CODE = 'preset';

	/**
	 * Internal class.
	 * @var string
	 */
	public static $internalClass = 'BlockTable';

	/**
	 * Id of current block.
	 * @var int
	 */
	protected $id = 0;

	/**
	 * Id of landing.
	 * @var int
	 */
	protected $lid = 0;

	/**
	 * Id of site of landing.
	 * @var int
	 */
	protected $siteId = 0;

	/**
	 * Sort of current block.
	 * @var int
	 */
	protected $sort = 0;

	/**
	 * Is the rest block if > 0.
	 * @var int
	 */
	protected $repoId = 0;

	/**
	 * Code of current block.
	 * @var string
	 */
	protected $code = '';

	/**
	 * Custom anchor of the block.
	 * @var string
	 */
	protected $anchor = '';

	/**
	 * Actually content of current block.
	 * @var string
	 */
	protected $content = '';

	/**
	 * Access for this block.
	 * @see ACCESS_* constants.
	 * @var string
	 */
	protected $access = 'X';

	/**
	 * Additional data of current block.
	 * @var array
	 */
	protected $metaData = array();

	/**
	 * Active or not current block.
	 * @var boolean
	 */
	protected $active = false;

	/**
	 * Deleted or not current block.
	 * @var boolean
	 */
	protected $deleted = false;

	/**
	 * Public or not current block.
	 * @var boolean
	 */
	protected $block = false;

	/**
	 * Manifest from database.
	 * @var array
	 */
	protected $manifestDB = null;

	/**
	 * Document root.
	 * @var string
	 */
	protected $docRoot = '';

	/**
	 * Instance of Error.
	 * @var \Bitrix\Landing\Error
	 */
	protected $error = null;

	/**
	 * Constructor.
	 * @param int $id Block id.
	 * @param array $data Data row from BlockTable (by default get from DB).
	 */
	public function __construct($id, $data = array())
	{
		if (empty($data) || !is_array($data))
		{
			$data = parent::getList(array(
				'select' => array(
					'*',
					'SITE_ID' => 'LANDING.SITE_ID',
					'MANIFEST' => 'MANIFEST_DB.MANIFEST'
				),
				'filter' => array(
					'ID' => $id
				)
			))->fetch();
			if (!$data)
			{
				$id = 0;
			}
		}

		// if content is empty, fill from repository
		if (!isset($data['CONTENT']) || trim($data['CONTENT']) == '')
		{
			$data['CONTENT'] = '';
		}

		$this->id = intval($id);
		$this->lid = isset($data['LID']) ? intval($data['LID']) : 0;
		$this->siteId = isset($data['SITE_ID']) ? intval($data['SITE_ID']) : 0;
		$this->sort = isset($data['SORT']) ? intval($data['SORT']) : '';
		$this->code = isset($data['CODE']) ? trim($data['CODE']) : '';
		$this->anchor = isset($data['ANCHOR']) ? trim($data['ANCHOR']) : '';
		$this->active = isset($data['ACTIVE']) && $data['ACTIVE'] == 'Y';
		$this->deleted = isset($data['DELETED']) && $data['DELETED'] == 'Y';
		$this->public = isset($data['PUBLIC']) && $data['PUBLIC'] == 'Y';
		$this->content = (!$this->deleted && isset($data['CONTENT'])) ? trim($data['CONTENT']) : '';

		if (isset($data['ACCESS']))
		{
			$this->access = $data['ACCESS'];
		}
		if (isset($data['MANIFEST']))
		{
			$this->manifestDB = $data['MANIFEST'];
		}

		// fill meta data
		$keys = ['CREATED_BY_ID', 'MODIFIED_BY_ID', 'DATE_CREATE', 'DATE_MODIFY'];
		foreach ($keys as $key)
		{
			if (isset($data[$key]))
			{
				$this->metaData[$key] = $data[$key];
			}
		}

		if (preg_match(self::REPO_MASK, $this->code, $matches))
		{
			$this->repoId = $matches[1];
		}

		if (!$this->content && !$this->deleted)
		{
			$this->content = self::getContentFromRepository($this->code);
		}

		$this->error = new Error;
		$this->docRoot = Manager::getDocRoot();
	}

	/**
	 * Fill landing with blocks.
	 * @param \Bitrix\Landing\Landing $landing Landing instance.
	 * @param int $limit Limit count for blocks.
	 * @param array $params Additional params.
	 * @return boolean
	 */
	public static function fillLanding(\Bitrix\Landing\Landing $landing, $limit = 0, array $params = array())
	{
		if ($landing->exist())
		{
			$editMode = $landing->getEditMode() || $landing->getPreviewMode();
			$repo = array();
			$blocks = array();
			$rows = array();
			// get all blocks by filter
			$res = parent::getList(array(
				'select' => array(
					'*',
					'MANIFEST' => 'MANIFEST_DB.MANIFEST'
				),
				'filter' => array(
					'LID' => $landing->getId(),
					'=PUBLIC' => $editMode ? 'N' : 'Y',
					'=DELETED' => (isset($params['deleted']) && $params['deleted'] === true)
								? 'Y'
								: 'N'
				),
				'order' => array(
					'SORT' => 'ASC',
					'ID' => 'ASC'
				),
				'limit' => $limit
			));
			while ($row = $res->fetch())
			{
				$row['SITE_ID'] = $landing->getSiteId();
				$block = new self($row['ID'], $row);
				if (!$editMode && $block->getRepoId())
				{
					$repo[] = $block->getRepoId();
				}
				$blocks[$row['ID']] = $block;
			}
			if (!empty($repo))
			{
				$repo = Repo::getAppInfo($repo);
			}
			// add blocks to landing
			foreach ($blocks as $block)
			{
				$reposInfo = isset($repo[$block->getRepoId()])
							? $repo[$block->getRepoId()]
							: array();
				if ($editMode || !$reposInfo)
				{
					$landing->addBlockToCollection($block);
				}
				elseif (
					isset($reposInfo['PAYMENT_ALLOW']) &&
					$reposInfo['PAYMENT_ALLOW'] == 'Y'
				)
				{
					$landing->addBlockToCollection($block);
				}
			}
			return true;
		}

		return false;
	}

	/**
	 * Create copy of blocks for draft version.
	 * @param \Bitrix\Landing\Landing $landing Landing instance.
	 * @return void
	 */
	public static function cloneForEdit(\Bitrix\Landing\Landing $landing)
	{
		if ($landing->exist())
		{
			$clone = true;
			$forClone = array();

			$res = parent::getList(array(
				'select' => array(
					'ID', 'LID', 'CODE', 'SORT', 'ACTIVE',
					'CONTENT', 'PUBLIC', 'ACCESS'
				),
				'filter' => array(
					'LID' => $landing->getId()
				)
			));
			while ($row = $res->fetch())
			{
				if ($row['PUBLIC'] != 'Y')
				{
					$clone = false;
					break;
				}
				else
				{
					$row['PUBLIC'] = 'N';
					$row['PARENT_ID'] = $row['ID'];
					unset($row['ID']);
					$forClone[] = $row;
				}
			}

			if ($clone)
			{
				foreach ($forClone as $row)
				{
					parent::add($row);
				}
			}
		}
	}

	/**
	 * Publication blocks for landing.
	 * @param \Bitrix\Landing\Landing $landing Landing instance.
	 * @return void
	 */
	public static function publicationBlocks(\Bitrix\Landing\Landing $landing)
	{
		Mutator::blocksPublication($landing);
	}

	/**
	 * Recognize landing id by block id.
	 * @param int|array $id Block id (id array).
	 * @return int|array|false
	 */
	public static function getLandingIdByBlockId($id)
	{
		$data = array();
		$res = parent::getList(array(
			'select' => array(
				'ID', 'LID'
			),
			'filter' => array(
				'ID' => $id
			)
		));
		while ($row = $res->fetch())
		{
			$data[$row['ID']] = $row['LID'];
		}

		if (is_array($id))
		{
			return $data;
		}
		elseif (!empty($data))
		{
			return array_pop($data);
		}

		return false;
	}

	/**
	 * Gets row by block id.
	 * @param int|array $id Block id (id array).
	 * @param array $select Select row.
	 * @deprecated since 18.5.0
	 * @return int|array|false
	 */
	public static function getLandingRowByBlockId($id, array $select = array('ID'))
	{
		return self::getRowByBlockId($id, $select);
	}

	/**
	 * Gets landing row by block id.
	 * @param int|array $id Block id (id array).
	 * @param array $select Select row.
	 * @return int|array|false
	 */
	public static function getRowByBlockId($id, array $select = array('ID'))
	{
		$data = array();
		$res = parent::getList(array(
			'select' => $select,
			'filter' => array(
				'ID' => $id
			)
		));
		while ($row = $res->fetch())
		{
			$data[$row['ID']] = $row;
		}

		if (is_array($id))
		{
			return $data;
		}
		elseif (!empty($data))
		{
			return array_pop($data);
		}

		return false;
	}

	/**
	 * Get content from repository by code.
	 * @param string $code Block code.
	 * @param string $namespace Namespace (optional).
	 * @return string
	 */
	public static function getContentFromRepository($code, $namespace = null)
	{
		$content = '';

		// local repo
		if (preg_match(self::REPO_MASK, $code, $matches))
		{
			$repo = Repo::getById($matches[1])->fetch();
			$content = $repo['CONTENT'];
		}
		// files storage
		elseif ($path = self::getBlockPath($code, $namespace))
		{
			$path = Manager::getDocRoot() . $path . '/block.php';
			if (file_exists($path))
			{
				$content = file_get_contents($path);
			}
		}
		elseif ($manifest = Manifest::getByCode($code, true))
		{
			$content = $manifest['CONTENT'];
		}

		return $content;
	}

	/**
	 * Create instance by string code.
	 * @param \Bitrix\Landing\Landing $landing Landing - owner for new block.
	 * @param string $code Code of block from repository.
	 * @param array $data Additional data array.
	 * @return Block|false
	 */
	public static function createFromRepository(\Bitrix\Landing\Landing $landing, $code, $data = array())
	{
		// get content and mainfest
		$content = self::getContentFromRepository($code);
		$manifest = self::getManifestFile($code);
		// version control
		if (
			isset($manifest['block']['version']) &&
			version_compare(Manager::getVersion(), $manifest['block']['version']) < 0
		)
		{
			$landing->getError()->addError(
				'BLOCK_WRONG_VERSION',
				Loc::getMessage('LANDING_BLOCK_WRONG_VERSION')
			);
			return false;
		}
		// check errors
		if (!$landing->exist())
		{
			$landing->getError()->addError(
				'LANDING_NOT_EXIST',
				Loc::getMessage('LANDING_BLOCK_LANDING_NOT_EXIST')
			);
			return false;
		}
		if ($content == '')
		{
			$landing->getError()->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
			return false;
		}
		// add
		$fields = array(
			'LID' => $landing->getId(),
			'CODE' => $code,
			'CONTENT' => $content,
			'ACTIVE' => 'Y'
		);
		$availableReplace = array(
			'ACTIVE', 'PUBLIC', 'ACCESS',
			'SORT', 'CONTENT', 'ANCHOR'
		);
		foreach ($availableReplace as $replace)
		{
			if (isset($data[$replace]))
			{
				$fields[$replace] = $data[$replace];
			}
		}
		$res = parent::add($fields);
		if ($res->isSuccess())
		{
			$block = new self($res->getId());
			$manifest = $block->getManifest();
			if (
				isset($manifest['callbacks']['afteradd']) &&
				is_callable($manifest['callbacks']['afteradd'])
			)
			{
				$manifest['callbacks']['afteradd']($block);
			}
			// get all images from block to local storage
			if (self::ADD_FILES_TO_LOCAL_STORAGE)
			{
				foreach ($manifest['nodes'] as $selector => $node)
				{
					if (isset($node['type']) && $node['type'] == 'img')
					{
						$images = \Bitrix\Landing\Node\Img::getNode(
							$block, $selector
						);
						foreach ($images as &$img)
						{
							$file = Manager::savePicture($img['src']);
							if ($file)
							{
								File::addToBlock(
									$block->getId(),
									$file['ID']
								);
								$img['src'] = $file['SRC'];
								$img['id'] = $file['ID'];
							}
						}
						\Bitrix\Landing\Node\Img::saveNode(
							$block,
							$selector,
							$images
						);
					}
				}
				$block->saveContent(
					$block->getDom()->saveHTML()
				);
				$block->save();
			}
			return $block;
		}
		else
		{
			$landing->getError()->addFromResult($res);
			return false;
		}
	}

	/**
	 * New or not the block.
	 * @param string $block Block code.
	 * @return boolean
	 */
	protected static function isNewBlock($block)
	{
		static $newBlocks = null;

		if ($newBlocks === null)
		{
			$newBlocks = unserialize(Manager::getOption('new_blocks'));
			if (!is_array($newBlocks))
			{
				$newBlocks = array();
			}
			if (
				!isset($newBlocks['date']) ||
				(
					isset($newBlocks['date']) &&
					((time() - $newBlocks['date']) > self::NEW_BLOCK_LT)
				)
			)
			{
				$newBlocks = array();
			}
			if (isset($newBlocks['items']))
			{
				$newBlocks = $newBlocks['items'];
			}
		}

		return in_array($block, $newBlocks);
	}

	/**
	 * Clear cache repository.
	 * @return void
	 */
	public static function clearRepositoryCache()
	{
		if (defined('BX_COMP_MANAGED_CACHE'))
		{
			Manager::getCacheManager()->clearByTag(self::BLOCKS_TAG);
		}
	}

	/**
	 * Get blocks from repository.
	 * @param bool $withManifest Get repo with manifest files of blocks.
	 * @return array
	 */
	public static function getRepository($withManifest = false)
	{
		static $blocksCats = array();

		// function for prepare return
		$returnFunc = function($blocksCats) use($withManifest)
		{
			$event = new \Bitrix\Main\Event('landing', 'onBlockGetRepository', array(
				'blocks' => $blocksCats,
				'withManifest' => $withManifest
			));
			$event->send();
			foreach ($event->getResults() as $result)
			{
				if ($result->getResultType() != \Bitrix\Main\EventResult::ERROR)
				{
					if (($modified = $result->getModified()))
					{
						if (isset($modified['blocks']))
						{
							$blocksCats = $modified['blocks'];
						}
					}
				}
			}
			return $blocksCats;
		};

		// static cache
		if (!$withManifest && !empty($blocksCats))
		{
			return $returnFunc($blocksCats);
		}

		// local function for fill last used blocks
		$fillLastUsed = function($blocksCats)
		{
			$blocksCats['last']['items'] = array();
			$lastUsed = self::getLastUsed();
			if ($lastUsed)
			{
				foreach ($lastUsed as $code)
				{
					$blocksCats['last']['items'][$code] = array();
				}
				foreach ($blocksCats as &$cat)
				{
					foreach ($cat['items'] as $code => &$block)
					{
						if (in_array($code, $lastUsed))
						{
							$block['section'][] = 'last';
							$blocksCats['last']['items'][$code] = $block;
						}
					}
					unset($block);
				}
				unset($cat);
			}
			return $blocksCats;
		};

		// system cache begin
		$cache = new \CPHPCache();
		$cacheTime = 86400;
		$cacheStarted = false;
		$cacheId =  $withManifest ? 'blocks_manifest' : 'blocks';
		$cacheId .= LANGUAGE_ID;
		$cachePath = 'landing';
		if ($cache->initCache($cacheTime, $cacheId, $cachePath))
		{
			$blocksCats = $cache->getVars();
			if (is_array($blocksCats) && !empty($blocksCats))
			{
				$blocksCats = $fillLastUsed($blocksCats);
				return $returnFunc($blocksCats);
			}
		}
		if ($cache->startDataCache($cacheTime, $cacheId, $cachePath))
		{
			$cacheStarted = true;
			if (defined('BX_COMP_MANAGED_CACHE'))
			{
				Manager::getCacheManager()->startTagCache($cachePath);
				Manager::getCacheManager()->registerTag(self::BLOCKS_TAG);
			}
		}

		// not in cache - init
		$blocks = array();
		$sections = array();
		$path = Manager::getDocRoot() . self::BLOCKS_DIR;

		// read all subdirs ($namespaces) in block dir
		$namespaces = array();
		if (($handle = opendir($path)))
		{
			while ((($entry = readdir($handle)) !== false))
			{
				if (
					$entry != '.' && $entry != '..' &&
					is_dir($path . '/' . $entry)
				)
				{
					$namespaces[] = $entry;
				}
			}
		}

		//get all blocks with description-file
		sort($namespaces);
		foreach ($namespaces as $subdir)
		{
//			get from cloud only if it not repo
			$restSrc = Manager::getOption('block_vendor_' . $subdir);
			if (
				(!defined('LANDING_IS_REPO') || LANDING_IS_REPO !== true) &&
				$restSrc
			)
			{
				$http = new HttpClient;
				try
				{
					$json = Json::decode($http->get(
						$restSrc . 'landing_cloud.cloud.getrepository' .
						'?user_lang=' . LANGUAGE_ID .
						'&version=' . Manager::getVersion()
					));
				}
				catch (\Exception $e)
				{
					if ($cacheStarted)
					{
						$cache->abortDataCache();
					}
				}
				if (
					isset($json['result']) &&
					is_array($json['result'])
				)
				{
					$insertCodes = array();
					foreach ($json['result'] as $sectionCode => $sectionItem)
					{
						$sections[$sectionCode] = $sectionItem['name'];
						if (
							isset($sectionItem['items']) &&
							is_array($sectionItem['items'])
						)
						{
							foreach ($sectionItem['items'] as $code => $item)
							{
								if (isset($item['manifest']))
								{
									if (!isset($item['content']))
									{
										$item['content'] = '';
									}
									if (!isset($insertCodes[$code]))
									{
										$insertCodes[$code] = true;
										Manifest::add(array(
											'CODE' => $code,
											'MANIFEST' => $item['manifest'],
											'CONTENT' => $item['content']
										));
									}
									unset($item['content']);
									unset($item['manifest']);
								}
								$blocks[$code] = $item;
							}
						}
					}
				}
			}
			else if (($handle = opendir($path . '/' . $subdir)))
			{
				// sections
				$sectionsPath = $path . '/' . $subdir . '/.sections.php';
				if (file_exists($sectionsPath))
				{
					$sections = array_merge(
						$sections,
						(array) include $sectionsPath
					);
				}
				// blocks
				while ((($entry = readdir($handle)) !== false))
				{
					$descriptionPath = $path . '/' . $subdir . '/' . $entry . '/.description.php';
					$previewPathJpg = $path . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME;
					if ($entry != '.' && $entry != '..' && file_exists($descriptionPath))
					{
						Loc::loadLanguageFile($descriptionPath);
						$description = include $descriptionPath;
						if (isset($description['block']['name']))
						{
							$previewFileName = Manager::getUrlFromFile(
								self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME
							);
							$blocks[$entry] = array(
								'name' => $description['block']['name'],
								'namespace' => $subdir,
								'new' => self::isNewBlock($entry),
								'version' => isset($description['block']['version'])
												? $description['block']['version']
												: null,
								'type' => isset($description['block']['type'])
												? $description['block']['type']
												: array(),
								'section' => isset($description['block']['section'])
												? $description['block']['section']
												: 'other',
								'description' => isset($description['block']['description'])
												? $description['block']['description']
												: '',
								'preview' => file_exists($previewPathJpg)
												? $previewFileName
												: '',
								'restricted' => false,
								'repo_id' => false,
								'app_code' => false
							);
							if ($withManifest)
							{
								$blocks[$entry]['manifest'] = self::getManifestFile(
									$subdir . ':' . $entry
								);
								$blocks[$entry]['content'] = self::getContentFromRepository(
									$entry, $subdir
								);
								if (isset($blocks[$entry]['manifest']['block']))
								{
									$blocks[$entry]['manifest']['block']['preview'] = $blocks[$entry]['preview'];
								}
								// local assets to manifest's assets
								if (!isset($blocks[$entry]['manifest']['assets']))
								{
									$blocks[$entry]['manifest']['assets'] = array();
								}
								// if css exists
								if (file_exists($path . '/' . $subdir . '/' . $entry . '/style.min.css'))
								{
									if (!isset($blocks[$entry]['manifest']['assets']['css']))
									{
										$blocks[$entry]['manifest']['assets']['css'] = array();
									}
									$blocks[$entry]['manifest']['assets']['css'][] = Manager::getUrlFromFile(
										self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/style.min.css'
									);
								}
								// if js exists
								if (file_exists($path . '/' . $subdir . '/' . $entry . '/script.min.js' ))
								{
									if (!isset($blocks[$entry]['manifest']['assets']['js']))
									{
										$blocks[$entry]['manifest']['assets']['js'] = array();
									}
									$blocks[$entry]['manifest']['assets']['js'][] = Manager::getUrlFromFile(
										self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/script.min.js'
									);
								}
								if (empty($blocks[$entry]['manifest']['assets']))
								{
									unset($blocks[$entry]['manifest']['assets']);
								}
							}
						}
					}
				}
			}
		}

		// rest repo
		$blocksRepo = \Bitrix\Landing\Repo::getRepository();
		// get apps by blocks
		$apps = array();
		foreach ($blocksRepo as $block)
		{
			if ($block['app_code'])
			{
				$apps[] = $block['app_code'];
			}
		}
		if ($apps)
		{
			$apps = Repo::getAppByCode($apps);
			// mark repo blocks expired
			foreach ($blocksRepo as &$block)
			{
				if (
					$block['app_code'] &&
					isset($apps[$block['app_code']]) &&
					$apps[$block['app_code']]['PAYMENT_ALLOW'] == 'N'
				)
				{
					$block['app_expired'] = true;
				}
			}
			unset($block);
		}
		$blocks += $blocksRepo;

		// create new section in repo
		$createNewSection = function($title)
		{
			return array(
				'name' => $title,
				'new' => false,
				'separator' => false,
				'app_code' => false,
				'items' => array()
			);
		};

		// set by sections
		$md5s = array();
		foreach ($sections as $code => $title)
		{
			$title = trim($title);
			$blocksCats[$code] = $createNewSection($title);
			$md5s[md5(strtolower($title))] = $code;
		}
		foreach ($blocks as $key => $block)
		{
			if (!is_array($block['section']))
			{
				$block['section'] = array($block['section']);
			}
			foreach ($block['section'] as $section)
			{
				$section = trim($section);
				$sectionMd5 = md5(strtolower($section));
				// adding new sections (actual for repo blocks)
				if (
					!isset($blocksCats[$section]) &&
					!isset($blocksCats[$sectionMd5])
				)
				{
					if (isset($md5s[$sectionMd5]))
					{
						$section = $md5s[$sectionMd5];
					}
					else
					{
						$blocksCats[$sectionMd5] = $createNewSection($section);
						$section = $sectionMd5;
					}
				}
				else if (isset($blocksCats[$sectionMd5]))
				{
					$section = $sectionMd5;
				}
				$blocksCats[$section]['items'][$key] = $block;
				if ($block['new'])
				{
					$blocksCats[$section]['new'] = true;
				}
			}
		}

		// add apps sections
		if (!empty($blocksRepo))
		{
			$blocksCats['separator_apps'] = array(
				'name' => Loc::getMessage('LANDING_BLOCK_SEPARATOR_PARTNER'),
				'separator' => true,
				'items' => array()
			);
			foreach ($apps as $app)
			{
				$blocksCats[$app['CODE']] = array(
					'name' => $app['APP_NAME'],
					'new' => false,
					'separator' => false,
					'app_code' => $app['CODE'],
					'items' => array()
				);
			}
			// add blocks to the app sections
			foreach ($blocksRepo as $key => $block)
			{
				if ($block['app_code'])
				{
					$blocksCats[$block['app_code']]['items'][$key] = $block;
				}
			}
		}

		// system cache end
		if ($cacheStarted)
		{
			$cache->endDataCache($blocksCats);
			if (defined('BX_COMP_MANAGED_CACHE'))
			{
				Manager::getCacheManager()->endTagCache();
			}
		}

		$blocksCats = $fillLastUsed($blocksCats);

		return $returnFunc($blocksCats);
	}

	/**
	 * Get last used blocks by current user.
	 * @param int $count Count of blocks.
	 * @return array
	 */
	public static function getLastUsed($count = 10)
	{
		$blocks = array();

		$c = 0;
		$res = parent::getList(array(
			'select' => array(
				'CODE'
			),
			'filter' => array(
				'CREATED_BY_ID' => Manager::getUserId(),
				'=PUBLIC' => 'N'
			),
			'order' => array(
				'DATE_CREATE' => 'DESC'
			)
		));
		while ($row = $res->fetch())
		{
			$blocks[$row['CODE']] = $row['CODE'];
			if (++$c >= $count)
			{
				break;
			}
		}

		return array_values($blocks);
	}


	/**
     * Get blocks style manifest from repository.
     * @return array
	*/
	public static function getStyle()
	{
		$style = array();

		// read all subdirs ($namespaces) in block dir
		$path = Manager::getDocRoot() . self::BLOCKS_DIR;
		if (($handle = opendir($path)))
		{
			while ((($entry = readdir($handle)) !== false))
			{
				if (
					$entry != '.' && $entry != '..' &&
					is_dir($path . '/' . $entry) &&
					file_exists($path . '/' . $entry . '/.style.php')
				)
				{
					$style[$entry] = include $path . '/' . $entry . '/.style.php';
				}
			}
		}

		return $style;
	}

	/**
	 * Get block content array.
	 * @param int $id Block id.
	 * @param boolean $editMode Edit mode if true.
	 * @param array $params Some params.
	 * @return array
	 */
	public static function getBlockContent($id, $editMode = false, array $params = array())
	{
		if (!isset($params['wrapper_show']))
		{
			$params['wrapper_show'] = true;
		}

		ob_start();
		$block = new self($id);
		$extContent = '';
		if (($ext = $block->getExt()))
		{
			$extContent = \CUtil::initJSCore($ext, true);
			$extContent = preg_replace(
				'#<script type="text/javascript"(\sdata\-skip\-moving\="true")?>.*?</script>#is',
				'',
				$extContent
			);
		}
		$landing = Landing::createInstance($block->getLandingId());
		$block->view(
			false,
			$landing->exist() ? $landing : null,
			$params
		);
		$content = ob_get_contents();
		$content = self::replaceMetaMarkers($content);
		ob_end_clean();
		if ($block->exist())
		{
			$availableJS = !$editMode || !$block->getRepoId();
			$return = array(
				'id' => $id,
				'content' => $content,
				'content_ext' => $extContent,
				'css' => $block->getCSS(),
				'js' => $availableJS ? $block->getJS() : array(),
				'manifest' => $block->getManifest()
			);
			if (
				$editMode &&
				isset($return['manifest']['requiredUserAction'])
			)
			{
				$return['requiredUserAction'] = $return['manifest']['requiredUserAction'];
			}
			return $return;
		}
		else
		{
			return array();
		}
	}

	/**
	 * Get block anchor.
	 * @param int $id Block id.
	 * @return string
	 */
	public static function getAnchor($id)
	{
		return 'block' . $id;
	}

	/**
	 * Get namespace for block.
	 * @param string $code Code of block.
	 * @return string
	 */
	protected static function getBlockNamespace($code)
	{
		static $paths = array();
		static $namespace = array();

		$code = trim($code);

		if (isset($paths[$code]))
		{
			return $paths[$code];
		}

		$paths[$code] = '';
		$path = Manager::getDocRoot() . self::BLOCKS_DIR;

		// read all subdirs ($namespaces) in block dir
		if (empty($namespace))
		{
			if (($handle = opendir($path)))
			{
				while ((($entry = readdir($handle)) !== false))
				{
					if (
						is_dir($path . '/' . $entry) &&
						$entry != '.' && $entry != '..'
					)
					{
						$namespace[] = $entry;
					}
				}
			}
			sort($namespace);
		}

		// get first needed block from end
		foreach (array_reverse($namespace) as $subdir)
		{
			if (file_exists($path . '/' . $subdir . '/' . $code . '/.description.php'))
			{
				$paths[$code] = $subdir;
				break;
			}
		}

		return $paths[$code];
	}

	/**
	 * Get local path for block.
	 * @param string $code Code of block.
	 * @param string $namespace Namespace (optional).
	 * @return string
	 */
	protected static function getBlockPath($code, $namespace = null)
	{
		if (!$namespace)
		{
			$namespace = self::getBlockNamespace($code);
		}
		if ($namespace)
		{
			$disabled = explode(',', Manager::getOption('disabled_namespaces', ''));
			if (!in_array($namespace, $disabled))
			{
				return self::BLOCKS_DIR . '/' . $namespace . '/' . $code;
			}
		}

		return '';
	}

	/**
	 * Exist or not block in current instance.
	 * @return boolean
	 */
	public function exist()
	{
		return $this->id > 0;
	}

	/**
	 * Get id of the block.
	 * @return int
	 */
	public function getId()
	{
		return $this->id;
	}

	/**
	 * Gets landing id.
	 * @return int
	 */
	public function getLandingId()
	{
		return $this->lid;
	}

	/**
	 * Gets site id (of landing).
	 * @return int
	 */
	public function getSiteId()
	{
		return $this->siteId;
	}

	/**
	 * Get code of the block.
	 * @return string
	 */
	public function getCode()
	{
		return $this->code;
	}

	/**
	 * Get anchor of the block.
	 * @return string
	 */
	public function getLocalAnchor()
	{
		return $this->anchor;
	}

	/**
	 * Get content of the block.
	 * @return string
	 */
	public function getContent()
	{
		return $this->content;
	}

	/**
	 * Active or not the block.
	 * @return boolean
	 */
	public function isActive()
	{
		return $this->active;
	}

	/**
	 * Public or not the block.
	 * @return boolean
	 */
	public function isPublic()
	{
		return $this->public;
	}

	/**
	 * Get current access.
	 * @return string
	 */
	public function getAccess()
	{
		return $this->access;
	}

	/**
	 * Set active to the block.
	 * @param boolean $active Bool: true or false.
	 * @return boolean
	 */
	public function setActive($active)
	{
		if ($this->access < $this::ACCESS_X)
		{
			$this->error->addError(
				'ACCESS_DENIED',
				Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
			);
			return false;
		}
		$this->active = (boolean) $active;
		return true;
	}

	/**
	 * Get repo id, if block from repo.
	 * @return int
	 */
	public function getRepoId()
	{
		return $this->repoId;
	}

	/**
	 * Gets site row.
	 * @return array
	 */
	public function getSite()
	{
		static $site = null;

		if (
			$site === null &&
			$this->siteId
		)
		{
			$site = Site::getList(array(
				'filter' => array(
					'ID' => $this->siteId
				)
		 	))->fetch();
		}

		return $site;
	}

	/**
	 * Get preview picture of the block.
	 * @return string
	 */
	public function getPreview()
	{
		$path = self::getBlockPath($this->code);
		if ($path && file_exists($this->docRoot . '/' . $path . '/' . self::PREVIEW_FILE_NAME))
		{
			return $path . '/' . self::PREVIEW_FILE_NAME;
		}
		elseif (isset($this->manifestDB['block']['preview']))
		{
			return $this->manifestDB['block']['preview'];
		}
		return '';
	}

	/**
	 * Get error collection
	 * @return \Bitrix\Landing\Error
	 */
	public function getError()
	{
		return $this->error;
	}

	/**
	 * Get class handler for type of node.
	 * @param string $type Type.
	 * @return string
	 */
	protected function getTypeClass($type)
	{
		static $classes = array();

		$type = strtolower($type);

		if (isset($classes[$type]))
		{
			return $classes[$type];
		}

		$class = __NAMESPACE__ . '\\Node\\' . $type;

		// check custom classes
		$event = new \Bitrix\Main\Event('landing', 'onGetNodeClass', array(
			'type' => $type,
		));
		$event->send();
		foreach ($event->getResults() as $result)
		{
			if ($result->getResultType() != \Bitrix\Main\EventResult::ERROR)
			{
				if (
					($modified = $result->getModified()) &&
					isset($modified['class']) &&
					is_subclass_of($modified['class'], '\\Bitrix\\Landing\\Node')
				)
				{
					$class = $modified['class'];
				}
			}
		}

		$classes[$type] = $class;

		return $classes[$type];
	}

	/**
	 * Get manifest array from block.
	 * @param bool $extended Get extended manifest.
	 * @param bool $missCache Don't save in static cache.
	 * @param array $params Additional params.
	 * @return array
	 */
	public function getManifest($extended = false, $missCache = false, array $params = array())
	{
		static $manifestStore = array();

		if (
			!$missCache &&
			isset($manifestStore[$this->code])
		)
		{
			if (
				!isset($manifestStore[$this->code]['disableCache']) ||
				$manifestStore[$this->code]['disableCache'] !== true
			)
			{
				return $manifestStore[$this->code];
			}
		}

		// manifest from market, files, or rest
		if ($this->repoId)
		{
			$manifest = Repo::getBlock($this->repoId);
		}
		else if ($path = self::getBlockPath($this->code))
		{
			//isolate variables from .description.php
			$includeDesc = function($path)
			{
				Loc::loadLanguageFile($path . '/.description.php');
				$manifest = include $path . '/.description.php';
				return $manifest;
			};

			$manifest = $includeDesc($this->docRoot . $path);
		}
		else
		{
			if ($this->manifestDB === null)
			{
				$this->manifestDB = Manifest::getByCode($this->code);
			}
			$manifest = $this->manifestDB;
		}

		// prepare manifest
		if (isset($manifest['block']['name']))
		{
			// prepare by subtype
			if (
				isset($manifest['block']['subtype']) &&
				(
					!isset($params['miss_subtype']) ||
					$params['miss_subtype'] !== true
				)
			)
			{
				$subtypes = $manifest['block']['subtype'];
				if (!is_array($subtypes))
				{
					$subtypes = [$subtypes];
				}
				
				foreach ($subtypes as $subtype)
				{
					$subtypeClass = '\\Bitrix\\Landing\\Subtype\\';
					$subtypeClass .= $subtype;
					if (class_exists($subtypeClass))
					{
						$manifest = $subtypeClass::prepareManifest(
							$manifest,
							$this,
							isset($manifest['block']['subtype_params'])
								? (array)$manifest['block']['subtype_params']
								: array()
						);
					}
				}
			}
			// set empty array if no exists
			foreach (array('cards', 'nodes', 'attrs') as $code)
			{
				if (!isset($manifest[$code]) || !is_array($manifest[$code]))
				{
					$manifest[$code] = array();
				}
			}
			// prepare every node
			foreach ($manifest['nodes'] as $keyNode => &$node)
			{
				if (is_callable($node) && !$this->repoId)
				{
					$node = $node();
				}
				$node['code'] = $keyNode;
				$class = $this->getTypeClass($node['type']);
				if (isset($node['type']) && class_exists($class))
				{
					$node['handler'] = call_user_func(array(
						$class,
						'getHandlerJS'
					));
					if (method_exists($class, 'prepareManifest'))
					{
						$node = call_user_func_array(array(
							$class,
							'prepareManifest'
						), array(
							$this,
							$node,
							&$manifest
						));
						if (!is_array($node))
						{
							unset($manifest['nodes'][$keyNode]);
						}
					}
				}
				else
				{
					unset($manifest['nodes'][$keyNode]);
				}
			}
			unset($node);
			// and attrs
			foreach ($manifest['attrs'] as $keyNode => &$node)
			{
				if (is_callable($node) && !$this->repoId)
				{
					$node = $node();
				}
			}
			unset($node);
			// callbacks
			if (isset($manifest['callbacks']) && is_array($manifest['callbacks']))
			{
				$callbacks = array();
				foreach ($manifest['callbacks'] as $code => $callback)
				{
					$callbacks[strtolower($code)] = $callback;
				}
				$manifest['callbacks'] = $callbacks;
			}
			// prepare styles
			if (!isset($manifest['namespace']))
			{
				$manifest['namespace'] = $this->getBlockNamespace($this->code);
			}
			if (
				isset($manifest['style']) &&
				!(
					isset($manifest['style']['block']) &&
					isset($manifest['style']['nodes']) &&
					count($manifest['style']) == 2
				)
			)
			{
				$manifest['style'] = array(
					'block' => array(),
					'nodes' => is_array($manifest['style'])
								? $manifest['style']
								: array()
				);
			}
			elseif (
				!isset($manifest['style']) ||
				!is_array($manifest['style'])
			)
			{
				$manifest['style'] = array(
					'block' => array(),
					'nodes' => array()
				);
			}
			// other
			$manifest['code'] = $this->code;
		}
		else
		{
			$manifest = array();
		}

		if (!$missCache)
		{
			$manifestStore[$this->code] = $manifest;
		}

		return $manifest;
	}

	/**
	 * Get manifest array as is from block.
	 * @param string $code Code name, format "namespace:code" or just "code".
	 * @return array
	 */
	public static function getManifestFile($code)
	{
		static $manifests = array();

		if (isset($manifests[$code]))
		{
			return $manifests[$code];
		}

		$manifests[$code] = array();
		$namespace = null;

		if (strpos($code, ':') !== false)
		{
			list($namespace, $code) = explode(':', $code);
		}

		if ($path = self::getBlockPath($code ,$namespace))
		{
			$docRoot = Manager::getDocRoot();
			Loc::loadLanguageFile($docRoot . $path . '/.description.php');
			$manifests[$code] = include $docRoot . $path . '/.description.php';
		}
		else
		{
			$manifests[$code] = Manifest::getByCode($code);
		}

		return $manifests[$code];
	}

	/**
	 * Get CSS/JS array of block.
	 * @param string $type What return: css or js.
	 * @return array
	 */
	public function getAsset($type = null)
	{
		static $asset = array();

		if (!isset($asset[$this->code]))
		{
			$asset[$this->code] = array(
				'css' => array(),
				'js' => array(),
				'ext' => array()
			);

			// additional asset first
			if ($this->repoId)
			{
				$manifest = Repo::getBlock($this->repoId);
			}
			else if ($path = self::getBlockPath($this->code))
			{
				$manifest = include $this->docRoot . $path . '/.description.php';
			}
			else
			{
				if ($this->manifestDB === null)
				{
					$this->manifestDB = Manifest::getByCode($this->code);
				}
				$manifest = $this->manifestDB;
			}

			foreach (array_keys($asset[$this->code]) as $ass)
			{
				if (
					isset($manifest['assets'][$ass]) &&
					!empty($manifest['assets'][$ass])
				)
				{
					foreach ($manifest['assets'][$ass] as $file)
					{
						if (!is_string($file))
						{
							continue;
						}
						if ($ass != 'ext')
						{
							$asset[$this->code][$ass][] = trim($file);
						}
						// for rest block allowed only this
						else if (
							!$this->repoId ||
							in_array($file, array('landing_form'))
						)
						{
							$asset[$this->code][$ass][] = trim($file);
						}
					}
					$asset[$this->code][$ass] = array_unique($asset[$this->code][$ass]);
				}
			}

			// next is phis files
			if (isset($path) && $path)
			{
				// base files next
				$file = $path . '/' . self::CSS_FILE_NAME;
				if (file_exists($this->docRoot . $file))
				{
					$asset[$this->code]['css'][] = $file;
				}
				$file = $path . '/' . self::JS_FILE_NAME;
				if (file_exists($this->docRoot . $file))
				{
					$asset[$this->code]['js'][] = $file;
				}
			}
		}

		return isset($asset[$this->code][$type])
				? $asset[$this->code][$type]
				: $asset[$this->code];
	}

	/**
	 * Get css file path, if exists.
	 * @return array
	 */
	public function getCSS()
	{
		return $this->getAsset('css');
	}

	/**
	 * Get js file path, if exists.
	 * @return array
	 */
	public function getJS()
	{
		return $this->getAsset('js');
	}

	/**
	 * Get extensions.
	 * @return array
	 */
	public function getExt()
	{
		return $this->getAsset('ext');
	}

	/**
	 * Out the block.
	 * @param boolean $edit Out block in edit mode.
	 * @param Landing|null $landing Landing of this block.
	 * @param array $params Some params.
	 * @return void
	 */
	public function view($edit = false, \Bitrix\Landing\Landing $landing = null, array $params = array())
	{
		global $APPLICATION;

		static $jsPlaced = array();

		if (!isset($params['wrapper_show']))
		{
			$params['wrapper_show'] = true;
		}

		if ($this->deleted)
		{
			return;
		}

		if ($edit || $this->active)
		{
			foreach ($this->getCSS() as $css)
			{
				Asset::getInstance()->addCSS($css);
			}
			if (($ext = $this->getExt()))
			{
				\CUtil::initJSCore($ext);
			}
			if (!$edit || !$this->repoId)
			{
				foreach ($this->getJS() as $js)
				{
					if ($this->repoId)
					{
						if (!in_array($js, $jsPlaced))
						{
							$jsPlaced[] = $js;
							Manager::setPageClass(
								'FooterJS',
								'<script type="text/javascript" src="' . \htmlspecialcharsbx($js) . '"></script>'
							);
						}
					}
					else
					{
						Asset::getInstance()->addJS($js);
					}
				}
			}
		}

		if ($params['wrapper_show'])
		{
			if ($edit)
			{
				$anchor = $this->getAnchor($this->id);
			}
			else
			{
				$anchor = $this->anchor
							? \htmlspecialcharsbx($this->anchor)
							: $this->getAnchor($this->id);
			}
			$classFromCode = 'block-' . $this->code;
			$classFromCode = preg_replace('/([^a-z0-9-])/i', '-', $classFromCode);
			$classFromCode = ' ' . $classFromCode;
			$content = '<div id="' . $anchor . '" class="block-wrapper' .
					   		(!$this->active ? ' landing-block-deactive' : '') .
					   		$classFromCode .
					   		'">' .
								$this->content .
						'</div>';
		}
		else
		{
			$content = $this->content;
		}
		// @tmp bug with setInnerHTML save result
		$content = preg_replace('/&amp;([^\s]{1})/is', '&$1', $content);

		if ($edit)
		{
			$manifest = $this->getManifest();
			if (!$manifest)
			{
				$manifest = array(
					'code' => $this->code
				);
			}
			if ($manifest)
			{
				echo '<script type="text/javascript">'
						. 'BX.ready(function(){'
							. 'if (typeof BX.Landing.Block !== "undefined")'
							. '{'
								. 'new BX.Landing.Block('
									. 'BX("block' . $this->id  . '"), '
									. '{'
										. 'id: ' . $this->id  . ', '
										. 'active: ' . ($this->active ? 'true' : 'false')  . ', '
										. 'anchor: ' . '"' . \CUtil::jsEscape($this->anchor) . '"' . ', '
										. 'access: ' . '"' . $this->access . '"' . ', '
										. 'manifest: ' . Json::encode($manifest)
					 					. (
					 						isset($manifest['requiredUserAction'])
											? ', requiredUserAction: ' . Json::encode($manifest['requiredUserAction'])
											: ''
										)
									. '}'
								. ');'
							. '}'
						. '});'
					. '</script>';
			}
			$content = $this::replaceMetaMarkers($content);
			if ($this->repoId)
			{
				echo $content;
			}
			else
			{
				eval('?>' . $content . '<?');
			}
		}
		elseif ($this->active)
		{
			// @todo make better
			static $sysPages = null;
			if ($sysPages === null)
			{
				$sysPages = array();
				foreach (Syspage::get($this->siteId) as $syspage)
				{
					$sysPages['@#system_' . $syspage['TYPE'] . '@'] = $syspage['LANDING_ID'];
				}
				if (!empty($sysPages))
				{
					$urls = Landing::getPublicUrl($sysPages);
					foreach ($sysPages as $code => $lid)
					{
						if (isset($urls[$lid]))
						{
							$sysPages[$code] = $urls[$lid];
						}
						else
						{
							unset($sysPages[$code]);
						}
					}
				}
			}
			if (!empty($sysPages))
			{
				$content = preg_replace(
					array_keys($sysPages),
					array_values($sysPages),
					$content
				);
			}
			if ($this->repoId)
			{
				echo $content;
			}
			else
			{
				eval('?>' . $content . '<?');
			}

		}
	}

	/**
	 * Set new content.
	 * @param string $content New content.
	 * @return void
	 */
	public function saveContent($content)
	{
		$this->content = trim($content);
		$this->getDom(true);
	}

	/**
	 * Save current block in DB.
	 * @return boolean
	 */
	public function save()
	{
		$data = array(
			'SORT' => $this->sort,
			'ACTIVE' => $this->active ? 'Y' : 'N',
			'ANCHOR' => $this->anchor,
			'DELETED' => $this->deleted ? 'Y' : 'N'
		);
		if ($this->content)
		{
			// if this is php-block, limited by size
			$manifest = $this->getManifest();
			if (strlen($this->content) > 16*1024)
			{
				if (
					isset($manifest['block']['html']) &&
					$manifest['block']['html'] === false
				)
				{
					$this->error->addError(
						'BLOCK_TEXT_FULL',
						Loc::getMessage('LANDING_BLOCK_TEXT_FULL')
					);
					return false;
				}
			}
			$data['CONTENT'] = $this->content;
		}
		$res = parent::update($this->id, $data);
		$this->error->addFromResult($res);
		return $res->isSuccess();
	}

	/**
	 * Change landing of current block.
	 * @param int $lid New landing id.
	 * @return boolean
	 *
	 */
	public function changeLanding($lid)
	{
		$res = parent::update($this->id, array(
			'LID' => $lid,
			'PARENT_ID' => 0,
			'PUBLIC' => 'N'
		));
		$this->error->addFromResult($res);
		return $res->isSuccess();
	}

	/**
	 * Delete current block.
	 * @return boolean
	 */
	public function unlink()
	{
		if ($this->access < $this::ACCESS_X)
		{
			$this->error->addError(
				'ACCESS_DENIED',
				Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
			);
			return false;
		}

		$manifest = $this->getManifest();
		$res = self::parentDelete($this->id);
		if (!$res->isSuccess())
		{
			$this->error->addFromResult($res);
		}
		return $res->isSuccess();
	}

	/**
	 * Mark delete or not current block.
	 * @param boolean $mark Mark.
	 * @return void
	 */
	public function markDeleted($mark)
	{
		if ($this->access >= $this::ACCESS_X)
		{
			$this->deleted = (boolean) $mark;
		}
	}

	/**
	 * Set new sort to current block.
	 * @param int $sort New sort.
	 * @return void
	 */
	public function setSort($sort)
	{
		$this->sort = $sort;
	}

	/**
	 * Set new anchor to current block.
	 * @param string $anchor New anchor.
	 * @return boolean
	 */
	public function setAnchor($anchor)
	{
		$anchor = trim($anchor);
		$check = !$anchor || preg_match_all('/^[a-z]{1}[a-z0-9\-\_\.\:]+$/i', $anchor);
		if (!$check)
		{
			$this->error->addError(
				'BAD_ANCHOR',
				Loc::getMessage('LANDING_BLOCK_BAD_ANCHOR')
			);
			return false;
		}
		$this->anchor = $anchor;
		return true;
	}

	/**
	 * Save new sort to current block to DB.
	 * @param int $sort New sort.
	 * @return void
	 */
	public function saveSort($sort)
	{
		$this->sort = $sort;
		Internals\BlockTable::update($this->id, array(
			'SORT' => $sort
		));
	}

	/**
	 * Get sort of current block.
	 * @return int
	 */
	public function getSort()
	{
		return $this->sort;
	}

	/**
	 * Load current content in DOM html structure.
	 * @param bool $clear CLear static cache.
	 * @return DOM\Document
	 */
	public function getDom($clear = false)
	{
		static $doc = array();

		if (
			$clear &&
			isset($doc[$this->id])
		)
		{
			unset($doc[$this->id]);
		}

		if (!isset($doc[$this->id]))
		{
			$doc[$this->id] = new DOM\Document;
			$doc[$this->id]->loadHTML($this->content);
		}

		return $doc[$this->id];
	}

	/**
	 * Get metadata of current block.
	 * @return array
	 */
	public function getMeta()
	{
		return $this->metaData;
	}

	/**
	 * Adjust cards count by selector.
	 * @param string $selector Selector.
	 * @param int $count Needed cards count.
	 * @param bool &$changed Changed.
	 * @return boolean Success or failure.
	 */
	public function adjustCards($selector, $count, &$changed = false)
	{
		$manifest = $this->getManifest();
		if (isset($manifest['cards'][$selector]))
		{
			$count = (int)$count;
			$doc = $this->getDom();
			$resultList = $doc->querySelectorAll($selector);
			$resultCount = count($resultList);
			if ($count > $resultCount)
			{
				for ($i = $resultCount; $i < $count; $i++)
				{
					$changed = true;
					$this->cloneCard($selector, $i - 1);
				}
			}
			elseif ($count < $resultCount)
			{
				for ($i = $resultCount; $i > $count; $i--)
				{
					$changed = true;
					$this->removeCard($selector, $i - 1);
				}
			}
			return true;
		}

		$this->error->addError(
			'CARD_NOT_FOUND',
			Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND')
		);

		return false;
	}

	/**
	 * Clone one card in block by selector.
	 * @param string $selector Selector.
	 * @param int $position Card position.
	 * @param string $content New content for cloned card.
	 * @return boolean Success or failure.
	 */
	public function cloneCard($selector, $position, $content = '')
	{
		$manifest = $this->getManifest();
		if (isset($manifest['cards'][$selector]))
		{
			$position = max($position, -1);
			$realPosition = max($position, 0);
			$doc = $this->getDom();
			$resultList = $doc->querySelectorAll($selector);
			if (isset($resultList[$realPosition]))
			{
				$parentNode = $resultList[$realPosition]->getParentNode();
				$refChild = isset($resultList[$position + 1])
					? $resultList[$position + 1]
					: null;
				$haveChild = false;
				if ($refChild)
				{
					foreach ($parentNode->getChildNodes() as $child)
					{
						if ($child === $refChild)
						{
							$haveChild = true;
							break;
						}
					}
				}
				if ($parentNode && (!$refChild || $haveChild))
				{
					// some dance for set new content ;)
					if ($content)
					{
						$tmpCardName = strtolower('tmpcard' . randString(10));
						$newChild = new DOM\Element($tmpCardName);
						$newChild->setOwnerDocument($doc);
						$newChild->setInnerHTML($content);
					}
					else
					{
						$newChild = $resultList[$realPosition];
					}
					$parentNode->insertBefore(
						$newChild,
						$refChild,
						false
					);
					// cleaning and save
					if (isset($tmpCardName))
					{
						$this->saveContent(
							str_replace(
								array('<' . $tmpCardName . '>', '</' . $tmpCardName . '>'),
								'',
								$doc->saveHTML()
							)
						);
					}
					else
					{
						$this->saveContent($doc->saveHTML());
					}
				}
				return true;
			}

		}

		$this->error->addError(
			'CARD_NOT_FOUND',
			Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND')
		);
		return false;
	}

	/**
	 * Set card content from block by selector.
	 * @param string $selector Selector.
	 * @param int $position Card position.
	 * @param string $content New content.
	 * @return boolean Success or failure.
	 */
	public function setCardContent($selector, $position, $content)
	{
		$doc = $this->getDom();
		$resultList = $doc->querySelectorAll($selector);
		if (isset($resultList[$position]))
		{
			$resultList[$position]->setInnerHTML(
				$content
			);
			$this->saveContent($doc->saveHTML());
			return true;
		}

		$this->error->addError(
			'CARD_NOT_FOUND',
			Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND')
		);
		return false;
	}

	/**
	 * Gets card content from block by selector.
	 * @param string $selector Selector.
	 * @param int $position Card position.
	 * @return string
	 */
	public function getCardContent($selector, $position)
	{
		$doc = $this->getDom();
		$resultList = $doc->querySelectorAll($selector);
		if (isset($resultList[$position]))
		{
			return $resultList[$position]->getOuterHtml();
		}

		return null;
	}

	/**
	 * Gets count of cards from block by selector.
	 * @param string $selector Selector.
	 * @return int
	 */
	public function getCardCount($selector)
	{
		$doc = $this->getDom();
		$resultList = $doc->querySelectorAll($selector);
		return count($resultList);
	}

	/**
	 * Remove one card from block by selector.
	 * @param string $selector Selector.
	 * @param int $position Card position.
	 * @return boolean Success or failure.
	 */
	public function removeCard($selector, $position)
	{
		$manifest = $this->getManifest();
		if (isset($manifest['cards'][$selector]))
		{
			$doc = $this->getDom();
			$resultList = $doc->querySelectorAll($selector);
			if (isset($resultList[$position]))
			{
				$resultList[$position]->getParentNode()->removeChild(
					$resultList[$position]
				);
				$this->saveContent($doc->saveHTML());
				return true;
			}

		}

		$this->error->addError(
			'CARD_NOT_FOUND',
			Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND')
		);
		return false;
	}

	/**
	 * Set new names for nodes of block.
	 * @param array $data Nodes data array.
	 * @return boolean
	 */
	public function changeNodeName($data)
	{
		if ($this->access < $this::ACCESS_W)
		{
			$this->error->addError(
				'ACCESS_DENIED',
				Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
			);
			return false;
		}
		$doc = $this->getDom();
		$manifest = $this->getManifest();
		// find available nodes by manifest from data
		foreach ($manifest['nodes'] as $selector => $node)
		{
			if (isset($data[$selector]))
			{
				$resultList = $doc->querySelectorAll($selector);

				foreach ($data[$selector] as $pos => $value)
				{
					$value = trim($value);
					if (
						preg_match('/^[a-z0-9]+$/i', $value) &&
						isset($resultList[$pos]))
					{
						$resultList[$pos]->setNodeName($value);
					}
				}
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
		return true;
	}

	/**
	 * Set new content to nodes of block.
	 * @param array $data Nodes data array.
	 * @param array $additional Additional prams for save.
	 * @return boolean
	 */
	public function updateNodes($data, $additional = array())
	{
		if ($this->access < $this::ACCESS_W)
		{
			$this->error->addError(
				'ACCESS_DENIED',
				Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
			);
			return false;
		}

		$doc = $this->getDom();
		$manifest = $this->getManifest();
		// find available nodes by manifest from data
		foreach ($manifest['nodes'] as $selector => $node)
		{
			if (isset($data[$selector]))
			{
				if (!is_array($data[$selector]))
				{
					$data[$selector] = array(
						$data[$selector]
					);
				}
				// and save content from frontend in DOM by handler-class
				call_user_func_array(array(
					$this->getTypeClass($node['type']),
					'saveNode'
				), array(
					&$this,
					$selector,
					$data[$selector],
					$additional
				));
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
		return true;
	}

	/**
	 * Change cards multiple.
	 * @param array $data Array with cards.
	 * @return boolean
	 */
	public function updateCards(array $data = array())
	{
		$manifest = $this->getManifest();

		foreach ($data as $selector => $item)
		{
			$cardManifest = $manifest['cards'][$selector];
			// first gets content of current cards
			$cardContent = array();
			$cardCount = $this->getCardCount($selector);
			for ($i = 0; $i < $cardCount; $i++)
			{
				$cardContent[$i] = $this->getCardContent(
					$selector,
					$i
				);
			}
			// then fill all cards by content from existing cards and presets
			if (
				isset($item['source']) &&
				is_array($item['source'])
			)
			{
				$newContent = array();
				foreach ($item['source'] as $i => $source)
				{
					$type = isset($source['type'])
						? $source['type']
						: self::CARD_SYM_CODE;
					$value = isset($source['value'])
						? $source['value']
						: 0;
					// clone card
					if (
						$type == self::CARD_SYM_CODE &&
						isset($cardContent[$value])
					)
					{
						$newContent[$i] = $cardContent[$value];
					}
					// clone preset
					else if (
						$type == 'preset' &&
						isset($cardManifest['presets'][$value]['html'])
					)
					{
						$newContent[$i] = $cardManifest['presets'][$value]['html'];
					}
					else
					{
						$newContent[$i] = '';
					}
				}
				$newContent = trim(implode('', $newContent));
				if ($newContent)
				{
					$dom = $this->getDom();
					$resultList = $dom->querySelectorAll($selector);
					if (isset($resultList[0]))
					{
						$resultList[0]->getParentNode()->setInnerHtml(
							$newContent
						);
					}
					$this->saveContent(
						$dom->saveHTML()
					);
				}
			}
			// and finally update content cards
			if (
				isset($item['values']) &&
				is_array($item['values'])
			)
			{
				$updNodes = array();
				foreach ($item['values'] as $upd)
				{
					if (is_array($upd))
					{
						foreach ($upd as $sel => $content)
						{
							if (strpos($sel, '@'))
							{
								list($sel, $pos) = explode('@', $sel);
							}
							if (!isset($updNodes[$sel]))
							{
								$updNodes[$sel] = array();
							}
							$updNodes[$sel][$pos] = $content;
						}
					}
				}
				if (!empty($updNodes))
				{
					$this->updateNodes($updNodes);
				}
			}
		}

		return true;
	}

	/**
	 * Recursive styles remove in Node.
	 * @param \Bitrix\Main\Web\DOM\Node $node Node for clear.
	 * @param array $styleToRemove Array of styles to remove.
	 * @return \Bitrix\Main\Web\DOM\Node
	 */
	protected function removeStyle(\Bitrix\Main\Web\DOM\Node $node, array $styleToRemove)
	{
		foreach ($node->getChildNodesArray() as $nodeChild)
		{
			if ($nodeChild instanceof \Bitrix\Main\Web\DOM\Element)
			{
				$styles = DOM\StyleInliner::getStyle($nodeChild);
				if (!empty($styles))
				{
					foreach ($styleToRemove as $remove)
					{
						if (isset($styles[$remove]))
						{
							unset($styles[$remove]);
						}
					}
					DOM\StyleInliner::setStyle($nodeChild, $styles);
				}
			}
			$node = $this->removeStyle($nodeChild, $styleToRemove);
		}

		return $node;
	}

	/**
	 * Set new classes to nodes of block.
	 * @param array $data Classes data array.
	 * @return boolean
	 */
	public function setClasses($data)
	{
		if ($this->access < $this::ACCESS_V)
		{
			$this->error->addError(
				'ACCESS_DENIED',
				Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
			);
			return false;
		}

		$doc = $this->getDom();
		$manifest = $this->getManifest();

		// detects position
		$positions = array();
		foreach ((array)$data as $selector => $item)
		{
			if (strpos($selector, '@') !== false)
			{
				list($selector, $position) = explode('@', $selector);
			}
			else
			{
				$position = -1;
			}
			if ($position >= 0)
			{
				if (!isset($positions[$selector]))
				{
					$positions[$selector] = array();
				}
				$positions[$selector][] = $position;
			}
			$data[$selector] = $item;
		}

		// wrapper (not realy exist)
		$wrapper = '#' . $this->getAnchor($this->id);

		// find available nodes by manifest from data
		$styles = array_merge(
			$manifest['style']['block'],
			$manifest['style']['nodes']
		);
		$styles[$wrapper] = array(
			//
		);
		foreach ($styles as $selector => $node)
		{
			if (isset($data[$selector]))
			{
				// prepare data
				if (!is_array($data[$selector]))
				{
					$data[$selector] = array(
						$data[$selector]
					);
				}
				if (!isset($data[$selector]['classList']))
				{
					$data[$selector] = array(
						'classList' => $data[$selector]
					);
				}
				if (!isset($data[$selector]['affect']))
				{
					$data[$selector]['affect'] = array();
				}
				// apply classes to the block
				if ($selector == $wrapper)
				{
					$resultList = array(
						array_pop($doc->getChildNodesArray())
					);
				}
				// or by selector
				else
				{
					$resultList = $doc->querySelectorAll($selector);
				}
				foreach ($resultList as $pos => $resultNode)
				{
					if (
						isset($positions[$selector]) &&
						!in_array($pos, $positions[$selector])
					)
					{
						continue;
					}
					if ($resultNode)
					{
						if ($resultNode->getNodeType() == $resultNode::ELEMENT_NODE)
						{
							$resultNode->setClassName(
								implode(' ', $data[$selector]['classList'])
							);
						}
						// affected styles
						if (!empty($data[$selector]['affect']))
						{
							$this->removeStyle(
								$resultNode,
								$data[$selector]['affect']
							);
						}
					}
				}
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
		return true;
	}

	/**
	 * Set attributes to nodes of block.
	 * @param array $data Attrs data array.
	 * @return void
	 */
	public function setAttributes($data)
	{
		$doc = $this->getDom();
		$manifest = $this->getManifest();

		// wrapper (not realy exist)
		$wrapper = '#' . $this->getAnchor($this->id);

		// find available nodes by manifest from data
		$attrs = $manifest['attrs'];
		$attrs[$wrapper] = array(
			//
		);

		// find attrs in style key
		if (isset($manifest['style']['nodes']))
		{
			foreach ($manifest['style']['nodes'] as $selector => $item)
			{
				if (
					isset($item['additional']['attrs']) &&
					is_array($item['additional']['attrs'])
				)
				{
					foreach ($item['additional']['attrs'] as $attr)
					{
						if (!isset($attrs[$selector]))
						{
							$attrs[$selector] = array();
						}
						$attrs[$selector][] = $attr;
					}
				}
			}
		}
		// and in block styles
		if (
			isset($manifest['style']['block']['additional']['attrs']) &&
			is_array($manifest['style']['block']['additional']['attrs'])
		)
		{
			foreach ($manifest['style']['block']['additional']['attrs'] as $attr)
			{
				if (!isset($attrs[$wrapper]))
				{
					$attrs[$wrapper] = array();
				}
				$attrs[$wrapper][] = $attr;
			}
		}

		// and in cards key
		if (isset($manifest['cards']))
		{
			foreach ($manifest['cards'] as $selector => $item)
			{
				if (
					isset($item['additional']['attrs']) &&
					is_array($item['additional']['attrs'])
				)
				{
					foreach ($item['additional']['attrs'] as $attr)
					{
						if (!isset($attrs[$selector]))
						{
							$attrs[$selector] = array();
						}
						$attrs[$selector][] = $attr;
					}
				}
			}
		}

		foreach ($attrs as $selector => $item)
		{
			if (isset($data[$selector]))
			{
				// not multi
				if (!isset($item[0]))
				{
					$item = array($item);
				}
				// prepare attrs (and group attrs)
				$attrItems = array();
				foreach ($item as $key => $val)
				{
					if (
						isset($val['attrs']) &&
						is_array($val['attrs'])
					)
					{
						foreach ($val['attrs'] as $groupAttr)
						{
							$item[] = $groupAttr;
						}
						unset($item[$key]);
					}
				}
				foreach ($item as $val)
				{
					if (!isset($val['attribute']))
					{
						continue;
					}
					if (!isset($attrItems[$val['attribute']]))
					{
						$attrItems[$val['attribute']] = array();
					}
					if (isset($data[$selector][$val['attribute']]))
					{
						$attrItems[$val['attribute']][-1] = $data[$selector][$val['attribute']];
					}
					// cards
					else if (is_array($data[$selector]))
					{
						foreach ($data[$selector] as $pos => $card)
						{
							if (isset($card[$val['attribute']]))
							{
								$attrItems[$val['attribute']][$pos] = $card[$val['attribute']];
							}
						}
					}
				}
				// set attrs to the block
				if ($selector == $wrapper)
				{
					$resultList = array(
						array_pop($doc->getChildNodesArray())
					);
				}
				// or by selector
				else
				{
					$resultList = $doc->querySelectorAll($selector);
				}
				foreach ($resultList as $pos => $resultNode)
				{
					foreach ($attrItems as $code => $val)
					{
						if (isset($val[-1]))
						{
							$val = $val[-1];
						}
						else if (isset($val[$pos]))
						{
							$val = $val[$pos];
						}
						else
						{
							continue;
						}
						$resultNode->setAttribute(
							\htmlspecialcharsbx($code),
							is_array($val)
							? json_encode($val)
							: $val
						);
					}
				}
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
	}

	/**
	 * Replace title and breadcrumb marker in the block.
	 * @param string $content Some content.
	 * @return string
	 */
	protected static function replaceMetaMarkers($content)
	{
		if (strpos($content, '#breadcrumb#') !== false)
		{
			ob_start();
			$arResult = array(
				array(
					'LINK' => '#',
					'TITLE' => ''
				),
				array(
					'LINK' => '#',
					'TITLE' => Loc::getMessage('LANDING_BLOCK_BR1')
				),
				array(
					'LINK' => '#',
					'TITLE' => Loc::getMessage('LANDING_BLOCK_BR2')
				),
				array(
					'LINK' => '#',
					'TITLE' => ''
				)
			);
			$tplId = Manager::getOption('site_template_id');//@todo fixme
			$strChainTemplate = getLocalPath('templates/' . $tplId . '/chain_template.php');
			$strChainTemplate = Manager::getDocRoot() . $strChainTemplate;
			if (file_exists($strChainTemplate))
			{
				echo include $strChainTemplate;
			}
			$breadcrumb = ob_get_contents();
			ob_end_clean();
			$content = str_replace(
				'#breadcrumb#',
				$breadcrumb,
				$content
			);
		}

		if (strpos($content, '#title#') !== false)
		{
			$content = str_replace(
				'#title#',
				Loc::getMessage('LANDING_BLOCK_TITLE'),
				$content
			);
		}

		return $content;
	}

	/**
	 * Delete all blocks from db by codes.
	 * @param array $code Array of codes to delete.
	 * @return void
	 */
	public static function deleteByCode($code)
	{
		if (!is_array($code))
		{
			$code = array($code);
		}
		$res = parent::getList(array(
			'select' => array(
				'ID'
			),
			'filter' => array(
				'=CODE' => $code
			)
		));
		while ($row = $res->fetch())
		{
			self::parentDelete($row['ID']);
		}
	}

	/**
	 * Delete block row.
	 * @param int $id Block id.
	 * @return \Bitrix\Main\Result
	 */
	private static function parentDelete($id)
	{
		return parent::delete($id);
	}

	/**
	 * Add block row.
	 * @param array $fields Block data.
	 * @return \Bitrix\Main\Result
	 */
	public static function add($fields)
	{
		if (
			!defined('LANDING_MUTATOR_MODE') ||
			LANDING_MUTATOR_MODE !== true
		)
		{
			throw new \Bitrix\Main\SystemException(
				'Disabled for direct access.'
			);
		}
		else
		{
			return parent::add($fields);
		}
	}

	/**
	 * Update block row.
	 * @param int $id Primary key.
	 * @param array $fields Block data.
	 * @return \Bitrix\Main\Result
	 */
	public static function update($id, $fields = array())
	{
		if (
			!defined('LANDING_MUTATOR_MODE') ||
			LANDING_MUTATOR_MODE !== true
		)
		{
			throw new \Bitrix\Main\SystemException(
				'Disabled for direct access.'
			);
		}
		else
		{
			return parent::update($id, $fields);
		}
	}

	/**
	 * Delete block row.
	 * @param int $id Primary key.
	 * @return \Bitrix\Main\Result
	 */
	public static function delete($id)
	{
		if (
			!defined('LANDING_MUTATOR_MODE') ||
			LANDING_MUTATOR_MODE !== true
		)
		{
			throw new \Bitrix\Main\SystemException(
				'Disabled for direct access.'
			);
		}
		else
		{
			return parent::delete($id);
		}
	}

	/**
	 * Gets block's rows.
	 * @param array $fields Block orm data.
	 * @return \Bitrix\Main\DB\Result
	 */
	public static function getList($fields = array())
	{
		if (
			!defined('LANDING_MUTATOR_MODE') ||
			LANDING_MUTATOR_MODE !== true
		)
		{
			throw new \Bitrix\Main\SystemException(
				'Disabled for direct access.'
			);
		}
		else
		{
			return parent::getList($fields);
		}
	}
}