%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/bitrix/www/bitrix/modules/landing/lib/
Upload File :
Create Path :
Current File : //home/bitrix/www/bitrix/modules/landing/lib/landing.php

<?php
namespace Bitrix\Landing;

use \Bitrix\Main\Event;
use \Bitrix\Main\EventResult;
use \Bitrix\Main\Page\Asset;
use \Bitrix\Main\Localization\Loc;
use \Bitrix\Main\ModuleManager;

Loc::loadMessages(__FILE__);

class Landing extends \Bitrix\Landing\Internals\BaseTable
{
	/**
	 * Internal class.
	 * @var string
	 */
	public static $internalClass = 'LandingTable';

	/**
	 * Enabled updated or not.
	 * @var bool
	 */
	protected static $enabledUpdate = true;

	/**
	 * Check deleted pages or not.
	 * @var bool
	 */
	protected static $checkDelete = true;

	/**
	 * Current mode is edit.
	 * @var boolean
	 */
	protected static $editMode = false;

	/**
	 * Current mode is preview.
	 * @var boolean
	 */
	protected static $previewMode = false;

	/**
	 * External variables of Landing.
	 * @var array
	 */
	protected static $variables = array();

	/**
	 * Set false if landing view as area.
	 * @var boolean
	 */
	protected $mainInstance = true;

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

	/**
	 * All blocks of current landing.
	 * @var array
	 */
	protected $blocks = array();

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

	/**
	 * Title of current landing.
	 * @var string
	 */
	protected $title = '';

	/**
	 * Code (part of URL) of current landing.
	 * @var string
	 */
	protected $code = '';

	/**
	 * XMl Id of current landing.
	 * @var string
	 */
	protected $xmlId = '';

	/**
	 * Site id of current landing.
	 * @var int
	 */
	protected $siteId = 0;

	/**
	 * Folder id of current landing.
	 * @var int
	 */
	protected $folderId = 0;

	/**
	 * Current template id.
	 * @var int
	 */
	protected $tplId = 0;

	/**
	 * Current template type (site or landing).
	 * @var string
	 */
	protected $tplType = 'landing';

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

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

	/**
	 * Constructor.
	 * @param int $id Landing id.
	 * @param array $params Some params.
	 */
	protected function __construct($id, $params = array())
	{
		$this->error = new Error;
		$filter = array(
			'ID' => $id
		);
		if (
			isset($params['force_deleted']) &&
			$params['force_deleted'] === true
		)
		{
			$filter['=DELETED'] = ['Y', 'N'];
			$filter['=SITE.DELETED'] = ['Y', 'N'];
		}
		$landing = self::getList(array(
			'select' => array(
				'*',
				'SITE_TPL_ID' => 'SITE.TPL_ID'
			),
			'filter' => $filter
		))->fetch();

		if ($landing)
		{
			/*
			 * $this->getEditMode()
			 * @todo return if no access
			 */
			// get base data
			$this->title = $landing['TITLE'];
			$this->code = $landing['CODE'];
			$this->xmlId = $landing['XML_ID'];
			$this->id = (int)$landing['ID'];
			$this->siteId = (int)$landing['SITE_ID'];
			$this->folderId = (int)$landing['FOLDER_ID'];
			$this->active = $landing['ACTIVE'] == 'Y';
			$this->tplId = $landing['TPL_ID'] > 0
							? $landing['TPL_ID']
							: (
								$landing['SITE_TPL_ID'] > 0
								? $landing['SITE_TPL_ID']
								: 0
							);
			if (isset($params['is_area']) && $params['is_area'])
			{
				$this->mainInstance = false;
			}
			if ($landing['SITE_TPL_ID'] > 0 && !$landing['TPL_ID'])
			{
				$this->tplType = 'site';
			}
			// if edit mode - create copy for edit
			if ($this->getEditMode())
			{
				if ($landing['PUBLIC'] == 'Y')
				{
					self::update($id, array(
						'PUBLIC' => 'N',
						'DATE_MODIFY' => false
					));
					Block::cloneForEdit($this);
				}
			}
			// if landing is unactive
			if (
				false &&
				!$this->active
			)
			{
				//add error ?
				//add title ? $this->title = Loc::getMessage('LANDING_TITLE_NOT_FOUND');
			}
			// get available blocks
			else
			{
				Block::fillLanding(
					$this,
					isset($params['blocks_limit']) ? $params['blocks_limit'] : 0,
					array(
						'deleted' => isset($params['deleted']) && $params['deleted'] === true
					)
				);
			}
			// fill meta data
			$keys = ['CREATED_BY_ID', 'MODIFIED_BY_ID', 'DATE_CREATE', 'DATE_MODIFY'];
			foreach ($keys as $key)
			{
				if (isset($landing[$key]))
				{
					$this->metaData[$key] = $landing[$key];
				}
			}
		}
		// landing not found
		else
		{
			$this->error->addError(
				'LANDING_NOT_EXIST',
				Loc::getMessage('LANDING_NOT_FOUND')
			);
			$this->title = Loc::getMessage('LANDING_TITLE_NOT_FOUND');
		}
	}

