Your IP : 18.226.185.28


Current Path : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/landing/lib/
Upload File :
Current File : /home/bitrix/ext_www/klimatlend.ua/bitrix/modules/landing/lib/block.php

<?php
namespace Bitrix\Landing;

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
{
	/**
	 * Dir of repoitory of blocks.
	 */
	const BLOCKS_DIR = '/bitrix/blocks';

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

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

	/**
	 * Id of current block.
	 * @var int
	 */
	protected $id = 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 = '';

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

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

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

	/**
	 * Local path to the block.
	 * @var string
	 */
	protected $path = null;

	/**
	 * Absolutely path to the block.
	 * @var string
	 */
	protected $pathFull = null;

	/**
	 * 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(
				'filter' => array(
					'ID' => $id
				)
			))->fetch();
			if (!$data)
			{
				$id = 0;
			}
		}

		$this->id = intval($id);
		$this->sort = isset($data['SORT']) ? intval($data['SORT']) : '';
		$this->code = isset($data['CODE']) ? trim($data['CODE']) : '';
		$this->content = isset($data['CONTENT']) ? trim($data['CONTENT']) : '';
		$this->active = isset($data['ACTIVE']) && $data['ACTIVE'] == 'Y';

		if (preg_match('/repo_([\d]+)/', $this->code, $matches))
		{
			$this->repoId = $matches[1];
		}

		if (!$this->repoId)
		{
			$this->path = self::getBlockPath($this->code);
			$this->pathFull = Manager::getDocRoot() . $this->path;
		}

		$this->error = new Error;
	}

	/**
	 * Fill landing with blocks.
	 * @param \Bitrix\Landing\Landing $landing Landing instance.
	 * @param int $limit Limit count for blocks.
	 * @return boolean
	 */
	public static function fillLanding(\Bitrix\Landing\Landing $landing, $limit = 0)
	{
		if ($landing->exist())
		{
			$res = parent::getList(array(
				'filter' => array(
					'LID' => $landing->getId(),
					'=PUBLIC' => $landing->getEditMode() ? 'N' : 'Y'
				),
				'order' => array(
					'SORT' => 'ASC',
					'ID' => 'ASC'
				),
				'limit' => $limit
			));
			while ($row = $res->fetch())
			{
				$block = new self($row['ID'], $row);
				$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())
		{
			$res = parent::getList(array(
				'select' => array(
					'LID', 'CODE', 'SORT', 'ACTIVE', 'CONTENT'
				),
				'filter' => array(
					'LID' => $landing->getId(),
					'=PUBLIC' => 'Y'
				)
			));
			while ($row = $res->fetch())
			{
				$row['PUBLIC'] = 'N';
				parent::add($row);
			}
		}
	}

	/**
	 * Publication blocks for landing.
	 * @param \Bitrix\Landing\Landing $landing Landing instance.
	 * @return void
	 */
	public static function publicationBlocks(\Bitrix\Landing\Landing $landing)
	{
		if ($landing->exist())
		{
			$toDelete = array();
			$updateExist = false;
			$res = parent::getList(array(
				'select' => array(
					'ID', 'PUBLIC'
				),
				'filter' => array(
					'LID' => $landing->getId()
				)
			));
			while ($row = $res->fetch())
			{
				if ($row['PUBLIC'] == 'Y')
				{
					$toDelete[] = $row['ID'];
				}
				else
				{
					$updateExist = true;
					parent::update($row['ID'], array(
						'PUBLIC' => 'Y'
					));
				}
			}
			if ($updateExist)
			{
				foreach ($toDelete as $id)
				{
					parent::delete($id);
				}
			}
		}
	}

	/**
	 * 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;
	}

	/**
	 * 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
		$content = '';
		// local repo
		if (preg_match('/repo_([\d]+)/', $code, $matches))
		{
			$repo = Repo::getById($matches[1])->fetch();
			$content = $repo['CONTENT'];
		}
		// files storage
		elseif (($path = self::getBlockPath($code)))
		{
			$path = Manager::getDocRoot() . $path . '/block.php';
			if (file_exists($path))
			{
				$content = file_get_contents($path);
			}
		}
		// 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'
		);
		if (isset($data['ACTIVE']))
		{
			$fields['ACTIVE'] = $data['ACTIVE'];
		}
		if (isset($data['PUBLIC']))
		{
			$fields['PUBLIC'] = $data['PUBLIC'];
		}
		if (isset($data['SORT']))
		{
			$fields['SORT'] = $data['SORT'];
		}
		if (isset($data['CONTENT']))
		{
			$fields['CONTENT'] = $data['CONTENT'];
		}
		$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);
			}
			return $block;
		}
		else
		{
			$landing->getError()->addFromResult($res);
			return false;
		}
	}

	/**
	 * Get blocks from repository.
	 * @return array
	 */
	public static function getRepository()
	{
		$blocks = 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)
		{
			if (($handle = opendir($path . '/' . $subdir)))
			{
				while ((($entry = readdir($handle)) !== false))
				{
					$descriptionPath = $path . '/' . $subdir . '/' . $entry . '/.description.php';
					$previewPath = $path . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME;
					if ($entry != '.' && $entry != '..' && file_exists($descriptionPath))
					{
						Loc::loadLanguageFile($descriptionPath);
						$description = include $descriptionPath;
						if (isset($description['block']['name']))
						{
							$blocks[$entry] = array(
								'name' => $description['block']['name'],
								'namespace' => $subdir,
								'section' => isset($description['block']['section'])
												? $description['block']['section']
												: 'other',
								'description' => isset($description['block']['description'])
												? $description['block']['description']
												: '',
								'preview' => file_exists($previewPath)
												? self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME
												: ''
							);
						}
					}
				}
			}
		}

		// rest repo
		$blocks += \Bitrix\Landing\Repo::getRepository();

		// set by sections
		Loc::loadMessages(Manager::getDocRoot() . '/bitrix/modules/landing/blocks/sections.php');
		$blocksCats = array();
		$otherTitle = Loc::getMessage('LD_BLOCK_SECTION_OTHER');
		foreach ($blocks as $key => $block)
		{
			if (!is_array($block['section']))
			{
				$block['section'] = array($block['section']);
			}
			foreach ($block['section'] as $section)
			{
				$section = trim($section);
				if (!isset($blocksCats[$section]))
				{
					$blocksCats[$section] = array(
						'name' => Loc::getMessage('LD_BLOCK_SECTION_' . strtoupper($section)),
						'items' => array()
					);
					if (!$blocksCats[$section]['name'])
					{
						$blocksCats[$section]['name'] = $otherTitle;
					}
				}
				$blocksCats[$section]['items'][$key] = $block;
			}
		}

		return $blocksCats;
	}

	/**
     * 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.
	 * @return array
	 */
	public static function getBlockContent($id)
	{
		ob_start();
		$block = new self($id);
		$extContent = '';
		if (($ext = $block->getExt()))
		{
			$extContent = \CUtil::InitJSCore($ext, true);
		}
		$block->view();
		$content = ob_get_contents();
		ob_end_clean();
		if ($block->exist())
		{
			return array(
				'id' => $id,
				'content' => $content,
				'content_ext' => $extContent,
				'css' => $block->getCSS(),
				'js' => $block->getJS(),
				'manifest' => $block->getManifest()
			);
		}
		else
		{
			return array();
		}
	}

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

		$code = trim($code);

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

		$pathes[$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 . '/block.php'))
			{
				$pathes[$code] = $subdir;
				break;
			}
		}

		return $pathes[$code];
	}

	/**
	 * Get local path for block.
	 * @param string $code Code of block.
	 * @return string
	 */
	protected static function getBlockPath($code)
	{
		if (($namespace = self::getBlockNamespace($code)))
		{
			return self::BLOCKS_DIR . '/' . $namespace . '/' . $code;
		}
		else
		{
			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;
	}

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

	/**
	 * 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;
	}

	/**
	 * Set active to the block.
	 * @param boolean $active Bool: true or falce.
	 * @return void
	 */
	public function setActive($active)
	{
		$this->active = (boolean) $active;
	}

	/**
	 * Get local path to the block.
	 * @return string|null
	 */
	public function getPath()
	{
		return $this->path;
	}

	/**
	 * Get preview picture of the block.
	 * @return string
	 */
	public function getPreview()
	{
		if (file_exists($this->pathFull . '/' . self::PREVIEW_FILE_NAME))
		{
			return $this->path . '/' . self::PREVIEW_FILE_NAME;
		}
		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'])
				)
				{
					$class = $modified['class'];
				}
			}
		}

		$classes[$type] = $class;

		return $classes[$type];
	}

	/**
	 * Get manifest array from block.
	 * @return array
	 */
	public function getManifest()
	{
		static $manifestStore = array();

		if (isset($manifestStore[$this->code]))
		{
			return $manifestStore[$this->code];
		}

		$manifest = array();
		// manifet is exist and correct
		if ($this->repoId || file_exists($this->pathFull . '/.description.php'))
		{
			if ($this->repoId)
			{
				$manifest = Repo::getBlock($this->repoId);
			}
			else
			{
				Loc::loadLanguageFile($this->pathFull . '/.description.php');
				$manifest = include $this->pathFull . '/.description.php';
			}
			if (isset($manifest['block']['name']))
			{
				// 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))
					{
						$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
							));
							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))
					{
						$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' => $manifest['style']
					);
				}
				elseif (
					!isset($manifest['style']) ||
					!is_array($manifest['style'])
				)
				{
					$manifest['style'] = array(
						'block' => array(),
						'nodes' => array()
					);
				}
				// other
				$manifest['code'] = $this->code;
			}
			else
			{
				$manifest = array();
			}
		}

		$manifestStore[$this->code] = $manifest;

		return $manifest;
	}

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

		if (strpos($code, ':') !== false)
		{
			list($namespace, $code) = explode(':', $code);
			$pathFull = self::BLOCKS_DIR . '/' . $namespace . '/' . $code;
			$pathFull = Manager::getDocRoot() . $pathFull;

			if (file_exists($pathFull . '/.description.php'))
			{
				Loc::loadLanguageFile($pathFull . '/.description.php');
				$manifest = include $pathFull . '/.description.php';
			}
		}

		return $manifest;
	}

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

		if (!isset($asset[$this->code]))
		{
			$asset[$this->code] = array(
				'css' => array(),
				'js' => array(),
				'ext' => array()
			);
			// additional asset first
			if ($this->repoId || file_exists($this->pathFull . '/.description.php'))
			{
				if ($this->repoId)
				{
					$keys = array('css', 'js');
					$manifest = Repo::getBlock($this->repoId);
				}
				else
				{
					$keys = array('css', 'js', 'ext');
					$manifest = include $this->pathFull . '/.description.php';
				}
				foreach ($keys as $ass)
				{
					if (
						isset($manifest['assets'][$ass]) &&
						!empty($manifest['assets'][$ass])
					)
					{
						$asset[$this->code][$ass] = array_merge(
							$asset[$this->code][$ass],
							(array)$manifest['assets'][$ass]
						);
					}
				}
			}
			if (!$this->repoId)
			{
				// base files next
				$file = $this->getPath() . '/style.css';
				if (file_exists(Manager::getDocRoot() . $file))
				{
					$asset[$this->code]['css'][] = $file;
				}
				$file = $this->getPath() . '/script.js';
				if (file_exists(Manager::getDocRoot() . $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');
	}

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

	/**
	 * Out the block.
	 * @param boolean $edit Out block in edit mode.
	 * @return void
	 */
	public function view($edit = false)
	{
		global $APPLICATION;

		if ($edit || $this->active)
		{
			if (!$this->repoId)
			{
				Loc::loadMessages($this->pathFull . '/block.php');
			}
			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)
				{
					Asset::getInstance()->addJS($js);
				}
			}
		}

		$content = '<div id="' . $this->getAnchor($this->id) . '" class="block-wrapper' . (!$this->active ? ' landing-block-deactive' : '') . '">' .
						$this->content .
					'</div>';

		if ($edit)
		{
			$manifest = $this->getManifest();
			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')  . ', '
										. 'manifest: ' . Json::encode($manifest)
									. '}'
								. ');'
							. '}'
						. '});'
					. '</script>';
			}
			if ($this->repoId)
			{
				echo $content;
			}
			else
			{
				eval('?>' . $content . '<?');
			}
		}
		elseif ($this->active)
		{
			if ($this->repoId)
			{
				echo $content;
			}
			else
			{
				eval('?>' . $content . '<?');
			}

		}
	}

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

	/**
	 * Save current block in DB.
	 * @return boolean
	 */
	public function save()
	{
		$data = array(
			'SORT' => $this->sort,
			'ACTIVE' => $this->active ? 'Y' : 'N',
			'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
		));
		$this->error->addFromResult($res);
		return $res->isSuccess();
	}

	/**
	 * Delete current block.
	 * @return boolean
	 */
	public function unlink()
	{
		$res = parent::delete($this->id);
		if (!$res->isSuccess())
		{
			$this->error->addFromResult($res);
		}

		return $res->isSuccess();
	}

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

	/**
	 * 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.
	 * @return DOM\Document
	 */
	public function getDom()
	{
		static $doc = array();

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

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

	/**
	 * Clone one card in block by selector.
	 * @param string $selector Selector.
	 * @param int $position Card position.
	 * @return boolean Success or failure.
	 */
	public function cloneCard($selector, $position)
	{
		$manifest = $this->getManifest();
		if (isset($manifest['cards'][$selector]))
		{
			$doc = $this->getDom();
			$resultList = $doc->querySelectorAll($selector);
			if (isset($resultList[$position]))
			{
				$resultList[$position]->getParentNode()->insertBefore(
					$resultList[$position],
					isset($resultList[$position + 1])
					? $resultList[$position + 1]
					: null,
					false
				);
				$this->saveContent($doc->saveHTML());
				return true;
			}

		}

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

	/**
	 * 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 content to nodes of block.
	 * @param array $data Nodes data array.
	 * @return void
	 */
	public function updateNodes($data)
	{
		$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]
				));
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
	}

	/**
	 * 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 void
	 */
	public function setClasses($data)
	{
		$doc = $this->getDom();
		$manifest = $this->getManifest();

		// 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 $resultNode)
				{
					$resultNode->setClassName(
						implode(' ', $data[$selector]['classList'])
					);
					// affected styles
					if (!empty($data[$selector]['affect']))
					{
						$resultNode = $this->removeStyle(
							$resultNode,
							$data[$selector]['affect']
						);
					}
				}
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
	}

	/**
	 * 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(
			//
		);
		foreach ($attrs as $selector => $item)
		{
			if (isset($data[$selector]))
			{
				// not multi
				if (!isset($item[0]))
				{
					$item = array($item);
				}
				// prepare attrs
				$attrItems = array();
				foreach ($item as $val)
				{
					if (
						isset($val['attribute']) &&
						isset($data[$selector][$val['attribute']])
					)
					{
						$attrItems[$val['attribute']] = $data[$selector][$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 $resultNode)
				{
					foreach ($attrItems as $code => $val)
					{
						$resultNode->setAttribute(
							\htmlspecialcharsbx($code),
							$val
						);
					}
				}
			}
		}
		// save rebuild html as text
		$this->saveContent($doc->saveHTML());
	}

	public static function add($fields)
	{
		throw new \Bitrix\Main\SystemException(
			'Disabled for direct access.'
		);
	}

	public static function update($id, $fields = array())
	{
		throw new \Bitrix\Main\SystemException(
			'Disabled for direct access.'
		);
	}

	public static function delete($id)
	{
		throw new \Bitrix\Main\SystemException(
			'Disabled for direct access.'
		);
	}

	public static function getList($fields = array())
	{
		throw new \Bitrix\Main\SystemException(
			'Disabled for direct access.'
		);
	}
}