Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/landing/lib/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/landing/lib/landing.php |
<?php namespace Bitrix\Landing; use Bitrix\Landing\Internals\BlockTable; use \Bitrix\Main\Application; use Bitrix\Main\Config\Option; 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 { /** * Keys for get meta data from landing data */ protected const META_KEYS = [ 'CREATED_BY_ID', 'MODIFIED_BY_ID', 'DATE_CREATE', 'DATE_MODIFY', 'DATE_PUBLIC', 'INITIATOR_APP_CODE', 'VIEWS', 'TPL_CODE', 'ACTIVE', 'PUBLIC', 'SITE_CODE', 'SITE_SPECIAL', 'RULE', 'SITE_VERSION', 'SITE_LANG', 'SITE_TPL_CODE' ]; /** * Meta keys, than can be changed */ protected const META_KEYS_MODIFIABLE = [ 'DATE_MODIFY', 'DATE_PUBLIC', 'ACTIVE', 'PUBLIC' ]; /** * Internal class. * @var string */ public static $internalClass = 'LandingTable'; /** * Enabled updated or not. * @var bool */ protected static $enabledUpdate = true; /** * Enabled checking unique page address or not; * @var bool */ protected static $checkUniqueAddress = 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(); /** * Dynamic filter id. * @var int */ protected static $dynamicFilterId = 0; /** * Dynamic element id. * @var int */ protected static $dynamicElementId = 0; /** * Landing's site code. * @var string */ protected static $siteCode = ''; /** * 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 Block[] */ 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 = ''; /** * Some fields from landing's site. * @var array */ protected $siteRow = []; /** * Site id of current landing. * @var int */ protected $siteId = 0; /** * Site title of current landing. * @var string */ protected $siteTitle = ''; /** * Domain id. * @var int */ protected $domainId = 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 Error */ protected $error = null; /** * Current landing rights. * @var string[] */ protected $rights = []; /** * Checks permissions within current landing. * @var bool */ protected $checkPermissions = true; /** * Current version. * @var int */ protected $version = 1; /** * Disable /preview for link in replace method. * @var bool */ protected $disableLinkPreview = false; /** * Constructor. * @param int $id Landing id. * @param array $params Some params. */ protected function __construct($id, $params = array()) { $id = intval($id); $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']; } if ( isset($params['check_permissions']) && $params['check_permissions'] === false ) { $filter['CHECK_PERMISSIONS'] = 'N'; $this->checkPermissions = false; } if ( isset($params['disable_link_preview']) && $params['disable_link_preview'] === true ) { $this->disableLinkPreview = true; } if ($id) { $landing = self::getList(array( 'select' => array( '*', 'SITE_TPL_ID' => 'SITE.TPL_ID', 'SITE_TPL_CODE' => 'SITE.TPL_CODE', 'SITE_CODE' => 'SITE.CODE', 'SITE_TYPE' => 'SITE.TYPE', 'SITE_SPECIAL' => 'SITE.SPECIAL', 'SITE_TITLE' => 'SITE.TITLE', 'SITE_VERSION' => 'SITE.VERSION', 'SITE_LANG' => 'SITE.LANG', 'DOMAIN_ID' => 'SITE.DOMAIN_ID', 'SITE_LANDING_ID_INDEX' => 'SITE.LANDING_ID_INDEX' ), 'filter' => $filter ))->fetch(); } // check landing folder if exists if (!empty($landing['FOLDER_ID'])) { $breadCrumbs = Folder::getBreadCrumbs($landing['FOLDER_ID'], $landing['SITE_ID']); foreach ($breadCrumbs as $crumb) { if ($crumb['DELETED'] === 'Y') { $id = 0; break; } } } if ($id && isset($landing) && is_array($landing)) { /* * $this->getEditMode() * @todo return if no access */ // get base data self::$siteCode = $landing['SITE_TYPE']; $this->title = $landing['TITLE']; $this->code = $landing['CODE']; $this->xmlId = $landing['XML_ID']; $this->id = (int)$landing['ID']; $this->version = (int)$landing['VERSION']; $this->siteId = (int)$landing['SITE_ID']; $this->siteTitle = $landing['SITE_TITLE']; $this->domainId = (int)$landing['DOMAIN_ID']; $this->folderId = (int)$landing['FOLDER_ID']; $this->active = $landing['ACTIVE'] == 'Y'; if ($this->checkPermissions) { $this->rights = Rights::getOperationsForSite( $this->siteId ); } $this->siteRow = [ 'TPL_ID' => $landing['SITE_TPL_ID'], 'LANDING_ID_INDEX' => $landing['SITE_LANDING_ID_INDEX'] ]; $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()/* && $landing['PUBLIC'] == 'Y'*/) { Block::cloneForEdit($this); } // some update if we need $this->updateVersion(); // get available blocks if ( !isset($params['skip_blocks']) || $params['skip_blocks'] !== true ) { Block::fillLanding( $this, $params['blocks_limit'] ?? null, [ 'id' => $params['blocks_id'] ?? 0, 'deleted' => isset($params['deleted']) && $params['deleted'] === true ] ); } // fill meta data foreach (self::META_KEYS as $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'); } } /** * Can refresh value of modifiable meta data * @param array $metaData * @return void */ public function setMetaData(array $metaData): void { foreach ($metaData as $key => $value) { if (in_array($key, self::META_KEYS_MODIFIABLE, true)) { $this->metaData[$key] = $value; } } } /** * Return true if landing exists and available. * @param int $id Landing id. * @param bool $deleted And from recycle bin. * @return bool */ public static function ping($id, $deleted = false) { $returnCheckDelete = false; $filter = [ 'ID' => $id ]; if ($deleted) { if (self::$checkDelete) { $returnCheckDelete = true; self::$checkDelete = false; } $filter['=DELETED'] = ['Y', 'N']; } $check = self::getList([ 'select' => [ 'ID' ], 'filter' => $filter ]); if ($returnCheckDelete) { self::$checkDelete = true; } return (boolean) $check->fetch(); } /** * 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; } /** * Checking or not unique address. * @return bool */ public static function isCheckUniqueAddress(): bool { return self::$checkUniqueAddress; } /** * Disable checking unique address. * @return void */ public static function disableCheckUniqueAddress(): void { self::$checkUniqueAddress = false; } /** * Enable checking unique address * @return void */ public static function enableCheckUniqueAddress(): void { self::$checkUniqueAddress = 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 landing as deleted. * @param int $id Landing id. * @return \Bitrix\Main\Result */ public static function markDelete(int $id): \Bitrix\Main\Result { if (TemplateRef::landingIsArea($id)) { $result = new \Bitrix\Main\Result; $result->addError( new \Bitrix\Main\Error( Loc::getMessage('LANDING_BLOCK_UNABLE_DEL_INC'), 'UNABLE_DELETE_INCLUDE' ) ); return $result; } $event = new Event('landing', 'onBeforeLandingRecycle', array( 'id' => $id, 'delete' => 'Y' )); $event->send(); foreach ($event->getResults() as $result) { if ($result->getType() == EventResult::ERROR) { $return = new \Bitrix\Main\Result; foreach ($result->getErrors() as $error) { $return->addError( $error ); } return $return; } } if (($currentScope = Site\Type::getCurrentScopeId())) { Agent::addUniqueAgent('clearRecycleScope', [$currentScope]); } $landing = self::createInstance($id, ['skip_blocks' => true]); $res = parent::update($id, array( 'DELETED' => 'Y' )); if ($res->isSuccess()) { $landing->clearFolderIndex(); } return $res; } /** * Mark entity as restored. * @param int $id Entity id. * @return \Bitrix\Main\Result */ public static function markUnDelete($id) { $id = intval($id); $event = new Event('landing', 'onBeforeLandingRecycle', array( 'id' => $id, 'delete' => 'N' )); $event->send(); foreach ($event->getResults() as $result) { if ($result->getType() == EventResult::ERROR) { $return = new \Bitrix\Main\Result; foreach ($result->getErrors() as $error) { $return->addError( $error ); } return $return; } } 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, bool $forceDeleted = false): \Bitrix\Main\Result { $result = new \Bitrix\Main\Entity\DeleteResult(); $params = []; if ($forceDeleted) { $params['force_deleted'] = true; } // first check if (Rights::isOn()) { foreach (['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 { if (!$landing->getError()->isEmpty()) { $result->addError( $landing->getError()->getErrors()[0] ); } return $result; } } } // delete blocks $params['skip_blocks'] = true; foreach (array('draft', 'public') as $code) { self::setEditMode($code == 'draft'); $landing = self::createInstance($id, $params); if ($landing->exist()) { Block::deleteAll($id); File::deleteFromLanding($id); File::deleteFromAsset($id); } } $res = parent::delete($id); if ($res->isSuccess()) { $landing->clearFolderIndex(); } return $res; } /** * Get hooks of Landing. * @param int $id Landing id. * @return array Array of Hook. */ public static function getHooks($id) { if (!Rights::hasAccessForLanding($id, Rights::ACCESS_TYPES['read'])) { return []; } 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; } /** * Returns additional fields of Landing as array. * @param int $landingId Landing id. * @param bool $skipEmpty Skip emty values. * @return array Array of Field. */ public static function getAdditionalFieldsAsArray(int $landingId, bool $skipEmpty = true): array { $hookFiles = Hook::HOOKS_CODES_FILES; $fields = self::getAdditionalFields($landingId); foreach ($fields as $key => $field) { $fields[$key] = $field->getValue(); if ($skipEmpty && !$fields[$key]) { unset($fields[$key]); continue; } if (in_array($key, $hookFiles)) { $fields['~' . $key] = $fields[$key]; if (intval($fields[$key]) > 0) { $fields[$key] = File::getFilePath($fields[$key]); } } } 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; } /** * Set dynamic params (filter id and dynamic element id). * @param int $filterId Id of filter. * @param int $elementId Id of dynamic element id. * @return void */ public static function setDynamicParams($filterId, $elementId) { self::$dynamicFilterId = $filterId; self::$dynamicElementId = $elementId; } /** * Get dynamic filter. * @return array */ public static function getDynamicFilter() { static $filter = null; if ($filter === null) { $filter = Source\FilterEntity::getFilter( self::$dynamicFilterId ); } return $filter; } /** * Get dynamic element id. * @return int */ public static function getDynamicElementId() { return self::$dynamicElementId; } /** * Return true, if current page is dynamic detail page. * @return bool */ public static function isDynamicDetailPage() { return self::$dynamicFilterId && self::$dynamicElementId; } /** * Get preview picture of the landing. * @param int|null $id Landing id (if null, gets for $this->id). * @param bool $skipCloud Skip getting picture from cloud. * @param string|null $publicUrl Full public url of site (you may set this param for optimisation). * @return string|null */ public function getPreview(?int $id = null, bool $skipCloud = false, ?string $publicUrl = null): ?string { if ( !$skipCloud && Manager::isB24() && !Manager::isCloudDisable() ) { if (!$publicUrl) { $publicUrl = $this->getPublicUrl(); } return rtrim($publicUrl, '/') . '/preview.jpg'; } static $hookPics = null; if ($hookPics === null) { $hookPics = Hook\Page\MetaOg::getAllImages(); } if (!$id) { $id = $this->id; } if (isset($hookPics[$id])) { $pic = $hookPics[$id]; if (intval($pic) > 0) { $pic = File::getFilePath($pic); } return $pic; } return Manager::getUrlFromFile('/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). * @param array &$fullUrl Returns full url of landings. * @return string|array */ public function getPublicUrl($id = false, $absolute = true, $createPubPath = false, &$fullUrl = []) { if ($id === false) { $id = $this->id; } $previewMode = self::$previewMode && !$this->disableLinkPreview; $siteKeyCode = Site\Type::getKeyCode(); $hostUrl = Domain::getHostUrl(); $siteId = Manager::getMainSiteId(); $bitrix24 = Manager::isB24(); $bitrix24originalVar = $bitrix24; $disableCloud = Manager::isCloudDisable(); $domainDefault = null; $data = array(); $res = Landing::getList(array( 'select' => array( 'ID', 'CODE', 'RULE', 'SITE_ID', 'FOLDER_ID', 'SITE_ID_INDEX' => 'SITE.LANDING_ID_INDEX', 'SITE_PROTOCOL' => 'SITE.DOMAIN.PROTOCOL', 'SITE_DOMAIN' => 'SITE.DOMAIN.DOMAIN', 'SITE_CODE' => 'SITE.CODE', 'SITE_TYPE' => 'SITE.TYPE', 'SITE_SMN_ID' => 'SITE.SMN_SITE_ID' ), 'filter' => array( 'ID' => $id, '=DELETED' => ['Y', 'N'], 'CHECK_PERMISSIONS' => 'N' ) )); while ($row = $res->fetch()) { if ($row['SITE_TYPE'] == 'SMN') { $bitrix24 = false; } else { $bitrix24 = $bitrix24originalVar; } $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'] : $siteId, $createPubPath ); $pubPath = rtrim($pubPath, '/'); } // for create publication path if (!ModuleManager::isModuleInstalled('bitrix24')) { Manager::getPublicationPath( null, $row['SITE_SMN_ID'] ? $row['SITE_SMN_ID'] : $siteId, $createPubPath ); } if (isset($row['SITE_ID'])) { if ($siteKeyCode == 'CODE') { $row['SITE_ID'] = $row['SITE_CODE']; } else { $row['SITE_ID'] = '/' . $row['SITE_ID'] . '/'; } } $publicHash = ''; if ($previewMode) { if ($siteKeyCode == 'CODE') { $publicHash = Site::getPublicHash(trim($row['SITE_CODE'], '/'), $row['SITE_DOMAIN']); } else { $publicHash = Site::getPublicHash($row['SITE_ID_ORIG'], $row['SITE_DOMAIN']); } } if ($row['CODE']) { $row['CODE'] .= '/'; } if ($disableCloud) { $lastFolderItem = []; $fullUrl[$row['ID']] = ($absolute ? $hostUrl : '') . $pubPath . ($bitrix24 ? $row['SITE_ID'] : '/') . ($previewMode ? 'preview/' . $publicHash . '/' : '') . ($row['FOLDER_ID'] ? ltrim(Folder::getFullPath($row['FOLDER_ID'], $row['SITE_ID_ORIG'], $lastFolderItem), '/') : ''); $folderIndex = $row['ID'] == ($lastFolderItem['INDEX_ID'] ?? 0) || !($lastFolderItem['INDEX_ID'] ?? 0) && ($row['CODE'] ?? null) && ($lastFolderItem['CODE'] ?? null) && trim($row['CODE'], '/') === $lastFolderItem['CODE'] ; $data[$row['ID']] = $fullUrl[$row['ID']] . (($row['ID'] == $row['SITE_ID_INDEX'] || $folderIndex || $row['RULE']) ? '' : $row['CODE']); if (!$row['RULE']) { $fullUrl[$row['ID']] .= $row['CODE']; } } else { $lastFolderItem = []; $fullUrl[$row['ID']] = ( $absolute ? ( $row['SITE_PROTOCOL'] . '://' . $row['SITE_DOMAIN'] ) : '' ) . (($domainReplace || !$bitrix24) ? $pubPath : '') . (($previewMode && !$bitrix24) ? '/preview/' . $publicHash : '') . (($domainReplace && $bitrix24) ? $row['SITE_ID'] : '/') . (($previewMode && $bitrix24) ? 'preview/' . $publicHash . '/' : '') . ($row['FOLDER_ID'] ? ltrim(Folder::getFullPath($row['FOLDER_ID'], $row['SITE_ID_ORIG'], $lastFolderItem), '/') : ''); $folderIndex = $row['ID'] == $lastFolderItem['INDEX_ID'] || !$lastFolderItem['INDEX_ID'] && trim($row['CODE'], '/') === $lastFolderItem['CODE']; $data[$row['ID']] = $fullUrl[$row['ID']] . (($row['ID'] == $row['SITE_ID_INDEX'] || $folderIndex || $row['RULE']) ? '' : $row['CODE']); if (!$row['RULE']) { $fullUrl[$row['ID']] .= $row['CODE']; } } } if (is_array($id)) { return $data; } elseif (!empty($data)) { return array_pop($data); } return false; } /** * Returns landing id resolves by landing public url. * @param string $landingUrl Landing public url. * @param int $siteId Landing's site id. * @return int|null */ public static function resolveIdByPublicUrl(string $landingUrl, int $siteId): ?int { if (Manager::isB24() && !Manager::isCloudDisable() && Site\Type::isPublicScope()) { $landingUrl = rtrim(Manager::getPublicationPath($siteId), '/') . '/' . ltrim($landingUrl, '/'); } $publicationPath = Manager::getPublicationPath(); $componentName = 'bitrix:landing.pub'; $className = \CBitrixComponent::includeComponentClass($componentName); $demoCmp = new $className; $demoCmp->initComponent($componentName); $demoCmp->arParams = [ 'PATH' => mb_substr($landingUrl, mb_strlen($publicationPath)), 'DRAFT_MODE' => 'Y', 'SITE_ID' => $siteId, 'SITE_TYPE' => self::getSiteType(), 'CHECK_PERMISSIONS' => 'N', 'NOT_CHECK_DOMAIN' => 'Y', 'NOT_SEND_HTTP_STATUS' => 'Y', 'SKIP_404' => 'Y' ]; return $demoCmp->detectPage() ?: null; } /** * 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; } if (!isset($params['check_permissions'])) { $params['check_permissions'] = true; } if (!$params['check_permissions']) { Rights::setOff(); } // title if ($this->mainInstance) { Manager::setPageTitle( \htmlspecialcharsbx($this->title) ); } // add chain item if need if ($this->mainInstance) { if ($this->folderId) { $chains = Folder::getBreadCrumbs($this->folderId, $this->siteId); foreach ($chains as $chain) { Manager::getApplication()->addChainItem( $chain['TITLE'], $chain['INDEX_ID'] ? '#landing' . $chain['INDEX_ID'] : '#' ); } } else if (($this->siteRow['LANDING_ID_INDEX'] ?? 0) != $this->id) { Manager::getApplication()->addChainItem( $this->title, '#landing' . $this->id ); } } // assets if ($editMode) { $options = array( 'site_id' => $this->siteId, 'server_name' => $_SERVER['SERVER_NAME'], 'xml_id' => $this->xmlId, 'blocks' => Block::getRepository(), 'style' => Block::getStyle(), 'attrs' => Block::getAttrs(), 'mainOptions' => [ 'saveOriginalFileName' => Option::get('main', 'save_original_file_name') === 'Y' ], ); // 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']) && is_array($modified['options'])) { $options = array_merge($options, $modified['options']); } } } } // output js Asset::getInstance()->addString( '<script>' . 'BX.ready(function(){' . 'if (typeof BX.Landing.Main !== "undefined")' . '{' . 'BX.Landing.Env.createInstance(' . \CUtil::phpToJSObject($options, false, false, true) . ');' . 'BX.Landing.Main.createInstance(' . $this->id . ');' . '}' . '});' . '</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(); Landing\Seo::beforeLandingView(); foreach ($this->blocks as $block) { $block->view($blockEditMode, $this); } Landing\Seo::afterLandingView(); if ($this->mainInstance) { $this->execHooks(); } $contentMain = ob_get_contents(); ob_end_clean(); $replace = []; if (!$content) { $content = $contentMain; } if (mb_strpos($content, '#CONTENT#') !== false) { $replace['#CONTENT#'] = '<a id="workarea"></a>' . $contentMain; } if (mb_strpos($content . $contentMain, '#crm') !== false) { $replace = array_merge($replace, Connector\Crm::getReplacesForContent($this->siteId, !$blockEditMode)); } if ($replace) { $content = str_replace( array_keys($replace), array_values($replace), $content ); } // 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; } if (!$params['check_permissions']) { Rights::setOn(); } } /** * 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, 'check_permissions' => false, 'disable_link_preview' => $this->disableLinkPreview )); if ($landing->exist()) { $landing->view(); } if ($editMode) { $rights = Rights::getOperationsForSite($landing->getSiteId()); $replace['>#AREA_' . $area . '#<'] = ' data-site="' . $landing->getSiteId() . '" data-landing="' . $lid . '" data-rights="' . implode(',', $rights) . '">#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(string $content): string { $pattern = '/([",\'\;]{1})(page:|block:|user:)?#(landing|block|dynamic|user)([\d\_]+)\@{0,1}([^\'"]*)([",\'\&]{1})/is'; $patternWithoutUser = '/([",\'\;]{1})(page:|block:)?#(landing|block|dynamic)([\d\_]+)\@{0,1}([^\'"]*)([",\'\&]{1})/is'; static $isIframe = null; if (!self::$editMode && $content) { $content = Subtype\Form::prepareFormsToView( $content ); } if ($isIframe === null) { $request = Application::getInstance()->getContext()->getRequest(); $isIframe = $request->get('IFRAME') == 'Y'; } // replace catalog links in preview mode if (self::$previewMode) { $content = preg_replace_callback( '/href\="(product:)?#catalog(Element|Section)([\d]+)"/i', function($href) { return 'href="' . PublicAction\Utils::getIblockURL( $href[3], mb_strtolower($href[2]) ) . '"'; }, $content); } $replace = []; // for form in frames we should insert hidden tag if ($isIframe) { $replace['</form>'] = '<input type="hidden" name="IFRAME" value="Y" /></form>'; } // fix breadcrumb navigation if ($this->siteRow['LANDING_ID_INDEX'] > 0) { $replace['#system_mainpage'] = '#landing' . $this->siteRow['LANDING_ID_INDEX']; } if ($replace) { $content = str_replace( array_keys($replace), array_values($replace), $content ); } // replace in content if (preg_match_all($pattern, $content, $matches)) { $urls = array( 'LANDING' => array(), 'BLOCK' => array(), 'USER' => array(), 'DYNAMIC' => array() ); for ($i = 0, $c = count($matches[0]); $i < $c; $i++) { if (mb_strtoupper($matches[3][$i]) == 'LANDING') { $urls['LANDING'][] = $matches[4][$i]; } else if (mb_strtoupper($matches[3][$i]) == 'DYNAMIC') { [$dynamicId, ] = explode('_', $matches[4][$i]); $urls['DYNAMIC'][] = $dynamicId; } else if (mb_strtoupper($matches[3][$i]) == 'USER') { $urls['USER'][] = $matches[4][$i]; } else { $urls['BLOCK'][] = $matches[4][$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'] )); } $urls['LANDING'] = array_unique(array_merge( $urls['LANDING'], $urls['DYNAMIC'] )); // 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; $landingFull = []; $lidEncoded = []; // get landing and blocks urls if (!empty($urls['LANDING'])) { $urls['LANDING'] = $this->getPublicUrl( $urls['LANDING'], !Connector\Mobile::isMobileHit(), false, $landingFull ); foreach ($urls['LANDING'] as $lid => &$url) { $lidEncoded[] = $lid; $url = \htmlspecialcharsbx($url); if ($isIframe) { $url .= '?IFRAME=Y'; } } unset($url); } if (!empty($urls['BLOCK'])) { foreach ($urls['BLOCK'] as $bid => $lid) { if (isset($urls['LANDING'][$lid])) { if (!in_array($lid, $lidEncoded)) { $urls['LANDING'][$lid] = \htmlspecialcharsbx($urls['LANDING'][$lid]); } $urls['LANDING'][$lid] .= ($isIframe ? '?IFRAME=Y' : ''); $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( $patternWithoutUser, function($matches) use($urls, $landingFull, $isIframe) { $dynamicPart = ''; $matches[3] = mb_strtoupper($matches[3]); if ($matches[3] == 'DYNAMIC') { $matches[3] = 'LANDING'; if (($underPos = mb_strpos($matches[4], '_')) !== false) { $dynamicPart = mb_substr($matches[4], $underPos); $matches[4] = mb_substr($matches[4], 0, $underPos); } [$dynamicId, ] = explode('_', $matches[4]); $matches[4] = $dynamicId; } if (isset($urls[$matches[3]][$matches[4]])) { if ($dynamicPart) { $landingUrl = $urls[$matches[3]][$matches[4]]; if (isset($landingFull[$matches[4]])) { $landingUrl = $landingFull[$matches[4]]; } $url = mb_substr($landingUrl, 0, mb_strlen($landingUrl) - 1); $url .= $dynamicPart . ($isIframe ? '/?IFRAME=Y' : '/'); } else { $url = $urls[$matches[3]][$matches[4]]; } return $matches[1] . $url . $matches[5] . $matches[6]; } else { return $matches[1] . '#landing' . $matches[4] . $matches[5] . $dynamicPart . $matches[6]; } }, $content ); $landingUrls = array(); foreach ($urls['LANDING'] as $lid => $url) { $landingUrls['@#landing' . $lid.'@'] = $url; } } // replace user urls if (!empty($urls['USER'])) { $patternForPseudoUrlUser = '/data-pseudo-url="{\S*(user:)?#user([\d\_]+)\S*}"/is'; $content = preg_replace_callback( $patternForPseudoUrlUser, function($matches) { $url = "'" . Domain::getHostUrl() . '/company/personal/user/' . $matches[2] . "/'"; return 'onClick="BX.SidePanel.Instance.open('. $url . ')" '; }, $content ); $patternForUser = '/(user:)?#(user)([\d\_]+)/is'; $content = preg_replace_callback( $patternForUser, function($matches) { return Domain::getHostUrl() . '/company/personal/user/' . $matches[3] . '/'; }, $content ); } } 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()) { $hook = $this->prepareHook($hook); $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(); } } } protected function prepareHook($hook) { if ($hook->getCode() === 'GMAP') { $hook->setSiteId($this->siteId); } return $hook; } /** * 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; } /** * Returns code of current landing. * @return string */ public function getCode(): string { return $this->code; } /** * Get metadata of current landing. * @return array */ public function getMeta() { return $this->metaData; } /** * Can current user edit this landing. * @return bool */ public function canEdit() { if (!$this->checkPermissions) { return true; } return in_array(Rights::ACCESS_TYPES['edit'], $this->rights); } /** * Can current user publication this landing. * @return bool */ public function canPublication() { if (!$this->checkPermissions) { return true; } return in_array(Rights::ACCESS_TYPES['public'], $this->rights); } /** * Can current user delete this landing. * @return bool */ public function canDelete() { if (!$this->checkPermissions) { return true; } return in_array(Rights::ACCESS_TYPES['delete'], $this->rights); } /** * Gets folder id of current landing. * @return int */ public function getFolderId() { return $this->folderId; } /** * Get site id of current landing. * @return int */ public function getSiteId() { return $this->siteId; } /** * Get site title of current landing. * @return string */ public function getSiteTitle() { return $this->siteTitle; } /** * Gets domain id of landing site. * @return int */ public function getDomainId() { return $this->domainId; } /** * Get site id of current landing. * @return int */ public static function getSiteType() { return self::$siteCode; } /** * Get site id of main, if exist. * @return string|null */ public function getSmnSiteId(): ?string { 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']; } } return null; } /** * Get all blocks of current landing. * @return Block[] */ public function getBlocks() { return $this->blocks; } /** * Get the block by id of current landing. * @param int $id Block id. * @return Block */ public function getBlockById($id) { $id = intval($id); return isset($this->blocks[$id]) ? $this->blocks[$id] : null; } /** * 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; } /** * Returns Error instance as Main\Error instance. * @return \Bitrix\Main\Error|null */ public function getErrorMain(): ?\Bitrix\Main\Error { if ($this->error) { $error = $this->error->getFirstError(); return new \Bitrix\Main\Error( $error->getMessage(), $error->getCode() ); } return null; } /** * Change modified user and date for current landing. * @return void */ public function touch() { static $touched = []; if (isset($touched[$this->id])) { return; } $touched[$this->id] = true; if (self::update($this->id, ['PUBLIC' => 'N'])->isSuccess()) { Site::touch($this->siteId); } } /** * If current version is not actual, making update. * @return void */ public function updateVersion() { $needUpdate = false; // double hooks: public and draft if ($this->version <= 1) { $needUpdate = true; $this->version = 2; $hookEditMode = Hook::getEditMode(); if (!$hookEditMode) { Hook::setEditMode(true); } Hook::publicationSite($this->siteId); Hook::publicationLanding($this->id); if (!$hookEditMode) { Hook::setEditMode(false); } } // block assets if ($this->version <= 2) { $needUpdate = true; $this->version = 3; Assets\PreProcessing\Icon::processingLanding($this->id); Assets\Manager::rebuildWebpackForLanding($this->id); } if ($this->version <= 3) { $needUpdate = true; $this->version = 4; Assets\PreProcessing\Font::processingLanding($this->id); } if ($this->version <= 4) { $needUpdate = true; $this->version = 5; Hook\Page\ThemeFonts::migrateFromTypoThemes($this->id, $this->siteId); } if ($this->version <= 5) { // fix 126641 $needUpdate = true; $this->version = 6; Assets\PreProcessing\Icon::processingLanding($this->id); Assets\Manager::rebuildWebpackForLanding($this->id); } if ($this->version <= 6) { $needUpdate = true; Update\Block\Buttons::updateLanding($this->id); Update\Block\FontWeight::updateLanding($this->id); $this->version = 7; } if ($this->version <= 7) { $needUpdate = true; Assets\PreProcessing\Icon::processingLanding($this->id); Assets\Manager::rebuildWebpackForLanding($this->id); $this->version = 8; } if ($this->version <= 9) { // +1 version for reupdate in hotfix $needUpdate = true; Subtype\Form::updateLandingToEmbedForms($this->id); Assets\Manager::rebuildWebpackForLanding($this->id); $this->version = 10; } if ($this->version <= 10) { $needUpdate = true; Update\Block\DuplicateImages::updateLanding($this->id); $this->version = 11; } if ($needUpdate) { Rights::setOff(); self::update($this->id, [ 'VERSION' => $this->version, 'DATE_MODIFY' => false, 'MODIFIED_BY_ID' => false ]); Rights::setOn(); } } /** * Publication current landing. * @param int|int[]|null $blockId Publication only this block(s). * @return boolean */ public function publication($blockId = null): bool { if ($this->canPublication()) { return Mutator::landingPublication($this, $blockId); } return false; } /** * Fake current landing (for testing potential publication and resolves the errors). * @return boolean */ public function fakePublication(): bool { if ($this->canPublication()) { return Mutator::landingPublication($this, null, true); } return false; } /** * Cancel publication of landing. * @return boolean */ public function unpublic() { static $siteUpdated = []; $res = parent::update($this->id, array( 'ACTIVE' => 'N', 'PUBLIC' => 'N', 'DATE_MODIFY' => false )); if ($res->isSuccess()) { if ( !in_array($this->siteId, $siteUpdated) && Manager::isB24() ) { $siteUpdated[] = $this->siteId; Site::update($this->siteId, array()); } // send event $event = new Event('landing', 'onLandingAfterUnPublication', array( 'id' => $this->getId() )); $event->send(); 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. * @param bool $saveInLastUsed Save this block as last used for current user. * @return int|false Id of new block or false on failure. */ public function addBlock(string $code, array $data = array(), bool $saveInLastUsed = false) { if (!$this->canEdit()) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } if (!isset($data['PUBLIC'])) { $data['PUBLIC'] = $this::$editMode ? 'N' : 'Y'; } $block = Block::createFromRepository($this, $code, $data); if ($block) { if ($saveInLastUsed) { Block::markAsUsed($code); } $this->touch(); $this->addBlockToCollection($block); if (History::isActive()) { $history = new History($this->id, History::ENTITY_TYPE_LANDING); $history->push('ADD_BLOCK', ['block' => $block]); } return $block->getId(); } return false; } /** * Delete one block from current landing. * @param int $id Block id. * @return boolean */ public function deleteBlock($id) { $id = intval($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) { $id = intval($id); if (!isset($this->blocks[$id])) { $this->blocks[$id] = new Block($id); } if ( isset($this->blocks[$id]) && $this->blocks[$id]->exist() && $this->blocks[$id]->getLandingId() == $this->getId() ) { if ($this->blocks[$id]->getAccess() >= $this->blocks[$id]::ACCESS_X) { $this->blocks[$id]->markDeleted($mark); if (!$mark) { Assets\PreProcessing::blockUndeleteProcessing( $this->blocks[$id] ); } if ($this->blocks[$id]->save()) { if ($mark) { if (History::isActive()) { $history = new History($this->id, History::ENTITY_TYPE_LANDING); $history->push('REMOVE_BLOCK', ['block' => $this->blocks[$id]]); } 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; } 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) { $id = intval($id); 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(int $id, string $action): bool { $id = intval($id); 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; } $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(int $id): bool { if ($this->sortBlock($id, 'up')) { if (History::isActive()) { $history = new History($this->id, History::ENTITY_TYPE_LANDING); $history->push('SORT_BLOCK', [ 'block' => $id, 'lid' => $this->getId(), 'up' => true, ]); } return true; } return false; } /** * Sort down the block on the landing. * @param int $id Block id. * @return boolean */ public function downBlock(int $id): bool { if ($this->sortBlock($id, 'down')) { if (History::isActive()) { $history = new History($this->id, History::ENTITY_TYPE_LANDING); $history->push('SORT_BLOCK', [ 'block' => $id, 'lid' => $this->getId(), 'up' => false, ]); } return true; } return false; } /** * 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) { $id = intval($id); 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'); } /** * Saves the block in favorites. * @param int $id Block id. * @param array $meta Meta info. * @return int|null New block id. */ public function favoriteBlock(int $id, array $meta = []): ?int { $bewBlockId = $this->copyBlock($id, $id); if ($bewBlockId > 0) { if ( $this->blocks[$bewBlockId]->changeLanding(0) && $this->blocks[$bewBlockId]->changeFavoriteMeta($meta) ) { Block::markAsUsed($this->blocks[$bewBlockId]->getCode() . '@' . $bewBlockId); \Bitrix\Landing\Block::clearRepositoryCache(); if ($meta['preview'] ?? null) { File::deleteFromBlock($id, $meta['preview']); } } else { $this->error->copyError($this->blocks[$bewBlockId]->getError()); } return $bewBlockId; } return null; } /** * Removes the block from favorites. * @param int $blockId Block id. * @return bool */ public function unFavoriteBlock(int $blockId): bool { $block = new Block($blockId); if (!$block || empty($block->getMeta()['FAVORITE_META'])) { $this->error->addError( 'BLOCK_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_NOT_FOUND') ); return false; } if ( $block->getAccess() < Block::ACCESS_X || (int)$block->getMeta()['CREATED_BY_ID'] !== Manager::getUserId() ) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } if (BlockTable::delete($blockId)->isSuccess()) { File::deleteFromBlock($blockId); Block::removeAsUsed($block->getCode() . '@' . $block->getId()); Block::clearRepositoryCache(); return true; } return false; } /** * 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) { $block = intval($block); $move = isset($params['MOVE']) && $params['MOVE']; $afterId = isset($params['AFTER_ID']) ? (int)$params['AFTER_ID'] : 0; $fromLandingRow = Block::getRowByBlockId($block, ['ID', 'LID', 'SITE_TYPE' => 'LANDING.SITE.TYPE']); $fromLandingId = $fromLandingRow['LID'] ?? null; $currentScopeId = Site\Type::getCurrentScopeId(); $same = $this->id == $fromLandingId; if ($currentScopeId !== $fromLandingRow['SITE_TYPE']) { Site\Type::setScope($fromLandingRow['SITE_TYPE']); } 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', 'DESIGNED' => $srcBlock->isDesigned() ? 'Y' : 'N', 'ACCESS' => $srcBlock->getAccess(), 'SORT' => $srcBlock->getSort(), 'CONTENT' => $srcBlock->getContent(), 'SOURCE_PARAMS' => $srcBlock->getDynamicParams(), 'PUBLIC' => 'N' )); // we should save original content after all callbacks $newBlock->saveContent( $srcBlock->getContent() ); $newBlock->save(); // 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 { $blocksTmp = array_values($this->blocks); $targetBlock = array_pop($blocksTmp); } if ($targetBlock) { $newBlock->setSort($targetBlock->getSort() + 1); } $this->addBlockToCollection($newBlock); $this->resortBlocks(); // search index $newBlock->save(); } //change dates if ($this->error->isEmpty()) { if ($move && !$same) { $fromLanding->touch(); } } } $this->error->copyError($fromLanding->getError()); if ($currentScopeId !== $fromLandingRow['SITE_TYPE']) { Site\Type::setScope($currentScopeId); } if ($this->error->isEmpty()) { $this->touch(); } 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) { $blockId = $this->changeParentOfBlock($id, array( 'MOVE' => false, 'AFTER_ID' => $afterId )); if (!$blockId) { $this->error->addError( 'BLOCK_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_NOT_FOUND') ); } return $blockId; } /** * Copy all blocks from another landing to this. * @param int $lid Landing id. * @param boolean $replaceLinks Replace links to the old blocks. * @param array &$references Array that will contain references between old and new IDs. * @return void */ public function copyAllBlocks($lid, $replaceLinks = true, array &$references = []) { $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', 'DESIGNED' => $block->isDesigned() ? 'Y' : 'N', 'PUBLIC' => $block->isPublic() ? 'Y' : 'N', 'ACCESS' => $block->getAccess(), 'SORT' => $block->getSort(), 'CONTENT' => $block->getContent(), 'SOURCE_PARAMS' => $block->getDynamicParams() )); if ($newBlock) { $oldNew[$block->getId()] = $newBlock; $references[$block->getId()] = $newBlock->getId(); $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 if ($replaceLinks) { $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()); } /** * If current landing is index page of folder, clear folder's index. * @return void */ public function clearFolderIndex(): void { if ($this->folderId) { $resFolder = Folder::getList([ 'select' => [ 'ID' ], 'filter' => [ 'ID' => $this->folderId, 'INDEX_ID' => $this->id ] ]); if ($resFolder->fetch()) { Folder::update($this->folderId, [ 'INDEX_ID' => null ]); } } } /** * System method for checking ability to publication page after copy/move. * * @return bool */ private function canPublicAfterCopy(): bool { $siteId = $this->getSiteId(); $folderId = $this->getFolderId(); // if permissions enough if (!$this->canPublication()) { return false; } // in root, we can if (!$folderId) { return true; } // check all folders above the page $crumbs = Folder::getBreadCrumbs($folderId, $siteId); foreach ($crumbs as $crumb) { // if folder is active, we don't care about if ($crumb['ACTIVE'] === 'Y') { continue; } // check active pages in each folder above $res = self::getList([ 'select' => [ 'ID' ], 'filter' => [ '=ACTIVE' => 'Y', 'FOLDER_ID' => $crumb['ID'], ], 'limit' => 1 ]); if ($res->fetch()) { // if such folder exists we cant public any folder return false; } // check active folders in folders above $res = Folder::getList([ 'select' => [ 'ID' ], 'filter' => [ '=ACTIVE' => 'Y', 'PARENT_ID' => $crumb['ID'], ], 'limit' => 1 ]); if ($res->fetch()) { // if such folder exists we cant public any folder return false; } } // all folders are active or not exists active pages return true; } /** * Move current page to site/folder. * @param int|null $toSiteId Destination site id (if you want copy in another site). * @param int|null $toFolderId Destination folder id. * @return bool */ public function move(?int $toSiteId = null, ?int $toFolderId = null): bool { if (!$this->exist()) { return false; } if (!$toSiteId) { $toSiteId = $this->getSiteId(); } $rightsSite = Rights::getOperationsForSite($toSiteId); if (!in_array(Rights::ACCESS_TYPES['edit'], $rightsSite)) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_SITE_ACCESS_DENIED') ); return false; } if (!$this->canDelete()) { $this->error->addError( 'DELETE_ACCESS_DENIED', Loc::getMessage('LANDING_DELETE_ACCESS_DENIED') ); return false; } $result = self::update($this->id, [ 'ACTIVE' => 'N', 'PUBLIC' => 'N', 'CODE' => $this->code, 'SITE_ID' => $toSiteId, 'FOLDER_ID' => $toFolderId ]); if ($result->isSuccess()) { $this->clearFolderIndex(); if ($this->active && $this->canPublicAfterCopy($toFolderId)) { $this->publication(); } } $this->error->addFromResult($result); return $result->isSuccess(); } /** * Copy current landing. * @param int|null $toSiteId Site id (if you want copy in another site). * @param int|null $toFolderId Folder id (if you want copy in some folder). * @param bool $withoutBlocks Copy only pages, without blocks. * @param bool $skipSystem If true, don't copy system flag. * @return int|null New landing id. */ public function copy(?int $toSiteId = null, ?int $toFolderId = null, bool $withoutBlocks = false, bool $skipSystem = false): ?int { if ($this->exist()) { $landingRow = Landing::getList([ 'filter' => [ 'ID' => $this->id ] ])->fetch(); $folderId = null; if (!$toSiteId) { $toSiteId = $this->getSiteId(); } if ($toFolderId !== null) { $folderId = $toFolderId; } // check if folder in the same site if ($folderId) { $folderRow = Site::getFolder($folderId); if (intval($folderRow['SITE_ID'] ?? null) !== $toSiteId) { $folderId = null; } } // add 'copy' to new page? $addCopyMark = !!Landing::getList([ 'select' => [ 'ID' ], 'filter' => [ '=TITLE' => $landingRow['TITLE'], '=DELETED' => 'N', 'FOLDER_ID' => $folderId, 'SITE_ID' => $toSiteId, ] ])->fetch(); // create new page $res = Landing::add([ 'CODE' => $landingRow['CODE'], 'ACTIVE' => 'N', 'PUBLIC' => 'N', 'TITLE' => $addCopyMark ? $landingRow['TITLE'] . ' ' . Loc::getMessage('LANDING_COPY_SUFFIX') : $landingRow['TITLE'], 'SYS' => $skipSystem ? 'N' : $landingRow['SYS'], 'XML_ID' => $landingRow['XML_ID'], 'TPL_CODE' => $landingRow['TPL_CODE'], 'INITIATOR_APP_CODE' => $landingRow['INITIATOR_APP_CODE'], 'DESCRIPTION' => $landingRow['DESCRIPTION'], 'TPL_ID' => $landingRow['TPL_ID'], 'SITE_ID' => $toSiteId, 'SITEMAP' => $landingRow['SITEMAP'], 'FOLDER_ID' => $folderId, 'RULE' => '' ]); // landing allready create, just copy the blocks if ($res->isSuccess()) { Landing::setEditMode(); $landingNew = Landing::createInstance($res->getId()); if ($landingNew->exist()) { if (!$withoutBlocks) { $landingNew->copyAllBlocks($this->id); } // copy hook data Hook::copyLanding( $this->id, $landingNew->getId() ); // copy files File::copyLandingFiles( $this->id, $landingNew->getId() ); // copy template refs if (($refs = TemplateRef::getForLanding($this->id))) { TemplateRef::setForLanding($landingNew->getId(), $refs); } // publication if needed if ($landingRow['ACTIVE'] === 'Y' && $landingNew->canPublicAfterCopy($toFolderId)) { $landingNew->publication(); } return $landingNew->getId(); } $this->error->copyError($landingNew->getError()); } else { $this->error->addFromResult($res); } } return null; } /** * 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) { $blockId = $this->changeParentOfBlock($id, array( 'MOVE' => true, 'AFTER_ID' => $afterId )); if (!$blockId) { $this->error->addError( 'BLOCK_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_NOT_FOUND') ); } return $blockId; } /** * 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; } } /** * Creates new page in the site by template. * @param int $siteId Site id. * @param string $code Template code. * @param array $fields Landing fields. * @return \Bitrix\Main\Entity\AddResult */ public static function addByTemplate(int $siteId, string $code, array $fields = []): \Bitrix\Main\Entity\AddResult { $result = new \Bitrix\Main\Entity\AddResult; // get type by siteId $res = Site::getList([ 'select' => [ 'TYPE' ], 'filter' => [ 'ID' => $siteId ] ]); if (!($site = $res->fetch())) { $result->addError(new \Bitrix\Main\Error( 'SITE_ERROR', Loc::getMessage('LANDING_SITE_ERROR') )); return $result; } // get landing if specified if ($fields['ID'] ?? null) { $res = self::getList([ 'select' => [ 'ID', 'FOLDER_ID', 'FOLDER', 'ACTIVE' ], 'filter' => [ 'SITE_ID' => $siteId, 'ID' => $fields['ID'] ] ]); if (!($landing = $res->fetch())) { $result->addError(new \Bitrix\Main\Error( 'LANDING_ERROR', Loc::getMessage('LANDING_NOT_FOUND') )); return $result; } if ($landing['FOLDER'] === 'Y') { $landing['FOLDER_ID'] = $landing['ID']; } } // include the component $componentName = 'bitrix:landing.demo'; $className = \CBitrixComponent::includeComponentClass($componentName); /** @var \LandingSiteDemoComponent $demoCmp */ $demoCmp = new $className; $demoCmp->initComponent($componentName); $demoCmp->arParams = [ 'TYPE' => $fields['SITE_TYPE'] ?? (($site['TYPE'] == 'STORE' || $site['TYPE'] == 'SMN') ? 'PAGE' : $site['TYPE']), 'SITE_ID' => $siteId, 'SITE_WORK_MODE' => 'N', 'DISABLE_REDIRECT' => 'Y', 'DONT_LEAVE_FRAME' => 'N', 'FOLDER_ID' => $landing['FOLDER_ID'] ?? $fields['FOLDER_ID'] ?? 0, 'META' => $fields ]; if ( isset($fields['PREPARE_BLOCKS'], $fields['PREPARE_BLOCKS_DATA']) && $fields['PREPARE_BLOCKS'] === true && is_array($fields['PREPARE_BLOCKS_DATA']) ) { $demoCmp->arParams['PREPARE_BLOCKS_DATA'] = $fields['PREPARE_BLOCKS_DATA']; } // ... and create the page by component's method $landingId = $demoCmp->createPage($siteId, $code); // prepare success or failure if ($landingId) { $result->setId($landingId); if (($landing['ACTIVE'] ?? 'N') === 'Y') { Landing::createInstance($landingId)->publication(); } } else { foreach ($demoCmp->getErrors() as $code => $title) { $result->addError(new \Bitrix\Main\Error($code, $title)); } } return $result; } }