	/**
	 * Set work mode to edit.
	 * @param boolean $mode Edit mode.
	 * @return void
	 */
	public static function setEditMode($mode = true)
	{
		self::$editMode = (boolean) $mode;
	}

	/**
	 * Get state of edit mode.
	 * @return boolean
	 */
	public static function getEditMode()
	{
		return self::$editMode;
	}

	/**
	 * Set work mode to preview.
	 * @param boolean $mode Preview mode.
	 * @return void
	 */
	public static function setPreviewMode($mode = true)
	{
		self::$previewMode = (boolean) $mode;
	}

	/**
	 * Get state of preview mode.
	 * @return boolean
	 */
	public static function getPreviewMode()
	{
		return self::$previewMode;
	}

	/**
	 * Check delete pages or not.
	 * @return bool
	 */
	public static function checkDeleted()
	{
		return self::$checkDelete;
	}

	/**
	 * Disable check delete.
	 * @return void
	 */
	public static function disableCheckDeleted()
	{
		self::$checkDelete = false;
	}

	/**
	 * Enable check delete.
	 * @return void
	 */
	public static function enableCheckDeleted()
	{
		self::$checkDelete = true;
	}

	/**
	 * Disable update.
	 * @return void
	 */
	public static function disableUpdate()
	{
		self::$enabledUpdate = false;
	}

	/**
	 * Enable update.
	 * @return void
	 */
	public static function enableUpdate()
	{
		self::$enabledUpdate = true;
	}

	/**
	 * Create current instance.
	 * @param int $id Landing id.
	 * @param array $params Additional params.
	 * @return Landing
	 */
	public static function createInstance($id, array $params = array())
	{
		return new self($id, $params);
	}

	/**
	 * Mark entity as deleted.
	 * @param int $id Entity id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markDelete($id)
	{
		return parent::update($id, array(
			'DELETED' => 'Y'
		));
	}


	/**
	 * Mark entity as restored.
	 * @param int $id Entity id.
	 * @return \Bitrix\Main\Result
	 */
	public static function markUnDelete($id)
	{
		return parent::update($id, array(
			'DELETED' => 'N'
		));
	}

	/**
	 * Delete landing by id and its blocks.
	 * @param int $id Landing id.
	 * @param bool $forceDeleted Force delete throw an errors.
	 * @return \Bitrix\Main\Result
	 */
	public static function delete($id, $forceDeleted = false)
	{
		$result = new \Bitrix\Main\Entity\DeleteResult();
		$params = [];

		if ($forceDeleted)
		{
			$params['force_deleted'] = true;
		}

		// first check
		foreach (array('draft', 'public') as $code)
		{
			self::setEditMode($code == 'draft');
			$landing = self::createInstance($id, $params);
			if ($landing->exist())
			{
				foreach ($landing->getBlocks() as $block)
				{
					if ($block->getAccess() < $block::ACCESS_X)
					{
						$result->addError(
							new \Bitrix\Main\Error(
								Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED'),
								'ACCESS_DENIED'
							)
						);
						return $result;
					}
				}
			}
			else
			{
				$result->addError(
					$landing->getError()->getErrors()[0]
				);
				return $result;
			}
		}

		// delete blocks
		foreach (array('draft', 'public') as $code)
		{
			self::setEditMode($code == 'draft');
			$landing = self::createInstance($id, $params);
			if ($landing->exist())
			{
				foreach ($landing->getBlocks() as $block)
				{
					if (!$block->unlink())
					{
						$result->addErrors($block->getError()->getErrors());
						return $result;
					}
				}
				File::deleteFromLanding($id);
			}
		}

		return parent::delete($id);
	}

	/**
	 * Get hooks of Landing.
	 * @param int $id Landing id.
	 * @return array Array of Hook.
	 */
	public static function getHooks($id)
	{
		return Hook::getForLanding($id);
	}

	/**
	 * Get additional fields of Landing.
	 * @param int $id Landing id.
	 * @return array Array of Field.
	 */
	public static function getAdditionalFields($id)
	{
		$fields = array();

		// now we can get additional fields only from hooks
		foreach (self::getHooks($id) as $hook)
		{
			$fields += $hook->getPageFields();
		}

		return $fields;
	}

	/**
	 * Save additional fields for Landing.
	 * @param int $id Landing id.
	 * @param array $data Data array.
	 * @return void
	 */
	public static function saveAdditionalFields($id, array $data)
	{
		// now we can get additional fields only from hooks
		Hook::saveForLanding($id, $data);
	}

	/**
	 * Set external variables of Landing.
	 * @param array $vars Additional vars.
	 * @return void
	 */
	public static function setVariables(array $vars)
	{
		foreach ($vars as $code => $val)
		{
			self::$variables[$code] = $val;
		}
	}

	/**
	 * Get external variables of Landing.
	 * @return array
	 */
	public static function getVariables()
	{
		return self::$variables;
	}

