%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/landing/lib/ |
Current File : /home/bitrix/www/bitrix/modules/landing/lib/block.php |
<?php namespace Bitrix\Landing; use \Bitrix\Main\Web\HttpClient; use \Bitrix\Main\Web\Json; use \Bitrix\Main\Page\Asset; use \Bitrix\Main\Web\DOM; use \Bitrix\Main\Localization\Loc; use \Bitrix\Landing\Internals; Loc::loadMessages(__FILE__); class Block extends \Bitrix\Landing\Internals\BaseTable { /** * Add images from new block to local storage. */ const ADD_FILES_TO_LOCAL_STORAGE = false; /** * Dir of repoitory of blocks. */ const BLOCKS_DIR = '/bitrix/blocks'; /** * Tag for managed cache. */ const BLOCKS_TAG = 'landing_blocks'; /** * Block preview filename. */ const PREVIEW_FILE_NAME = 'preview.jpg'; /** * Local css filename. */ const CSS_FILE_NAME = 'style.css'; /** * Local js filename. */ const JS_FILE_NAME = 'script.js'; /** * Pattern for repo code. */ const REPO_MASK = '/^repo_([\d]+)$/'; /** * Life time for mark new block. */ const NEW_BLOCK_LT = 1209600;//86400 * 14 /** * Access level: access deined. */ const ACCESS_D = 'D'; /** * Access level: edit only design. */ const ACCESS_V = 'V'; /** * Access level: edit content and design (not delete). */ const ACCESS_W = 'W'; /** * Access level: full access. */ const ACCESS_X = 'X'; /** * Symbolic code of card. */ const CARD_SYM_CODE = 'card'; /** * Symbolic code of preset. */ const PRESET_SYM_CODE = 'preset'; /** * Internal class. * @var string */ public static $internalClass = 'BlockTable'; /** * Id of current block. * @var int */ protected $id = 0; /** * Id of landing. * @var int */ protected $lid = 0; /** * Id of site of landing. * @var int */ protected $siteId = 0; /** * Sort of current block. * @var int */ protected $sort = 0; /** * Is the rest block if > 0. * @var int */ protected $repoId = 0; /** * Code of current block. * @var string */ protected $code = ''; /** * Custom anchor of the block. * @var string */ protected $anchor = ''; /** * Actually content of current block. * @var string */ protected $content = ''; /** * Access for this block. * @see ACCESS_* constants. * @var string */ protected $access = 'X'; /** * Additional data of current block. * @var array */ protected $metaData = array(); /** * Active or not current block. * @var boolean */ protected $active = false; /** * Deleted or not current block. * @var boolean */ protected $deleted = false; /** * Public or not current block. * @var boolean */ protected $block = false; /** * Manifest from database. * @var array */ protected $manifestDB = null; /** * Document root. * @var string */ protected $docRoot = ''; /** * Instance of Error. * @var \Bitrix\Landing\Error */ protected $error = null; /** * Constructor. * @param int $id Block id. * @param array $data Data row from BlockTable (by default get from DB). */ public function __construct($id, $data = array()) { if (empty($data) || !is_array($data)) { $data = parent::getList(array( 'select' => array( '*', 'SITE_ID' => 'LANDING.SITE_ID', 'MANIFEST' => 'MANIFEST_DB.MANIFEST' ), 'filter' => array( 'ID' => $id ) ))->fetch(); if (!$data) { $id = 0; } } // if content is empty, fill from repository if (!isset($data['CONTENT']) || trim($data['CONTENT']) == '') { $data['CONTENT'] = ''; } $this->id = intval($id); $this->lid = isset($data['LID']) ? intval($data['LID']) : 0; $this->siteId = isset($data['SITE_ID']) ? intval($data['SITE_ID']) : 0; $this->sort = isset($data['SORT']) ? intval($data['SORT']) : ''; $this->code = isset($data['CODE']) ? trim($data['CODE']) : ''; $this->anchor = isset($data['ANCHOR']) ? trim($data['ANCHOR']) : ''; $this->active = isset($data['ACTIVE']) && $data['ACTIVE'] == 'Y'; $this->deleted = isset($data['DELETED']) && $data['DELETED'] == 'Y'; $this->public = isset($data['PUBLIC']) && $data['PUBLIC'] == 'Y'; $this->content = (!$this->deleted && isset($data['CONTENT'])) ? trim($data['CONTENT']) : ''; if (isset($data['ACCESS'])) { $this->access = $data['ACCESS']; } if (isset($data['MANIFEST'])) { $this->manifestDB = $data['MANIFEST']; } // fill meta data $keys = ['CREATED_BY_ID', 'MODIFIED_BY_ID', 'DATE_CREATE', 'DATE_MODIFY']; foreach ($keys as $key) { if (isset($data[$key])) { $this->metaData[$key] = $data[$key]; } } if (preg_match(self::REPO_MASK, $this->code, $matches)) { $this->repoId = $matches[1]; } if (!$this->content && !$this->deleted) { $this->content = self::getContentFromRepository($this->code); } $this->error = new Error; $this->docRoot = Manager::getDocRoot(); } /** * Fill landing with blocks. * @param \Bitrix\Landing\Landing $landing Landing instance. * @param int $limit Limit count for blocks. * @param array $params Additional params. * @return boolean */ public static function fillLanding(\Bitrix\Landing\Landing $landing, $limit = 0, array $params = array()) { if ($landing->exist()) { $editMode = $landing->getEditMode() || $landing->getPreviewMode(); $repo = array(); $blocks = array(); $rows = array(); // get all blocks by filter $res = parent::getList(array( 'select' => array( '*', 'MANIFEST' => 'MANIFEST_DB.MANIFEST' ), 'filter' => array( 'LID' => $landing->getId(), '=PUBLIC' => $editMode ? 'N' : 'Y', '=DELETED' => (isset($params['deleted']) && $params['deleted'] === true) ? 'Y' : 'N' ), 'order' => array( 'SORT' => 'ASC', 'ID' => 'ASC' ), 'limit' => $limit )); while ($row = $res->fetch()) { $row['SITE_ID'] = $landing->getSiteId(); $block = new self($row['ID'], $row); if (!$editMode && $block->getRepoId()) { $repo[] = $block->getRepoId(); } $blocks[$row['ID']] = $block; } if (!empty($repo)) { $repo = Repo::getAppInfo($repo); } // add blocks to landing foreach ($blocks as $block) { $reposInfo = isset($repo[$block->getRepoId()]) ? $repo[$block->getRepoId()] : array(); if ($editMode || !$reposInfo) { $landing->addBlockToCollection($block); } elseif ( isset($reposInfo['PAYMENT_ALLOW']) && $reposInfo['PAYMENT_ALLOW'] == 'Y' ) { $landing->addBlockToCollection($block); } } return true; } return false; } /** * Create copy of blocks for draft version. * @param \Bitrix\Landing\Landing $landing Landing instance. * @return void */ public static function cloneForEdit(\Bitrix\Landing\Landing $landing) { if ($landing->exist()) { $clone = true; $forClone = array(); $res = parent::getList(array( 'select' => array( 'ID', 'LID', 'CODE', 'SORT', 'ACTIVE', 'CONTENT', 'PUBLIC', 'ACCESS' ), 'filter' => array( 'LID' => $landing->getId() ) )); while ($row = $res->fetch()) { if ($row['PUBLIC'] != 'Y') { $clone = false; break; } else { $row['PUBLIC'] = 'N'; $row['PARENT_ID'] = $row['ID']; unset($row['ID']); $forClone[] = $row; } } if ($clone) { foreach ($forClone as $row) { parent::add($row); } } } } /** * Publication blocks for landing. * @param \Bitrix\Landing\Landing $landing Landing instance. * @return void */ public static function publicationBlocks(\Bitrix\Landing\Landing $landing) { Mutator::blocksPublication($landing); } /** * Recognize landing id by block id. * @param int|array $id Block id (id array). * @return int|array|false */ public static function getLandingIdByBlockId($id) { $data = array(); $res = parent::getList(array( 'select' => array( 'ID', 'LID' ), 'filter' => array( 'ID' => $id ) )); while ($row = $res->fetch()) { $data[$row['ID']] = $row['LID']; } if (is_array($id)) { return $data; } elseif (!empty($data)) { return array_pop($data); } return false; } /** * Gets row by block id. * @param int|array $id Block id (id array). * @param array $select Select row. * @deprecated since 18.5.0 * @return int|array|false */ public static function getLandingRowByBlockId($id, array $select = array('ID')) { return self::getRowByBlockId($id, $select); } /** * Gets landing row by block id. * @param int|array $id Block id (id array). * @param array $select Select row. * @return int|array|false */ public static function getRowByBlockId($id, array $select = array('ID')) { $data = array(); $res = parent::getList(array( 'select' => $select, 'filter' => array( 'ID' => $id ) )); while ($row = $res->fetch()) { $data[$row['ID']] = $row; } if (is_array($id)) { return $data; } elseif (!empty($data)) { return array_pop($data); } return false; } /** * Get content from repository by code. * @param string $code Block code. * @param string $namespace Namespace (optional). * @return string */ public static function getContentFromRepository($code, $namespace = null) { $content = ''; // local repo if (preg_match(self::REPO_MASK, $code, $matches)) { $repo = Repo::getById($matches[1])->fetch(); $content = $repo['CONTENT']; } // files storage elseif ($path = self::getBlockPath($code, $namespace)) { $path = Manager::getDocRoot() . $path . '/block.php'; if (file_exists($path)) { $content = file_get_contents($path); } } elseif ($manifest = Manifest::getByCode($code, true)) { $content = $manifest['CONTENT']; } return $content; } /** * Create instance by string code. * @param \Bitrix\Landing\Landing $landing Landing - owner for new block. * @param string $code Code of block from repository. * @param array $data Additional data array. * @return Block|false */ public static function createFromRepository(\Bitrix\Landing\Landing $landing, $code, $data = array()) { // get content and mainfest $content = self::getContentFromRepository($code); $manifest = self::getManifestFile($code); // version control if ( isset($manifest['block']['version']) && version_compare(Manager::getVersion(), $manifest['block']['version']) < 0 ) { $landing->getError()->addError( 'BLOCK_WRONG_VERSION', Loc::getMessage('LANDING_BLOCK_WRONG_VERSION') ); return false; } // check errors if (!$landing->exist()) { $landing->getError()->addError( 'LANDING_NOT_EXIST', Loc::getMessage('LANDING_BLOCK_LANDING_NOT_EXIST') ); return false; } if ($content == '') { $landing->getError()->addError( 'BLOCK_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_NOT_FOUND') ); return false; } // add $fields = array( 'LID' => $landing->getId(), 'CODE' => $code, 'CONTENT' => $content, 'ACTIVE' => 'Y' ); $availableReplace = array( 'ACTIVE', 'PUBLIC', 'ACCESS', 'SORT', 'CONTENT', 'ANCHOR' ); foreach ($availableReplace as $replace) { if (isset($data[$replace])) { $fields[$replace] = $data[$replace]; } } $res = parent::add($fields); if ($res->isSuccess()) { $block = new self($res->getId()); $manifest = $block->getManifest(); if ( isset($manifest['callbacks']['afteradd']) && is_callable($manifest['callbacks']['afteradd']) ) { $manifest['callbacks']['afteradd']($block); } // get all images from block to local storage if (self::ADD_FILES_TO_LOCAL_STORAGE) { foreach ($manifest['nodes'] as $selector => $node) { if (isset($node['type']) && $node['type'] == 'img') { $images = \Bitrix\Landing\Node\Img::getNode( $block, $selector ); foreach ($images as &$img) { $file = Manager::savePicture($img['src']); if ($file) { File::addToBlock( $block->getId(), $file['ID'] ); $img['src'] = $file['SRC']; $img['id'] = $file['ID']; } } \Bitrix\Landing\Node\Img::saveNode( $block, $selector, $images ); } } $block->saveContent( $block->getDom()->saveHTML() ); $block->save(); } return $block; } else { $landing->getError()->addFromResult($res); return false; } } /** * New or not the block. * @param string $block Block code. * @return boolean */ protected static function isNewBlock($block) { static $newBlocks = null; if ($newBlocks === null) { $newBlocks = unserialize(Manager::getOption('new_blocks')); if (!is_array($newBlocks)) { $newBlocks = array(); } if ( !isset($newBlocks['date']) || ( isset($newBlocks['date']) && ((time() - $newBlocks['date']) > self::NEW_BLOCK_LT) ) ) { $newBlocks = array(); } if (isset($newBlocks['items'])) { $newBlocks = $newBlocks['items']; } } return in_array($block, $newBlocks); } /** * Clear cache repository. * @return void */ public static function clearRepositoryCache() { if (defined('BX_COMP_MANAGED_CACHE')) { Manager::getCacheManager()->clearByTag(self::BLOCKS_TAG); } } /** * Get blocks from repository. * @param bool $withManifest Get repo with manifest files of blocks. * @return array */ public static function getRepository($withManifest = false) { static $blocksCats = array(); // function for prepare return $returnFunc = function($blocksCats) use($withManifest) { $event = new \Bitrix\Main\Event('landing', 'onBlockGetRepository', array( 'blocks' => $blocksCats, 'withManifest' => $withManifest )); $event->send(); foreach ($event->getResults() as $result) { if ($result->getResultType() != \Bitrix\Main\EventResult::ERROR) { if (($modified = $result->getModified())) { if (isset($modified['blocks'])) { $blocksCats = $modified['blocks']; } } } } return $blocksCats; }; // static cache if (!$withManifest && !empty($blocksCats)) { return $returnFunc($blocksCats); } // local function for fill last used blocks $fillLastUsed = function($blocksCats) { $blocksCats['last']['items'] = array(); $lastUsed = self::getLastUsed(); if ($lastUsed) { foreach ($lastUsed as $code) { $blocksCats['last']['items'][$code] = array(); } foreach ($blocksCats as &$cat) { foreach ($cat['items'] as $code => &$block) { if (in_array($code, $lastUsed)) { $block['section'][] = 'last'; $blocksCats['last']['items'][$code] = $block; } } unset($block); } unset($cat); } return $blocksCats; }; // system cache begin $cache = new \CPHPCache(); $cacheTime = 86400; $cacheStarted = false; $cacheId = $withManifest ? 'blocks_manifest' : 'blocks'; $cacheId .= LANGUAGE_ID; $cachePath = 'landing'; if ($cache->initCache($cacheTime, $cacheId, $cachePath)) { $blocksCats = $cache->getVars(); if (is_array($blocksCats) && !empty($blocksCats)) { $blocksCats = $fillLastUsed($blocksCats); return $returnFunc($blocksCats); } } if ($cache->startDataCache($cacheTime, $cacheId, $cachePath)) { $cacheStarted = true; if (defined('BX_COMP_MANAGED_CACHE')) { Manager::getCacheManager()->startTagCache($cachePath); Manager::getCacheManager()->registerTag(self::BLOCKS_TAG); } } // not in cache - init $blocks = array(); $sections = array(); $path = Manager::getDocRoot() . self::BLOCKS_DIR; // read all subdirs ($namespaces) in block dir $namespaces = array(); if (($handle = opendir($path))) { while ((($entry = readdir($handle)) !== false)) { if ( $entry != '.' && $entry != '..' && is_dir($path . '/' . $entry) ) { $namespaces[] = $entry; } } } //get all blocks with description-file sort($namespaces); foreach ($namespaces as $subdir) { // get from cloud only if it not repo $restSrc = Manager::getOption('block_vendor_' . $subdir); if ( (!defined('LANDING_IS_REPO') || LANDING_IS_REPO !== true) && $restSrc ) { $http = new HttpClient; try { $json = Json::decode($http->get( $restSrc . 'landing_cloud.cloud.getrepository' . '?user_lang=' . LANGUAGE_ID . '&version=' . Manager::getVersion() )); } catch (\Exception $e) { if ($cacheStarted) { $cache->abortDataCache(); } } if ( isset($json['result']) && is_array($json['result']) ) { $insertCodes = array(); foreach ($json['result'] as $sectionCode => $sectionItem) { $sections[$sectionCode] = $sectionItem['name']; if ( isset($sectionItem['items']) && is_array($sectionItem['items']) ) { foreach ($sectionItem['items'] as $code => $item) { if (isset($item['manifest'])) { if (!isset($item['content'])) { $item['content'] = ''; } if (!isset($insertCodes[$code])) { $insertCodes[$code] = true; Manifest::add(array( 'CODE' => $code, 'MANIFEST' => $item['manifest'], 'CONTENT' => $item['content'] )); } unset($item['content']); unset($item['manifest']); } $blocks[$code] = $item; } } } } } else if (($handle = opendir($path . '/' . $subdir))) { // sections $sectionsPath = $path . '/' . $subdir . '/.sections.php'; if (file_exists($sectionsPath)) { $sections = array_merge( $sections, (array) include $sectionsPath ); } // blocks while ((($entry = readdir($handle)) !== false)) { $descriptionPath = $path . '/' . $subdir . '/' . $entry . '/.description.php'; $previewPathJpg = $path . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME; if ($entry != '.' && $entry != '..' && file_exists($descriptionPath)) { Loc::loadLanguageFile($descriptionPath); $description = include $descriptionPath; if (isset($description['block']['name'])) { $previewFileName = Manager::getUrlFromFile( self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/' . self::PREVIEW_FILE_NAME ); $blocks[$entry] = array( 'name' => $description['block']['name'], 'namespace' => $subdir, 'new' => self::isNewBlock($entry), 'version' => isset($description['block']['version']) ? $description['block']['version'] : null, 'type' => isset($description['block']['type']) ? $description['block']['type'] : array(), 'section' => isset($description['block']['section']) ? $description['block']['section'] : 'other', 'description' => isset($description['block']['description']) ? $description['block']['description'] : '', 'preview' => file_exists($previewPathJpg) ? $previewFileName : '', 'restricted' => false, 'repo_id' => false, 'app_code' => false ); if ($withManifest) { $blocks[$entry]['manifest'] = self::getManifestFile( $subdir . ':' . $entry ); $blocks[$entry]['content'] = self::getContentFromRepository( $entry, $subdir ); if (isset($blocks[$entry]['manifest']['block'])) { $blocks[$entry]['manifest']['block']['preview'] = $blocks[$entry]['preview']; } // local assets to manifest's assets if (!isset($blocks[$entry]['manifest']['assets'])) { $blocks[$entry]['manifest']['assets'] = array(); } // if css exists if (file_exists($path . '/' . $subdir . '/' . $entry . '/style.min.css')) { if (!isset($blocks[$entry]['manifest']['assets']['css'])) { $blocks[$entry]['manifest']['assets']['css'] = array(); } $blocks[$entry]['manifest']['assets']['css'][] = Manager::getUrlFromFile( self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/style.min.css' ); } // if js exists if (file_exists($path . '/' . $subdir . '/' . $entry . '/script.min.js' )) { if (!isset($blocks[$entry]['manifest']['assets']['js'])) { $blocks[$entry]['manifest']['assets']['js'] = array(); } $blocks[$entry]['manifest']['assets']['js'][] = Manager::getUrlFromFile( self::BLOCKS_DIR . '/' . $subdir . '/' . $entry . '/script.min.js' ); } if (empty($blocks[$entry]['manifest']['assets'])) { unset($blocks[$entry]['manifest']['assets']); } } } } } } } // rest repo $blocksRepo = \Bitrix\Landing\Repo::getRepository(); // get apps by blocks $apps = array(); foreach ($blocksRepo as $block) { if ($block['app_code']) { $apps[] = $block['app_code']; } } if ($apps) { $apps = Repo::getAppByCode($apps); // mark repo blocks expired foreach ($blocksRepo as &$block) { if ( $block['app_code'] && isset($apps[$block['app_code']]) && $apps[$block['app_code']]['PAYMENT_ALLOW'] == 'N' ) { $block['app_expired'] = true; } } unset($block); } $blocks += $blocksRepo; // create new section in repo $createNewSection = function($title) { return array( 'name' => $title, 'new' => false, 'separator' => false, 'app_code' => false, 'items' => array() ); }; // set by sections $md5s = array(); foreach ($sections as $code => $title) { $title = trim($title); $blocksCats[$code] = $createNewSection($title); $md5s[md5(strtolower($title))] = $code; } foreach ($blocks as $key => $block) { if (!is_array($block['section'])) { $block['section'] = array($block['section']); } foreach ($block['section'] as $section) { $section = trim($section); $sectionMd5 = md5(strtolower($section)); // adding new sections (actual for repo blocks) if ( !isset($blocksCats[$section]) && !isset($blocksCats[$sectionMd5]) ) { if (isset($md5s[$sectionMd5])) { $section = $md5s[$sectionMd5]; } else { $blocksCats[$sectionMd5] = $createNewSection($section); $section = $sectionMd5; } } else if (isset($blocksCats[$sectionMd5])) { $section = $sectionMd5; } $blocksCats[$section]['items'][$key] = $block; if ($block['new']) { $blocksCats[$section]['new'] = true; } } } // add apps sections if (!empty($blocksRepo)) { $blocksCats['separator_apps'] = array( 'name' => Loc::getMessage('LANDING_BLOCK_SEPARATOR_PARTNER'), 'separator' => true, 'items' => array() ); foreach ($apps as $app) { $blocksCats[$app['CODE']] = array( 'name' => $app['APP_NAME'], 'new' => false, 'separator' => false, 'app_code' => $app['CODE'], 'items' => array() ); } // add blocks to the app sections foreach ($blocksRepo as $key => $block) { if ($block['app_code']) { $blocksCats[$block['app_code']]['items'][$key] = $block; } } } // system cache end if ($cacheStarted) { $cache->endDataCache($blocksCats); if (defined('BX_COMP_MANAGED_CACHE')) { Manager::getCacheManager()->endTagCache(); } } $blocksCats = $fillLastUsed($blocksCats); return $returnFunc($blocksCats); } /** * Get last used blocks by current user. * @param int $count Count of blocks. * @return array */ public static function getLastUsed($count = 10) { $blocks = array(); $c = 0; $res = parent::getList(array( 'select' => array( 'CODE' ), 'filter' => array( 'CREATED_BY_ID' => Manager::getUserId(), '=PUBLIC' => 'N' ), 'order' => array( 'DATE_CREATE' => 'DESC' ) )); while ($row = $res->fetch()) { $blocks[$row['CODE']] = $row['CODE']; if (++$c >= $count) { break; } } return array_values($blocks); } /** * Get blocks style manifest from repository. * @return array */ public static function getStyle() { $style = array(); // read all subdirs ($namespaces) in block dir $path = Manager::getDocRoot() . self::BLOCKS_DIR; if (($handle = opendir($path))) { while ((($entry = readdir($handle)) !== false)) { if ( $entry != '.' && $entry != '..' && is_dir($path . '/' . $entry) && file_exists($path . '/' . $entry . '/.style.php') ) { $style[$entry] = include $path . '/' . $entry . '/.style.php'; } } } return $style; } /** * Get block content array. * @param int $id Block id. * @param boolean $editMode Edit mode if true. * @param array $params Some params. * @return array */ public static function getBlockContent($id, $editMode = false, array $params = array()) { if (!isset($params['wrapper_show'])) { $params['wrapper_show'] = true; } ob_start(); $block = new self($id); $extContent = ''; if (($ext = $block->getExt())) { $extContent = \CUtil::initJSCore($ext, true); $extContent = preg_replace( '#<script type="text/javascript"(\sdata\-skip\-moving\="true")?>.*?</script>#is', '', $extContent ); } $landing = Landing::createInstance($block->getLandingId()); $block->view( false, $landing->exist() ? $landing : null, $params ); $content = ob_get_contents(); $content = self::replaceMetaMarkers($content); ob_end_clean(); if ($block->exist()) { $availableJS = !$editMode || !$block->getRepoId(); $return = array( 'id' => $id, 'content' => $content, 'content_ext' => $extContent, 'css' => $block->getCSS(), 'js' => $availableJS ? $block->getJS() : array(), 'manifest' => $block->getManifest() ); if ( $editMode && isset($return['manifest']['requiredUserAction']) ) { $return['requiredUserAction'] = $return['manifest']['requiredUserAction']; } return $return; } else { return array(); } } /** * Get block anchor. * @param int $id Block id. * @return string */ public static function getAnchor($id) { return 'block' . $id; } /** * Get namespace for block. * @param string $code Code of block. * @return string */ protected static function getBlockNamespace($code) { static $paths = array(); static $namespace = array(); $code = trim($code); if (isset($paths[$code])) { return $paths[$code]; } $paths[$code] = ''; $path = Manager::getDocRoot() . self::BLOCKS_DIR; // read all subdirs ($namespaces) in block dir if (empty($namespace)) { if (($handle = opendir($path))) { while ((($entry = readdir($handle)) !== false)) { if ( is_dir($path . '/' . $entry) && $entry != '.' && $entry != '..' ) { $namespace[] = $entry; } } } sort($namespace); } // get first needed block from end foreach (array_reverse($namespace) as $subdir) { if (file_exists($path . '/' . $subdir . '/' . $code . '/.description.php')) { $paths[$code] = $subdir; break; } } return $paths[$code]; } /** * Get local path for block. * @param string $code Code of block. * @param string $namespace Namespace (optional). * @return string */ protected static function getBlockPath($code, $namespace = null) { if (!$namespace) { $namespace = self::getBlockNamespace($code); } if ($namespace) { $disabled = explode(',', Manager::getOption('disabled_namespaces', '')); if (!in_array($namespace, $disabled)) { return self::BLOCKS_DIR . '/' . $namespace . '/' . $code; } } return ''; } /** * Exist or not block in current instance. * @return boolean */ public function exist() { return $this->id > 0; } /** * Get id of the block. * @return int */ public function getId() { return $this->id; } /** * Gets landing id. * @return int */ public function getLandingId() { return $this->lid; } /** * Gets site id (of landing). * @return int */ public function getSiteId() { return $this->siteId; } /** * Get code of the block. * @return string */ public function getCode() { return $this->code; } /** * Get anchor of the block. * @return string */ public function getLocalAnchor() { return $this->anchor; } /** * Get content of the block. * @return string */ public function getContent() { return $this->content; } /** * Active or not the block. * @return boolean */ public function isActive() { return $this->active; } /** * Public or not the block. * @return boolean */ public function isPublic() { return $this->public; } /** * Get current access. * @return string */ public function getAccess() { return $this->access; } /** * Set active to the block. * @param boolean $active Bool: true or false. * @return boolean */ public function setActive($active) { if ($this->access < $this::ACCESS_X) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } $this->active = (boolean) $active; return true; } /** * Get repo id, if block from repo. * @return int */ public function getRepoId() { return $this->repoId; } /** * Gets site row. * @return array */ public function getSite() { static $site = null; if ( $site === null && $this->siteId ) { $site = Site::getList(array( 'filter' => array( 'ID' => $this->siteId ) ))->fetch(); } return $site; } /** * Get preview picture of the block. * @return string */ public function getPreview() { $path = self::getBlockPath($this->code); if ($path && file_exists($this->docRoot . '/' . $path . '/' . self::PREVIEW_FILE_NAME)) { return $path . '/' . self::PREVIEW_FILE_NAME; } elseif (isset($this->manifestDB['block']['preview'])) { return $this->manifestDB['block']['preview']; } return ''; } /** * Get error collection * @return \Bitrix\Landing\Error */ public function getError() { return $this->error; } /** * Get class handler for type of node. * @param string $type Type. * @return string */ protected function getTypeClass($type) { static $classes = array(); $type = strtolower($type); if (isset($classes[$type])) { return $classes[$type]; } $class = __NAMESPACE__ . '\\Node\\' . $type; // check custom classes $event = new \Bitrix\Main\Event('landing', 'onGetNodeClass', array( 'type' => $type, )); $event->send(); foreach ($event->getResults() as $result) { if ($result->getResultType() != \Bitrix\Main\EventResult::ERROR) { if ( ($modified = $result->getModified()) && isset($modified['class']) && is_subclass_of($modified['class'], '\\Bitrix\\Landing\\Node') ) { $class = $modified['class']; } } } $classes[$type] = $class; return $classes[$type]; } /** * Get manifest array from block. * @param bool $extended Get extended manifest. * @param bool $missCache Don't save in static cache. * @param array $params Additional params. * @return array */ public function getManifest($extended = false, $missCache = false, array $params = array()) { static $manifestStore = array(); if ( !$missCache && isset($manifestStore[$this->code]) ) { if ( !isset($manifestStore[$this->code]['disableCache']) || $manifestStore[$this->code]['disableCache'] !== true ) { return $manifestStore[$this->code]; } } // manifest from market, files, or rest if ($this->repoId) { $manifest = Repo::getBlock($this->repoId); } else if ($path = self::getBlockPath($this->code)) { //isolate variables from .description.php $includeDesc = function($path) { Loc::loadLanguageFile($path . '/.description.php'); $manifest = include $path . '/.description.php'; return $manifest; }; $manifest = $includeDesc($this->docRoot . $path); } else { if ($this->manifestDB === null) { $this->manifestDB = Manifest::getByCode($this->code); } $manifest = $this->manifestDB; } // prepare manifest if (isset($manifest['block']['name'])) { // prepare by subtype if ( isset($manifest['block']['subtype']) && ( !isset($params['miss_subtype']) || $params['miss_subtype'] !== true ) ) { $subtypes = $manifest['block']['subtype']; if (!is_array($subtypes)) { $subtypes = [$subtypes]; } foreach ($subtypes as $subtype) { $subtypeClass = '\\Bitrix\\Landing\\Subtype\\'; $subtypeClass .= $subtype; if (class_exists($subtypeClass)) { $manifest = $subtypeClass::prepareManifest( $manifest, $this, isset($manifest['block']['subtype_params']) ? (array)$manifest['block']['subtype_params'] : array() ); } } } // set empty array if no exists foreach (array('cards', 'nodes', 'attrs') as $code) { if (!isset($manifest[$code]) || !is_array($manifest[$code])) { $manifest[$code] = array(); } } // prepare every node foreach ($manifest['nodes'] as $keyNode => &$node) { if (is_callable($node) && !$this->repoId) { $node = $node(); } $node['code'] = $keyNode; $class = $this->getTypeClass($node['type']); if (isset($node['type']) && class_exists($class)) { $node['handler'] = call_user_func(array( $class, 'getHandlerJS' )); if (method_exists($class, 'prepareManifest')) { $node = call_user_func_array(array( $class, 'prepareManifest' ), array( $this, $node, &$manifest )); if (!is_array($node)) { unset($manifest['nodes'][$keyNode]); } } } else { unset($manifest['nodes'][$keyNode]); } } unset($node); // and attrs foreach ($manifest['attrs'] as $keyNode => &$node) { if (is_callable($node) && !$this->repoId) { $node = $node(); } } unset($node); // callbacks if (isset($manifest['callbacks']) && is_array($manifest['callbacks'])) { $callbacks = array(); foreach ($manifest['callbacks'] as $code => $callback) { $callbacks[strtolower($code)] = $callback; } $manifest['callbacks'] = $callbacks; } // prepare styles if (!isset($manifest['namespace'])) { $manifest['namespace'] = $this->getBlockNamespace($this->code); } if ( isset($manifest['style']) && !( isset($manifest['style']['block']) && isset($manifest['style']['nodes']) && count($manifest['style']) == 2 ) ) { $manifest['style'] = array( 'block' => array(), 'nodes' => is_array($manifest['style']) ? $manifest['style'] : array() ); } elseif ( !isset($manifest['style']) || !is_array($manifest['style']) ) { $manifest['style'] = array( 'block' => array(), 'nodes' => array() ); } // other $manifest['code'] = $this->code; } else { $manifest = array(); } if (!$missCache) { $manifestStore[$this->code] = $manifest; } return $manifest; } /** * Get manifest array as is from block. * @param string $code Code name, format "namespace:code" or just "code". * @return array */ public static function getManifestFile($code) { static $manifests = array(); if (isset($manifests[$code])) { return $manifests[$code]; } $manifests[$code] = array(); $namespace = null; if (strpos($code, ':') !== false) { list($namespace, $code) = explode(':', $code); } if ($path = self::getBlockPath($code ,$namespace)) { $docRoot = Manager::getDocRoot(); Loc::loadLanguageFile($docRoot . $path . '/.description.php'); $manifests[$code] = include $docRoot . $path . '/.description.php'; } else { $manifests[$code] = Manifest::getByCode($code); } return $manifests[$code]; } /** * Get CSS/JS array of block. * @param string $type What return: css or js. * @return array */ public function getAsset($type = null) { static $asset = array(); if (!isset($asset[$this->code])) { $asset[$this->code] = array( 'css' => array(), 'js' => array(), 'ext' => array() ); // additional asset first if ($this->repoId) { $manifest = Repo::getBlock($this->repoId); } else if ($path = self::getBlockPath($this->code)) { $manifest = include $this->docRoot . $path . '/.description.php'; } else { if ($this->manifestDB === null) { $this->manifestDB = Manifest::getByCode($this->code); } $manifest = $this->manifestDB; } foreach (array_keys($asset[$this->code]) as $ass) { if ( isset($manifest['assets'][$ass]) && !empty($manifest['assets'][$ass]) ) { foreach ($manifest['assets'][$ass] as $file) { if (!is_string($file)) { continue; } if ($ass != 'ext') { $asset[$this->code][$ass][] = trim($file); } // for rest block allowed only this else if ( !$this->repoId || in_array($file, array('landing_form')) ) { $asset[$this->code][$ass][] = trim($file); } } $asset[$this->code][$ass] = array_unique($asset[$this->code][$ass]); } } // next is phis files if (isset($path) && $path) { // base files next $file = $path . '/' . self::CSS_FILE_NAME; if (file_exists($this->docRoot . $file)) { $asset[$this->code]['css'][] = $file; } $file = $path . '/' . self::JS_FILE_NAME; if (file_exists($this->docRoot . $file)) { $asset[$this->code]['js'][] = $file; } } } return isset($asset[$this->code][$type]) ? $asset[$this->code][$type] : $asset[$this->code]; } /** * Get css file path, if exists. * @return array */ public function getCSS() { return $this->getAsset('css'); } /** * Get js file path, if exists. * @return array */ public function getJS() { return $this->getAsset('js'); } /** * Get extensions. * @return array */ public function getExt() { return $this->getAsset('ext'); } /** * Out the block. * @param boolean $edit Out block in edit mode. * @param Landing|null $landing Landing of this block. * @param array $params Some params. * @return void */ public function view($edit = false, \Bitrix\Landing\Landing $landing = null, array $params = array()) { global $APPLICATION; static $jsPlaced = array(); if (!isset($params['wrapper_show'])) { $params['wrapper_show'] = true; } if ($this->deleted) { return; } if ($edit || $this->active) { foreach ($this->getCSS() as $css) { Asset::getInstance()->addCSS($css); } if (($ext = $this->getExt())) { \CUtil::initJSCore($ext); } if (!$edit || !$this->repoId) { foreach ($this->getJS() as $js) { if ($this->repoId) { if (!in_array($js, $jsPlaced)) { $jsPlaced[] = $js; Manager::setPageClass( 'FooterJS', '<script type="text/javascript" src="' . \htmlspecialcharsbx($js) . '"></script>' ); } } else { Asset::getInstance()->addJS($js); } } } } if ($params['wrapper_show']) { if ($edit) { $anchor = $this->getAnchor($this->id); } else { $anchor = $this->anchor ? \htmlspecialcharsbx($this->anchor) : $this->getAnchor($this->id); } $classFromCode = 'block-' . $this->code; $classFromCode = preg_replace('/([^a-z0-9-])/i', '-', $classFromCode); $classFromCode = ' ' . $classFromCode; $content = '<div id="' . $anchor . '" class="block-wrapper' . (!$this->active ? ' landing-block-deactive' : '') . $classFromCode . '">' . $this->content . '</div>'; } else { $content = $this->content; } // @tmp bug with setInnerHTML save result $content = preg_replace('/&([^\s]{1})/is', '&$1', $content); if ($edit) { $manifest = $this->getManifest(); if (!$manifest) { $manifest = array( 'code' => $this->code ); } if ($manifest) { echo '<script type="text/javascript">' . 'BX.ready(function(){' . 'if (typeof BX.Landing.Block !== "undefined")' . '{' . 'new BX.Landing.Block(' . 'BX("block' . $this->id . '"), ' . '{' . 'id: ' . $this->id . ', ' . 'active: ' . ($this->active ? 'true' : 'false') . ', ' . 'anchor: ' . '"' . \CUtil::jsEscape($this->anchor) . '"' . ', ' . 'access: ' . '"' . $this->access . '"' . ', ' . 'manifest: ' . Json::encode($manifest) . ( isset($manifest['requiredUserAction']) ? ', requiredUserAction: ' . Json::encode($manifest['requiredUserAction']) : '' ) . '}' . ');' . '}' . '});' . '</script>'; } $content = $this::replaceMetaMarkers($content); if ($this->repoId) { echo $content; } else { eval('?>' . $content . '<?'); } } elseif ($this->active) { // @todo make better static $sysPages = null; if ($sysPages === null) { $sysPages = array(); foreach (Syspage::get($this->siteId) as $syspage) { $sysPages['@#system_' . $syspage['TYPE'] . '@'] = $syspage['LANDING_ID']; } if (!empty($sysPages)) { $urls = Landing::getPublicUrl($sysPages); foreach ($sysPages as $code => $lid) { if (isset($urls[$lid])) { $sysPages[$code] = $urls[$lid]; } else { unset($sysPages[$code]); } } } } if (!empty($sysPages)) { $content = preg_replace( array_keys($sysPages), array_values($sysPages), $content ); } if ($this->repoId) { echo $content; } else { eval('?>' . $content . '<?'); } } } /** * Set new content. * @param string $content New content. * @return void */ public function saveContent($content) { $this->content = trim($content); $this->getDom(true); } /** * Save current block in DB. * @return boolean */ public function save() { $data = array( 'SORT' => $this->sort, 'ACTIVE' => $this->active ? 'Y' : 'N', 'ANCHOR' => $this->anchor, 'DELETED' => $this->deleted ? 'Y' : 'N' ); if ($this->content) { // if this is php-block, limited by size $manifest = $this->getManifest(); if (strlen($this->content) > 16*1024) { if ( isset($manifest['block']['html']) && $manifest['block']['html'] === false ) { $this->error->addError( 'BLOCK_TEXT_FULL', Loc::getMessage('LANDING_BLOCK_TEXT_FULL') ); return false; } } $data['CONTENT'] = $this->content; } $res = parent::update($this->id, $data); $this->error->addFromResult($res); return $res->isSuccess(); } /** * Change landing of current block. * @param int $lid New landing id. * @return boolean * */ public function changeLanding($lid) { $res = parent::update($this->id, array( 'LID' => $lid, 'PARENT_ID' => 0, 'PUBLIC' => 'N' )); $this->error->addFromResult($res); return $res->isSuccess(); } /** * Delete current block. * @return boolean */ public function unlink() { if ($this->access < $this::ACCESS_X) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } $manifest = $this->getManifest(); $res = self::parentDelete($this->id); if (!$res->isSuccess()) { $this->error->addFromResult($res); } return $res->isSuccess(); } /** * Mark delete or not current block. * @param boolean $mark Mark. * @return void */ public function markDeleted($mark) { if ($this->access >= $this::ACCESS_X) { $this->deleted = (boolean) $mark; } } /** * Set new sort to current block. * @param int $sort New sort. * @return void */ public function setSort($sort) { $this->sort = $sort; } /** * Set new anchor to current block. * @param string $anchor New anchor. * @return boolean */ public function setAnchor($anchor) { $anchor = trim($anchor); $check = !$anchor || preg_match_all('/^[a-z]{1}[a-z0-9\-\_\.\:]+$/i', $anchor); if (!$check) { $this->error->addError( 'BAD_ANCHOR', Loc::getMessage('LANDING_BLOCK_BAD_ANCHOR') ); return false; } $this->anchor = $anchor; return true; } /** * Save new sort to current block to DB. * @param int $sort New sort. * @return void */ public function saveSort($sort) { $this->sort = $sort; Internals\BlockTable::update($this->id, array( 'SORT' => $sort )); } /** * Get sort of current block. * @return int */ public function getSort() { return $this->sort; } /** * Load current content in DOM html structure. * @param bool $clear CLear static cache. * @return DOM\Document */ public function getDom($clear = false) { static $doc = array(); if ( $clear && isset($doc[$this->id]) ) { unset($doc[$this->id]); } if (!isset($doc[$this->id])) { $doc[$this->id] = new DOM\Document; $doc[$this->id]->loadHTML($this->content); } return $doc[$this->id]; } /** * Get metadata of current block. * @return array */ public function getMeta() { return $this->metaData; } /** * Adjust cards count by selector. * @param string $selector Selector. * @param int $count Needed cards count. * @param bool &$changed Changed. * @return boolean Success or failure. */ public function adjustCards($selector, $count, &$changed = false) { $manifest = $this->getManifest(); if (isset($manifest['cards'][$selector])) { $count = (int)$count; $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); $resultCount = count($resultList); if ($count > $resultCount) { for ($i = $resultCount; $i < $count; $i++) { $changed = true; $this->cloneCard($selector, $i - 1); } } elseif ($count < $resultCount) { for ($i = $resultCount; $i > $count; $i--) { $changed = true; $this->removeCard($selector, $i - 1); } } return true; } $this->error->addError( 'CARD_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND') ); return false; } /** * Clone one card in block by selector. * @param string $selector Selector. * @param int $position Card position. * @param string $content New content for cloned card. * @return boolean Success or failure. */ public function cloneCard($selector, $position, $content = '') { $manifest = $this->getManifest(); if (isset($manifest['cards'][$selector])) { $position = max($position, -1); $realPosition = max($position, 0); $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); if (isset($resultList[$realPosition])) { $parentNode = $resultList[$realPosition]->getParentNode(); $refChild = isset($resultList[$position + 1]) ? $resultList[$position + 1] : null; $haveChild = false; if ($refChild) { foreach ($parentNode->getChildNodes() as $child) { if ($child === $refChild) { $haveChild = true; break; } } } if ($parentNode && (!$refChild || $haveChild)) { // some dance for set new content ;) if ($content) { $tmpCardName = strtolower('tmpcard' . randString(10)); $newChild = new DOM\Element($tmpCardName); $newChild->setOwnerDocument($doc); $newChild->setInnerHTML($content); } else { $newChild = $resultList[$realPosition]; } $parentNode->insertBefore( $newChild, $refChild, false ); // cleaning and save if (isset($tmpCardName)) { $this->saveContent( str_replace( array('<' . $tmpCardName . '>', '</' . $tmpCardName . '>'), '', $doc->saveHTML() ) ); } else { $this->saveContent($doc->saveHTML()); } } return true; } } $this->error->addError( 'CARD_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND') ); return false; } /** * Set card content from block by selector. * @param string $selector Selector. * @param int $position Card position. * @param string $content New content. * @return boolean Success or failure. */ public function setCardContent($selector, $position, $content) { $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); if (isset($resultList[$position])) { $resultList[$position]->setInnerHTML( $content ); $this->saveContent($doc->saveHTML()); return true; } $this->error->addError( 'CARD_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND') ); return false; } /** * Gets card content from block by selector. * @param string $selector Selector. * @param int $position Card position. * @return string */ public function getCardContent($selector, $position) { $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); if (isset($resultList[$position])) { return $resultList[$position]->getOuterHtml(); } return null; } /** * Gets count of cards from block by selector. * @param string $selector Selector. * @return int */ public function getCardCount($selector) { $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); return count($resultList); } /** * Remove one card from block by selector. * @param string $selector Selector. * @param int $position Card position. * @return boolean Success or failure. */ public function removeCard($selector, $position) { $manifest = $this->getManifest(); if (isset($manifest['cards'][$selector])) { $doc = $this->getDom(); $resultList = $doc->querySelectorAll($selector); if (isset($resultList[$position])) { $resultList[$position]->getParentNode()->removeChild( $resultList[$position] ); $this->saveContent($doc->saveHTML()); return true; } } $this->error->addError( 'CARD_NOT_FOUND', Loc::getMessage('LANDING_BLOCK_CARD_NOT_FOUND') ); return false; } /** * Set new names for nodes of block. * @param array $data Nodes data array. * @return boolean */ public function changeNodeName($data) { if ($this->access < $this::ACCESS_W) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } $doc = $this->getDom(); $manifest = $this->getManifest(); // find available nodes by manifest from data foreach ($manifest['nodes'] as $selector => $node) { if (isset($data[$selector])) { $resultList = $doc->querySelectorAll($selector); foreach ($data[$selector] as $pos => $value) { $value = trim($value); if ( preg_match('/^[a-z0-9]+$/i', $value) && isset($resultList[$pos])) { $resultList[$pos]->setNodeName($value); } } } } // save rebuild html as text $this->saveContent($doc->saveHTML()); return true; } /** * Set new content to nodes of block. * @param array $data Nodes data array. * @param array $additional Additional prams for save. * @return boolean */ public function updateNodes($data, $additional = array()) { if ($this->access < $this::ACCESS_W) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } $doc = $this->getDom(); $manifest = $this->getManifest(); // find available nodes by manifest from data foreach ($manifest['nodes'] as $selector => $node) { if (isset($data[$selector])) { if (!is_array($data[$selector])) { $data[$selector] = array( $data[$selector] ); } // and save content from frontend in DOM by handler-class call_user_func_array(array( $this->getTypeClass($node['type']), 'saveNode' ), array( &$this, $selector, $data[$selector], $additional )); } } // save rebuild html as text $this->saveContent($doc->saveHTML()); return true; } /** * Change cards multiple. * @param array $data Array with cards. * @return boolean */ public function updateCards(array $data = array()) { $manifest = $this->getManifest(); foreach ($data as $selector => $item) { $cardManifest = $manifest['cards'][$selector]; // first gets content of current cards $cardContent = array(); $cardCount = $this->getCardCount($selector); for ($i = 0; $i < $cardCount; $i++) { $cardContent[$i] = $this->getCardContent( $selector, $i ); } // then fill all cards by content from existing cards and presets if ( isset($item['source']) && is_array($item['source']) ) { $newContent = array(); foreach ($item['source'] as $i => $source) { $type = isset($source['type']) ? $source['type'] : self::CARD_SYM_CODE; $value = isset($source['value']) ? $source['value'] : 0; // clone card if ( $type == self::CARD_SYM_CODE && isset($cardContent[$value]) ) { $newContent[$i] = $cardContent[$value]; } // clone preset else if ( $type == 'preset' && isset($cardManifest['presets'][$value]['html']) ) { $newContent[$i] = $cardManifest['presets'][$value]['html']; } else { $newContent[$i] = ''; } } $newContent = trim(implode('', $newContent)); if ($newContent) { $dom = $this->getDom(); $resultList = $dom->querySelectorAll($selector); if (isset($resultList[0])) { $resultList[0]->getParentNode()->setInnerHtml( $newContent ); } $this->saveContent( $dom->saveHTML() ); } } // and finally update content cards if ( isset($item['values']) && is_array($item['values']) ) { $updNodes = array(); foreach ($item['values'] as $upd) { if (is_array($upd)) { foreach ($upd as $sel => $content) { if (strpos($sel, '@')) { list($sel, $pos) = explode('@', $sel); } if (!isset($updNodes[$sel])) { $updNodes[$sel] = array(); } $updNodes[$sel][$pos] = $content; } } } if (!empty($updNodes)) { $this->updateNodes($updNodes); } } } return true; } /** * Recursive styles remove in Node. * @param \Bitrix\Main\Web\DOM\Node $node Node for clear. * @param array $styleToRemove Array of styles to remove. * @return \Bitrix\Main\Web\DOM\Node */ protected function removeStyle(\Bitrix\Main\Web\DOM\Node $node, array $styleToRemove) { foreach ($node->getChildNodesArray() as $nodeChild) { if ($nodeChild instanceof \Bitrix\Main\Web\DOM\Element) { $styles = DOM\StyleInliner::getStyle($nodeChild); if (!empty($styles)) { foreach ($styleToRemove as $remove) { if (isset($styles[$remove])) { unset($styles[$remove]); } } DOM\StyleInliner::setStyle($nodeChild, $styles); } } $node = $this->removeStyle($nodeChild, $styleToRemove); } return $node; } /** * Set new classes to nodes of block. * @param array $data Classes data array. * @return boolean */ public function setClasses($data) { if ($this->access < $this::ACCESS_V) { $this->error->addError( 'ACCESS_DENIED', Loc::getMessage('LANDING_BLOCK_ACCESS_DENIED') ); return false; } $doc = $this->getDom(); $manifest = $this->getManifest(); // detects position $positions = array(); foreach ((array)$data as $selector => $item) { if (strpos($selector, '@') !== false) { list($selector, $position) = explode('@', $selector); } else { $position = -1; } if ($position >= 0) { if (!isset($positions[$selector])) { $positions[$selector] = array(); } $positions[$selector][] = $position; } $data[$selector] = $item; } // wrapper (not realy exist) $wrapper = '#' . $this->getAnchor($this->id); // find available nodes by manifest from data $styles = array_merge( $manifest['style']['block'], $manifest['style']['nodes'] ); $styles[$wrapper] = array( // ); foreach ($styles as $selector => $node) { if (isset($data[$selector])) { // prepare data if (!is_array($data[$selector])) { $data[$selector] = array( $data[$selector] ); } if (!isset($data[$selector]['classList'])) { $data[$selector] = array( 'classList' => $data[$selector] ); } if (!isset($data[$selector]['affect'])) { $data[$selector]['affect'] = array(); } // apply classes to the block if ($selector == $wrapper) { $resultList = array( array_pop($doc->getChildNodesArray()) ); } // or by selector else { $resultList = $doc->querySelectorAll($selector); } foreach ($resultList as $pos => $resultNode) { if ( isset($positions[$selector]) && !in_array($pos, $positions[$selector]) ) { continue; } if ($resultNode) { if ($resultNode->getNodeType() == $resultNode::ELEMENT_NODE) { $resultNode->setClassName( implode(' ', $data[$selector]['classList']) ); } // affected styles if (!empty($data[$selector]['affect'])) { $this->removeStyle( $resultNode, $data[$selector]['affect'] ); } } } } } // save rebuild html as text $this->saveContent($doc->saveHTML()); return true; } /** * Set attributes to nodes of block. * @param array $data Attrs data array. * @return void */ public function setAttributes($data) { $doc = $this->getDom(); $manifest = $this->getManifest(); // wrapper (not realy exist) $wrapper = '#' . $this->getAnchor($this->id); // find available nodes by manifest from data $attrs = $manifest['attrs']; $attrs[$wrapper] = array( // ); // find attrs in style key if (isset($manifest['style']['nodes'])) { foreach ($manifest['style']['nodes'] as $selector => $item) { if ( isset($item['additional']['attrs']) && is_array($item['additional']['attrs']) ) { foreach ($item['additional']['attrs'] as $attr) { if (!isset($attrs[$selector])) { $attrs[$selector] = array(); } $attrs[$selector][] = $attr; } } } } // and in block styles if ( isset($manifest['style']['block']['additional']['attrs']) && is_array($manifest['style']['block']['additional']['attrs']) ) { foreach ($manifest['style']['block']['additional']['attrs'] as $attr) { if (!isset($attrs[$wrapper])) { $attrs[$wrapper] = array(); } $attrs[$wrapper][] = $attr; } } // and in cards key if (isset($manifest['cards'])) { foreach ($manifest['cards'] as $selector => $item) { if ( isset($item['additional']['attrs']) && is_array($item['additional']['attrs']) ) { foreach ($item['additional']['attrs'] as $attr) { if (!isset($attrs[$selector])) { $attrs[$selector] = array(); } $attrs[$selector][] = $attr; } } } } foreach ($attrs as $selector => $item) { if (isset($data[$selector])) { // not multi if (!isset($item[0])) { $item = array($item); } // prepare attrs (and group attrs) $attrItems = array(); foreach ($item as $key => $val) { if ( isset($val['attrs']) && is_array($val['attrs']) ) { foreach ($val['attrs'] as $groupAttr) { $item[] = $groupAttr; } unset($item[$key]); } } foreach ($item as $val) { if (!isset($val['attribute'])) { continue; } if (!isset($attrItems[$val['attribute']])) { $attrItems[$val['attribute']] = array(); } if (isset($data[$selector][$val['attribute']])) { $attrItems[$val['attribute']][-1] = $data[$selector][$val['attribute']]; } // cards else if (is_array($data[$selector])) { foreach ($data[$selector] as $pos => $card) { if (isset($card[$val['attribute']])) { $attrItems[$val['attribute']][$pos] = $card[$val['attribute']]; } } } } // set attrs to the block if ($selector == $wrapper) { $resultList = array( array_pop($doc->getChildNodesArray()) ); } // or by selector else { $resultList = $doc->querySelectorAll($selector); } foreach ($resultList as $pos => $resultNode) { foreach ($attrItems as $code => $val) { if (isset($val[-1])) { $val = $val[-1]; } else if (isset($val[$pos])) { $val = $val[$pos]; } else { continue; } $resultNode->setAttribute( \htmlspecialcharsbx($code), is_array($val) ? json_encode($val) : $val ); } } } } // save rebuild html as text $this->saveContent($doc->saveHTML()); } /** * Replace title and breadcrumb marker in the block. * @param string $content Some content. * @return string */ protected static function replaceMetaMarkers($content) { if (strpos($content, '#breadcrumb#') !== false) { ob_start(); $arResult = array( array( 'LINK' => '#', 'TITLE' => '' ), array( 'LINK' => '#', 'TITLE' => Loc::getMessage('LANDING_BLOCK_BR1') ), array( 'LINK' => '#', 'TITLE' => Loc::getMessage('LANDING_BLOCK_BR2') ), array( 'LINK' => '#', 'TITLE' => '' ) ); $tplId = Manager::getOption('site_template_id');//@todo fixme $strChainTemplate = getLocalPath('templates/' . $tplId . '/chain_template.php'); $strChainTemplate = Manager::getDocRoot() . $strChainTemplate; if (file_exists($strChainTemplate)) { echo include $strChainTemplate; } $breadcrumb = ob_get_contents(); ob_end_clean(); $content = str_replace( '#breadcrumb#', $breadcrumb, $content ); } if (strpos($content, '#title#') !== false) { $content = str_replace( '#title#', Loc::getMessage('LANDING_BLOCK_TITLE'), $content ); } return $content; } /** * Delete all blocks from db by codes. * @param array $code Array of codes to delete. * @return void */ public static function deleteByCode($code) { if (!is_array($code)) { $code = array($code); } $res = parent::getList(array( 'select' => array( 'ID' ), 'filter' => array( '=CODE' => $code ) )); while ($row = $res->fetch()) { self::parentDelete($row['ID']); } } /** * Delete block row. * @param int $id Block id. * @return \Bitrix\Main\Result */ private static function parentDelete($id) { return parent::delete($id); } /** * Add block row. * @param array $fields Block data. * @return \Bitrix\Main\Result */ public static function add($fields) { if ( !defined('LANDING_MUTATOR_MODE') || LANDING_MUTATOR_MODE !== true ) { throw new \Bitrix\Main\SystemException( 'Disabled for direct access.' ); } else { return parent::add($fields); } } /** * Update block row. * @param int $id Primary key. * @param array $fields Block data. * @return \Bitrix\Main\Result */ public static function update($id, $fields = array()) { if ( !defined('LANDING_MUTATOR_MODE') || LANDING_MUTATOR_MODE !== true ) { throw new \Bitrix\Main\SystemException( 'Disabled for direct access.' ); } else { return parent::update($id, $fields); } } /** * Delete block row. * @param int $id Primary key. * @return \Bitrix\Main\Result */ public static function delete($id) { if ( !defined('LANDING_MUTATOR_MODE') || LANDING_MUTATOR_MODE !== true ) { throw new \Bitrix\Main\SystemException( 'Disabled for direct access.' ); } else { return parent::delete($id); } } /** * Gets block's rows. * @param array $fields Block orm data. * @return \Bitrix\Main\DB\Result */ public static function getList($fields = array()) { if ( !defined('LANDING_MUTATOR_MODE') || LANDING_MUTATOR_MODE !== true ) { throw new \Bitrix\Main\SystemException( 'Disabled for direct access.' ); } else { return parent::getList($fields); } } }