	/**
	 * Get preview picture of the landing.
	 * Is the preview of first block.
	 * @return string
	 */
	public function getPreview()
	{
		if (
			(
				!defined('LANDING_DISABLE_CLOUD')
				||
				LANDING_DISABLE_CLOUD !== true
			)
			&&
			Manager::isB24())
		{
			return $this->getPublicUrl() . 'preview.jpg';
		}

		static $hookPics = null;

		if ($hookPics === null)
		{
			$hookPics = \Bitrix\Landing\Hook\Page\MetaOg::getAllImages();
		}

		if (isset($hookPics[$this->id]))
		{
			$pic = $hookPics[$this->id];
			if (intval($pic) > 0)
			{
				$pic = \CFile::getPath($pic);
			}
			return $pic;
		}

		return '/bitrix/images/landing/nopreview.jpg';
	}

	/**
	 * Get full pubic URL for this landing.
	 * @param int|array $id Landing id (id array), optional.
	 * @param boolean $absolute Full url.
	 * @param bool $createPubPath Create pub path (checking and create).
	 * @return string
	 */
	public function getPublicUrl($id = false, $absolute = true, $createPubPath = false)
	{
		if ($id === false)
		{
			$id = $this->id;
		}

		$bitrix24 = Manager::isB24();
		$disableCloud = defined('LANDING_DISABLE_CLOUD') &&
						LANDING_DISABLE_CLOUD === true;

		$domainDefault = null;
		$data = array();
		$res = Landing::getList(array(
			'select' => array(
				'ID',
				'CODE',
				'SITE_ID',
				'SITE_ID_INDEX' => 'SITE.LANDING_ID_INDEX',
				'SITE_PROTOCOL' => 'SITE.DOMAIN.PROTOCOL',
				'SITE_DOMAIN' => 'SITE.DOMAIN.DOMAIN',
				'SITE_CODE' => 'SITE.CODE',
				'SITE_SMN_ID' => 'SITE.SMN_SITE_ID',
				'FOLDER_CODE' => 'LF.CODE'
			),
			'filter' => array(
				'ID' => $id,
				'=DELETED' => ['Y', 'N']
			),
			'runtime' => array(
				new \Bitrix\Main\Entity\ReferenceField(
					'LF',
					'\Bitrix\Landing\Internals\LandingTable',
					array('=this.FOLDER_ID' => 'ref.ID')
				)
			)
		));
		while ($row = $res->fetch())
		{
			$domainReplace = false;
			$row['SITE_ID_ORIG'] = $row['SITE_ID'];
			// build site domain by default
			if (!$row['SITE_DOMAIN'])
			{
				if (!$domainDefault)
				{
					$domainDefault =  Domain::getList(array(
					  	'filter' => array(
							'ID' => Domain::getCurrentId()
					  	)
					  ))->fetch();
				}
				if (isset($domainDefault['PROTOCOL']))
				{
					$row['SITE_PROTOCOL'] = $domainDefault['PROTOCOL'];
				}
				if (isset($domainDefault['DOMAIN']))
				{
					$row['SITE_DOMAIN'] = $domainDefault['DOMAIN'];
				}
				$domainReplace = true;
			}
			// force https
			if (Manager::isHttps())
			{
				$row['SITE_PROTOCOL'] = 'https';
			}
			if ($domainReplace || !$bitrix24 || $disableCloud)
			{
				$pubPath = Manager::getPublicationPath(
					null,
					$row['SITE_SMN_ID'] ? $row['SITE_SMN_ID'] : SITE_ID,
					$createPubPath
				);
				$pubPath = rtrim($pubPath, '/');
			}
			// for create publication path
			if (!ModuleManager::isModuleInstalled('bitrix24'))
			{
				Manager::getPublicationPath(
					null,
					$row['SITE_SMN_ID'] ? $row['SITE_SMN_ID'] : SITE_ID,
					$createPubPath
				);
			}
			if (isset($row['SITE_ID']))
			{
				$row['SITE_ID'] = '/' . $row['SITE_ID'] . '/';
			}
			if ($disableCloud)
			{
				$data[$row['ID']] = $pubPath .
									$row['SITE_ID'] .
									(self::$previewMode ? 'preview/' . Site::getPublicHash($row['SITE_ID_ORIG'], $row['SITE_DOMAIN']) . '/' : '') .
									($row['FOLDER_CODE'] ? $row['FOLDER_CODE'] . '/' : '') .
									(($row['ID'] == $row['SITE_ID_INDEX']) ? '' : $row['CODE'] . '/');
			}
			else
			{
				$data[$row['ID']] = (
									$absolute
										? (
											$row['SITE_PROTOCOL'] . '://' .
											$row['SITE_DOMAIN']
										)
										: ''
									) .
									(($domainReplace || !$bitrix24) ? $pubPath : '') .
									((self::$previewMode && !$bitrix24) ? '/preview/' . Site::getPublicHash($row['SITE_ID_ORIG'], $row['SITE_DOMAIN']) : '') .
									(($domainReplace && $bitrix24) ? $row['SITE_ID'] : '/') .
									((self::$previewMode && $bitrix24) ? 'preview/' . Site::getPublicHash($row['SITE_ID_ORIG'], $row['SITE_DOMAIN']) . '/' : '') .
									($row['FOLDER_CODE'] ? $row['FOLDER_CODE'] . '/' : '') .
									(($row['ID'] == $row['SITE_ID_INDEX']) ? '' : $row['CODE'] . '/');
			}

		}

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

		return false;
	}

	/**
	 * View landing in public or edit mode.
	 * @param array $params Some additional params.
	 * @return void
	 */
	public function view(array $params = array())
	{
		$blockEditMode = $this->getEditMode();
		$editMode = $this->mainInstance && $blockEditMode;

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

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

		// title
		if ($this->mainInstance)
		{
			Manager::setPageTitle(
				\htmlspecialcharsbx($this->title)
			);
		}

		// add chain item if need
		if ($this->mainInstance)
		{
			if ($this->folderId)
			{
				$res = self::getList(array(
					 'select' => array(
						'ID', 'TITLE'
					 ),
					'filter' => array(
						'ID' => $this->folderId
					)
		 		));
				if ($row = $res->fetch())
				{
					Manager::getApplication()->addChainItem(
						$row['TITLE'],
						'#landing' . $row['ID']
					);
				}
			}
			else
			{
				Manager::getApplication()->addChainItem(
					$this->title,
					'#landing' . $this->id
				);
			}
		}

		// assets
		if ($editMode)
		{
			$options = array(
				'site_id' => $this->siteId,
				'server_name' => $_SERVER['SERVER_NAME'],
				'url' => $this->getPublicUrl(),
				'xml_id' => $this->xmlId,
				'blocks' => Block::getRepository(),
				'style' => Block::getStyle()
			);
			// event for redefine $options
			$event = new Event('landing', 'onLandingView', array(
				'options' => $options
			));
			$event->send();
			foreach ($event->getResults() as $result)
			{
				if ($result->getResultType() != EventResult::ERROR)
				{
					if (($modified = $result->getModified()))
					{
						if (isset($modified['options']))
						{
							$options = $modified['options'];
						}
					}
				}
			}
			// output js
			Asset::getInstance()->addString(
				'<script type="text/javascript">' .
					'BX.ready(function(){'
						. 'if (typeof BX.Landing.Main !== "undefined")'
						. '{'
							. 'BX.Landing.Main.createInstance(' . $this->id . ', ' . \CUtil::phpToJSObject($options, false, false, true) . ');'
						. '}'
					. '});' .
				'</script>'
			);
		}

		$content = '';

		// templates part - first
		if ($params['apply_template'] && $this->mainInstance)
		{
			if (!TemplateRef::landingIsArea($this->id))
			{
				$content = $this->applyTemplate();
			}
			else
			{
				$content = '<div class="landing-main"' .
						   		' data-site="' . $this->siteId . '"' .
						   		' data-landing="' . $this->id . '">' .
								'#CONTENT#' .
							'</div>';
			}
		}

		// then content
		ob_start();
		foreach ($this->blocks as $block)
		{
			$block->view($blockEditMode, $this);
		}
		if ($this->mainInstance)
		{
			$this->execHooks();
		}
		$contentMain = ob_get_contents();
		ob_end_clean();

		// implode content and templates parts
		if ($content && strpos($content, '#CONTENT#') !== false)
		{
			$content = str_replace('#CONTENT#', $contentMain, $content);
		}
		else
		{
			$content = $contentMain;
		}

		// breadcrumb (see chain_template.php in tpl) and title
		if (!$blockEditMode && $this->mainInstance)
		{
			ob_start();
			echo Manager::getApplication()->getNavChain(
				false, 0, false, true
			);
			$breadcrumb = ob_get_contents();
			ob_end_clean();
			$content = str_replace(
				array(
					'#breadcrumb#',
					'#title#'
				),
				array(
					$breadcrumb,
					Manager::getApplication()->getTitle()
				),
				$content
			);
		}

		// parse links between landings
		if ($params['parse_link'] === true && !$blockEditMode)
		{
			echo $this->parseLocalUrl($content);
		}
		else
		{
			echo $content;
		}
	}

	/**
	 * Get included areas of this page.
	 * @return array
	 */
	public function getAreas()
	{
		if ($this->tplType == 'site')
		{
			return TemplateRef::getForSite($this->siteId);
		}
		else
		{
			return TemplateRef::getForLanding($this->id);
		}
	}

	/**
	 * Apply template for this landing.
	 * @param string $content Landing content.
	 * @return string
	 */
	protected function applyTemplate($content = null)
	{
		if ($this->tplId)
		{
			$template = Template::getList(array(
				'filter' => array(
					'ID' => $this->tplId
				)
			))->fetch();
			if ($template)
			{
				$editMode = $this->getEditMode();
				if ($template['XML_ID'] == 'empty')
				{
					$template['CONTENT'] = '<div class="landing-main">' .
												$template['CONTENT'] .
											'</div>';
				}
				if ($editMode)
				{
					$replace = array(
						'>#CONTENT#<' => ' data-site="' . $this->siteId .
										'" data-landing="' . $this->id .
										'">#CONTENT#<',
						'#CONTENT#' => $content ? $content : '#CONTENT#'
					);
				}
				else
				{
					$replace = array(
						'#CONTENT#' => $content ? $content : '#CONTENT#'
					);
				}
				// if areas exist, get landings
				if ($template['AREA_COUNT'] > 0)
				{
					foreach ($this->getAreas() as $area => $lid)
					{
						ob_start();
						$landing = self::createInstance($lid, array(
							'is_area' => true
						));
						if ($landing->exist())
						{
							$landing->view();
						}
						if ($editMode)
						{
							$replace['>#AREA_' . $area . '#<'] = ' data-site="' . $landing->getSiteId() .
																'" data-landing="' . $lid .
																'">#AREA_' . $area . '#<';
						}
						$replace['#AREA_' . $area . '#'] = ob_get_contents();
						ob_end_clean();
					}
				}
				$content = str_replace(
					array_keys($replace),
					array_values($replace),
					$template['CONTENT']
				);
			}
		}
		else if ($this->getEditMode())
		{
			if (!$content)
			{
				$content = '#CONTENT#';
			}
			$content = '<div class="landing-main" ' .
							'data-site="' . $this->siteId . '" ' .
							'data-landing="' . $this->id . '">' .
								$content .
						'</div>';
		}

		return $content;
	}

	/**
	 * Parse between-landings url in landing content.
	 * @param string $content Landing content.
	 * @return string
	 */
	protected function parseLocalUrl($content)
	{
		$pattern = '/([",\'\;]{1})#(landing|block)([\d]+)\@{0,1}([^\'"]*)([",\'\&]{1})/is';

		// replace catalog links in preview mode
		if (self::$previewMode)
		{
			$content = preg_replace_callback(
				'/href\="#catalog(Element|Section)([\d]+)"/i',
				function($href)
				{
					return 'href="' . PublicAction\Utils::getIblockURL(
							$href[2],
							strtolower($href[1])
						) . '"';
				},
				$content);
		}

		// prepare system pages
		$sysPages = array();
		foreach (Syspage::get($this->siteId) as $syspage)
		{
			$sysPages['@#system_' . $syspage['TYPE'] . '@'] = '#landing' . $syspage['LANDING_ID'];
		}
		if (!empty($sysPages))
		{
			$content = preg_replace(
				array_keys($sysPages),
				array_values($sysPages),
				$content
			);
		}

		// replace in content
		if (preg_match_all($pattern, $content, $matches))
		{
			$urls = array(
				'LANDING' => array(),
				'BLOCK' => array()
			);
			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
			{
				if (strtoupper($matches[2][$i]) == 'LANDING')
				{
					$urls['LANDING'][] = $matches[3][$i];
				}
				else
				{
					$urls['BLOCK'][] = $matches[3][$i];
				}
			}
			// get parent landings for blocks
			// and public version of blocks too
			$anchorsId = array();
			$anchorsPublicId = array();
			if (!empty($urls['BLOCK']))
			{
				$urls['BLOCK'] = Block::getRowByBlockId(
					$urls['BLOCK'],
					array(
						'ID', 'LID', 'PARENT_ID', 'ANCHOR'
					)
				);
				foreach ($urls['BLOCK'] as $bid => &$bidRow)
				{
					if (
						!self::$previewMode &&
						$bidRow['PARENT_ID']
					)
					{
						$anchorsPublicId[$bid] = $bidRow['PARENT_ID'];
					}
					else
					{
						$anchorsId[$bid] = $bidRow['ANCHOR']
											? \htmlspecialcharsbx($bidRow['ANCHOR'])
											: Block::getAnchor($bidRow['ID']);
					}
					$bidRow = $bidRow['LID'];
				}
				unset($bidRow);
				$urls['LANDING'] = array_unique(array_merge(
					$urls['LANDING'],
					$urls['BLOCK']
				));
			}
			// get anchors for public version
			if ($anchorsPublicId)
			{
				$anchorsPublicIdTmp = Block::getRowByBlockId(
					$anchorsPublicId,
					array(
						'ID', 'LID', 'PARENT_ID', 'ANCHOR'
					)
				);
				foreach ($anchorsPublicId as $bid => $bidParent)
				{
					if (!isset($anchorsPublicIdTmp[$bidParent]))
					{
						continue;
					}
					$bidParent = $anchorsPublicIdTmp[$bidParent];
					$anchorsPublicId[$bid] = $bidParent['ANCHOR']
											? \htmlspecialcharsbx($bidParent['ANCHOR'])
											: Block::getAnchor($bidParent['ID']);
				}
			}
			$anchorsPublicId += $anchorsId;
			// get landing and blocks urls
			if (!empty($urls['LANDING']))
			{
				$urls['LANDING'] = $this->getPublicUrl($urls['LANDING']);
			}
			if (!empty($urls['BLOCK']))
			{
				foreach ($urls['BLOCK'] as $bid => $lid)
				{
					if (isset($urls['LANDING'][$lid]))
					{
						$urls['BLOCK'][$bid] = $urls['LANDING'][$lid] . '#' . $anchorsPublicId[$bid];
					}
					else
					{
						unset($urls['BLOCK'][$bid]);
					}
				}
			}
			// replace urls
			if (!empty($urls['LANDING']))
			{
				krsort($urls['LANDING']);
				$content = preg_replace_callback(
					$pattern,
					function($matches) use($urls)
					{
						$matches[2] = strtoupper($matches[2]);
						if (isset($urls[$matches[2]][$matches[3]]))
						{
							return $matches[1] .
										$urls[$matches[2]][$matches[3]] . $matches[4] . 
									$matches[5];
						}
					},
					$content
				);
				$landingUrls = array();
				foreach ($urls['LANDING'] as $lid => $url)
				{
					$landingUrls['@#landing' . $lid.'@'] = $url;
				}
			}
		}

		return $content;
	}

	/**
	 * Exec hooks for landing (site and landing).
	 * @return void
	 */
	protected function execHooks()
	{
		$hooksExec = array();

		foreach (Hook::getForSite($this->siteId) as $hook)
		{
			if ($hook->enabled())
			{
				$hooksExec[$hook->getCode()] = $hook;
			}
		}

		foreach (Hook::getForLanding($this->id) as $hook)
		{
			if ($hook->enabled())
			{
				$hooksExec[$hook->getCode()] = $hook;
			}
		}

		foreach ($hooksExec as $hook)
		{
			if (
				!$this->getEditMode() ||
				$hook->enabledInEditMode()
			)
			{
				$hook->exec();
			}
		}
	}

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

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

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

	/**
	 * Get xml id of current landing.
	 * @return int
	 */
	public function getXmlId()
	{
		return $this->xmlId;
	}

	/**
	 * Get title of current landing.
	 * @return int
	 */
	public function getTitle()
	{
		return $this->title;
	}

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

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

	/**
	 * Get site id of main, if exist.
	 * @return string
	 */
	public function getSmnSiteId()
	{
		if ($this->siteId)
		{
			$res = Site::getList(array(
				'select' => array(
					'SMN_SITE_ID'
				),
				'filter' => array(
					'ID' => $this->siteId
				)
			));
			if ($row = $res->fetch())
			{
				return $row['SMN_SITE_ID']
						? $row['SMN_SITE_ID']
						: SITE_ID;
			}
		}

		return SITE_ID;
	}

	/**
	 * Get all blocks of current landing.
	 * @return Block[]
	 */
	public function getBlocks()
	{
		return $this->blocks;
	}

	/**
	 * Add new Block to the current landing.
	 * @param \Bitrix\Landing\Block $block New block instance.
	 * @return void
	 */
	public function addBlockToCollection(\Bitrix\Landing\Block $block)
	{
		if ($block->exist())
		{
			$this->blocks[$block->getId()] = $block;
		}
	}

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

	/**
	 * Change modified user and date for current landing.
	 * @return void
	 */
	public function touch()
	{
		self::update($this->id);
	}

	/**
	 * Publication current landing.
	 * @return boolean
	 */
	public function publication()
	{
		return Mutator::landingPublication($this);
	}

	/**
	 * Cancel publication of landing.
	 * @return boolean
	 */
	public function unpublic()
	{
		$date = new \Bitrix\Main\Type\DateTime;
		$res = parent::update($this->id, array(
			'ACTIVE' => 'N',
			'PUBLIC' => 'N',
			'DATE_MODIFY' => false
		));
		if ($res->isSuccess())
		{
			if (Manager::isB24())
			{
				Site::update($this->siteId, array());
			}
			return true;
		}
		else
		{
			$this->error->addFromResult($res);
			return false;
		}
	}

	/**
	 * Add new block to the landing.
	 * @param string $code Code of block.
	 * @param array $data Data array of block.
	 * @return int|false Id of new block or false on failure.
	 */
	public function addBlock($code, $data = array())
	{
		$block = Block::createFromRepository($this, $code, $data);

		if ($block)
		{
			$this->touch();
			$this->addBlockToCollection($block);
			return $block->getId();
		}

		return false;
	}

	/**
	 * Delete one block from current landing.
	 * @param int $id Block id.
	 * @return boolean
	 */
	public function deleteBlock($id)
	{
		if (isset($this->blocks[$id]))
		{
			$result = $this->blocks[$id]->unlink();
			$this->error->copyError(
				$this->blocks[$id]->getError()
			);
			if ($result)
			{
				unset($this->blocks[$id]);
			}
			$this->touch();
			return $result;
		}
		else
		{
			$this->error->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
			return false;
		}
	}

	/**
	 * Mark delete or not the block.
	 * @param int $id Block id.
	 * @param boolean $mark Mark.
	 * @return boolean
	 */
	public function markDeletedBlock($id, $mark)
	{
		if (!isset($this->blocks[$id]))
		{
			$this->blocks[$id] = new Block($id);
		}

		if (isset($this->blocks[$id]))
		{
			if ($this->blocks[$id]->getAccess() >= $this->blocks[$id]->ACCESS_X)
			{
				$this->blocks[$id]->markDeleted($mark);
				if ($this->blocks[$id]->save())
				{
					if ($mark)
					{
						unset($this->blocks[$id]);
					}
					else
					{
						$this->addBlockToCollection(
							$this->blocks[$id]
						);
					}
					$this->touch();
					return true;
				}
				else
				{
					$this->error->copyError(
						$this->blocks[$id]->getError()
					);
				}
			}
			else
			{
				$this->error->addError(
					'ACCESS_DENIED',
					Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED')
				);
				return false;
			}
		}
		else
		{
			$this->error->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
			return false;
		}
	}

	/**
	 * Transfer one block to another landing.
	 * @param int $id Block id.
	 * @param int $lid Landing id.
	 * @return boolean
	 */
	protected function transferBlock($id, $lid)
	{
		if (isset($this->blocks[$id]))
		{
			$result = $this->blocks[$id]->changeLanding($lid);
			$this->error->copyError($this->blocks[$id]->getError());
			if ($result)
			{
				unset($this->blocks[$id]);
			}
			return $result;
		}
		else
		{
			$this->error->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
			return false;
		}
	}

	/**
	 * Resort current blocks.
	 * @return void
	 */
	public function resortBlocks()
	{
		uasort($this->blocks, function($a, $b)
		{
			if ($a->getSort() == $b->getSort())
			{
				return ($a->getId() < $b->getId()) ? -1 : 1;
			}
			return ($a->getSort() < $b->getSort()) ? -1 : 1;
		});
		$sort = 0;
		foreach ($this->blocks as $id => $block)
		{
			$block->saveSort($sort);
			$sort += 500;
		}
	}

	/**
	 * Sort the block on the landing.
	 * @param int $id Block id.
	 * @param string $action Code: up or down.
	 * @return boolean
	 */
	protected function sortBlock($id, $action)
	{
		if (isset($this->blocks[$id]))
		{
			$blocks = array_keys($this->blocks);
			for ($i = 0, $c = count($blocks); $i < $c; $i++)
			{
				if ($blocks[$i] == $id)
				{
					// change sort between two blocks
					$targetKey = $i + ($action == 'up' ? -1 : 1);
					if (isset($blocks[$targetKey]))
					{
						$thisBlock = $this->blocks[$id];
						$targetBlock = $this->blocks[$blocks[$targetKey]];
						$thisBlockSort = $thisBlock->getSort();
						$targetBlockSort = $targetBlock->getSort();

						$thisBlock->setSort($targetBlockSort);
						$targetBlock->setSort($thisBlockSort);
						$res1 = $thisBlock->save();
						$res2 = $targetBlock->save();

						$this->error->copyError($thisBlock->getError());
						$this->error->copyError($targetBlock->getError());

						if ($res1 || $res2)
						{
							$this->touch();
						}

						return $res1 && $res2;
					}
					else
					{
						$this->error->addError(
							'BLOCK_WRONG_SORT',
							Loc::getMessage('LANDING_BLOCK_WRONG_SORT')
						);
						return false;
					}
				}
			}
		}
		else
		{
			$this->error->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
		}

		return false;
	}

	/**
	 * Sort up the block on the landing.
	 * @param int $id Block id.
	 * @return boolean
	 */
	public function upBlock($id)
	{
		return $this->sortBlock($id, 'up');
	}

	/**
	 * Sort down the block on the landing.
	 * @param int $id Block id.
	 * @return boolean
	 */
	public function downBlock($id)
	{
		return $this->sortBlock($id, 'down');
	}

	/**
	 * Show/hide the block on the landing.
	 * @param int $id Block id.
	 * @param string $action Code: up or down.
	 * @return boolean
	 */
	protected function activateBlock($id, $action)
	{
		if (isset($this->blocks[$id]))
		{
			if ($this->blocks[$id]->setActive($action == 'show'))
			{
				if ($res = $this->blocks[$id]->save())
				{
					$this->touch();
				}
			}
			$this->error->copyError($this->blocks[$id]->getError());
			return $res;
		}
		else
		{
			$this->error->addError(
				'BLOCK_NOT_FOUND',
				Loc::getMessage('LANDING_BLOCK_NOT_FOUND')
			);
			return false;
		}
	}

	/**
	 * Activate the block on the landing.
	 * @param int $id Block id.
	 * @return boolean
	 */
	public function showBlock($id)
	{
		return $this->activateBlock($id, 'show');
	}

	/**
	 * Dectivate the block on the landing.
	 * @param int $id Block id.
	 * @return boolean
	 */
	public function hideBlock($id)
	{
		return $this->activateBlock($id, 'hide');
	}

	/**
	 * Copy/move other block to this landing.
	 * @param int $block Block id.
	 * @param array $params Params array.
	 * @return int New Block id.
	 */
	protected function changeParentOfBlock($block, $params)
	{
		$move = isset($params['MOVE']) && $params['MOVE'];
		$afterId = isset($params['AFTER_ID']) ? $params['AFTER_ID'] : 0;
		$fromLandingId = Block::getLandingIdByBlockId($block);
		$same = $this->id == $fromLandingId;

		if ($same)
		{
			$fromLanding = clone $this;
		}
		else
		{
			$fromLanding = self::createInstance($fromLandingId);
		}

		// if landing exist and available, get it blocks
		if ($this->exist() && $fromLanding->exist())
		{
			$fromLandingBlocks = $fromLanding->getBlocks();
			// if move, just change landing id
			if ($move)
			{
				$res = $fromLanding->transferBlock($block, $this->id);
				$this->error->copyError($fromLanding->getError());
				if ($res)
				{
					$newBlock = $fromLandingBlocks[$block];
				}
			}
			// else create copy
			else if (isset($fromLandingBlocks[$block]))
			{
				$srcBlock = $fromLandingBlocks[$block];
				$newBlock = Block::createFromRepository(
					$this,
					$srcBlock->getCode(),
					array(
						'ACTIVE' => $srcBlock->isActive() ? 'Y' : 'N',
						'ACCESS' => $srcBlock->getAccess(),
						'SORT' => $srcBlock->getSort(),
						'CONTENT' => $srcBlock->getContent(),
						'PUBLIC' => 'N'
				));
				// copy files
				if ($newBlock)
				{
					File::copyBlockFiles(
						$srcBlock->getId(),
						$newBlock->getId()
					);
				}
			}
			// add block to collection and resort
			if (isset($newBlock) && $newBlock)
			{
				if ($afterId > 0 && isset($this->blocks[$afterId]))
				{
					$targetBlock = $this->blocks[$afterId];
				}
				else
				{
					$targetBlock = array_pop(array_values($this->blocks));
				}
				if ($targetBlock)
				{
					$newBlock->setSort($targetBlock->getSort() + 1);
				}
				$this->addBlockToCollection($newBlock);
				$this->resortBlocks();
			}
			//change dates
			if ($this->error->isEmpty())
			{
				$this->touch();
				if ($move && !$same)
				{
					$fromLanding->touch();
				}
			}
		}

		$this->error->copyError($fromLanding->getError());

		return isset($newBlock) ? $newBlock->getId() : null;
	}

	/**
	 * Copy other block to this landing.
	 * @param int $id Block id (from another landing).
	 * @param int $afterId Put after this block id (in this landing).
	 * @return int New Block id.
	 */
	public function copyBlock($id, $afterId)
	{
		return $this->changeParentOfBlock($id, array(
			'MOVE' => false,
			'AFTER_ID' => $afterId
		));
	}

	/**
	 * Copy all blocks from another landing to this.
	 * @param int $lid Landing id.
	 * @return void
	 */
	public function copyAllBlocks($lid)
	{
		$landing = self::createInstance($lid);

		if ($this->exist() && $landing->exist())
		{
			$oldNew = array();
			// copy blocks
			foreach ($landing->getBlocks() as $block)
			{
				$newBlock = Block::createFromRepository(
					$this,
					$block->getCode(),
					array(
						'ACTIVE' => $block->isActive() ? 'Y' : 'N',
						'PUBLIC' => $block->isPublic() ? 'Y' : 'N',
						'ACCESS' => $block->getAccess(),
						'SORT' => $block->getSort(),
						'CONTENT' => $block->getContent()
					));
				if ($newBlock)
				{
					$oldNew[$block->getId()] = $newBlock;
					$this->addBlockToCollection($newBlock);
				}
			}
			// replace old id of blocks to the new one and clone files
			foreach ($this->getBlocks() as $block)
			{
				$content = $block->getContent();
				foreach ($oldNew as $oldId => $newBlock)
				{
					// clone files
					File::addToBlock(
						$newBlock->getId(),
						File::getFilesFromBlockContent($oldId, $content)
					);
					// replace ids
					$content = str_replace(
						'#block' . $oldId,
						'#block' . $newBlock->getId(),
						$content
					);
					$block->saveContent($content);
					$block->save();
				}
			}
			$this->touch();
		}

		$this->error->copyError($this->getError());
		$this->error->copyError($landing->getError());
	}

	/**
	 * Move other block to this landing.
	 * @param int $id Block id (from another landing).
	 * @param int $afterId Put after this block id (in this landing).
	 * @return int New Block id.
	 */
	public function moveBlock($id, $afterId)
	{
		return $this->changeParentOfBlock($id, array(
			'MOVE' => true,
			'AFTER_ID' => $afterId
		));
	}

	/**
	 * Update the landing.
	 * @param int $id Landing id.
	 * @param array $fields Fields.
	 * @return \Bitrix\Main\Result
	 */
	public static function update($id, $fields = array())
	{
		if (self::$enabledUpdate)
		{
			return parent::update($id, $fields);
		}
		else
		{
			return new \Bitrix\Main\Result;
		}
	}
}

Zerion Mini Shell 1.0