%PDF- %PDF-
Direktori : /home/bitrix/www/bitrix/modules/landing/lib/update/block/ |
Current File : //home/bitrix/www/bitrix/modules/landing/lib/update/block/nodeattributes.php |
<? namespace Bitrix\Landing\Update\Block; use Bitrix\Landing\Manager; use Bitrix\Landing\Subtype\Form; use Bitrix\Main\Config\Option; use Bitrix\Main\Update\Stepper; use Bitrix\Landing\Block; use Bitrix\Landing\Internals\BlockTable; use Bitrix\Main\Entity; use Bitrix\Main\Localization\Loc; use Bitrix\Main\Loader; use Bitrix\Main\Web\DOM\Element; final class NodeAttributes extends Stepper { const CONTINUE_EXECUTING = true; const STOP_EXECUTING = false; const OPTION_NAME = 'blocks_attrs_update'; const OPTION_STATUS_NAME = 'blocks_attrs_update_status'; const STEP_PORTION = 10; //count of block CODES to step protected static $moduleId = 'landing'; protected $dataToUpdate = array(); protected $blocksToUpdate = array(); protected $sitesToUpdate = array(); protected $status = array(); protected $codesToStep = array(); /** * get progress from option or set default */ private function loadCurrentStatus() { // saved in option $this->status = Option::get('landing', self::OPTION_STATUS_NAME, ''); $this->status = ($this->status !== '' ? @unserialize($this->status) : array()); $this->status = (is_array($this->status) ? $this->status : array()); // or default if (empty($this->status)) { // get codes from all updaters options $count = 0; $params = array(); foreach (Option::getForModule('landing') as $key => $option) { if (strpos($key, self::OPTION_NAME) === 0 && $key != self::OPTION_STATUS_NAME) { $option = ($option !== '' ? @unserialize($option) : array()); // save params $params[$key] = $option['PARAMS']; // count of all blocks - to progress-bar $filter = array( 'CODE' => array_keys($option['BLOCKS']), 'DELETED' => 'N', ); if ( isset($option['PARAMS']['UPDATE_PUBLISHED_SITES']) && $option['PARAMS']['UPDATE_PUBLISHED_SITES'] != 'Y' ) { $filter['PUBLIC'] = 'N'; } $res = BlockTable::getList(array( 'select' => array( new \Bitrix\Main\Entity\ExpressionField( 'CNT', 'COUNT(*)' ), ), 'filter' => $filter, )); if ($row = $res->fetch()) { $count += $row['CNT']; } } } $this->status['COUNT'] = $count; $this->status['STEPS'] = 0; $this->status['SITES_TO_UPDATE'] = array(); $this->status['UPDATER_ID'] = ''; $this->status['PARAMS'] = $params; Option::set('landing', self::OPTION_STATUS_NAME, $this->status); } } /** * May be several Options for update data. They have uniqueId. Find ID of first option * * @return string */ private function getUpdaterUniqueId() { // continue processing current updater if ($this->status['UPDATER_ID'] !== '') { return $this->status['UPDATER_ID']; } $updaterOptions = Option::getForModule('landing'); $allOptions = preg_grep('/' . self::OPTION_NAME . '.+/', array_keys($updaterOptions)); $allOptions = array_diff($allOptions, array(self::OPTION_STATUS_NAME)); // remove status option from list sort($allOptions); if (!empty($allOptions)) { return str_replace(self::OPTION_NAME, '', $allOptions[0]); } else { return ''; } } public function execute(array &$result) { // dbg // self::log('UPDATER START'); // nothing to update $this->loadCurrentStatus(); // dbg self::log('UPDATER START', $this->status); if (!$this->status['COUNT']) { self::finish(); return self::STOP_EXECUTING; } // find option. If nothing - we update all $this->status['UPDATER_ID'] = $this->getUpdaterUniqueId(); // dbg // self::log('unique id', $this->status['UPDATER_ID']); if (!$this->status['UPDATER_ID']) { self::finish(); return self::STOP_EXECUTING; } $this->processBlocks(); // was processing all data for current option if (!is_array($this->dataToUpdate['BLOCKS']) || empty($this->dataToUpdate['BLOCKS'])) { $this->finishOption(); } // dbg self::log('UPDATER before CONTINUE (status)', $this->status); $result['count'] = $this->status['COUNT']; $result['steps'] = $this->status['STEPS']; return self::CONTINUE_EXECUTING; } /** * Additional operations before stop executing */ private static function finish() { // dbg self::log('UPDATER FINISH'); self::clearOptions(); self::removeCustomEvents(); } /** * If no more blocks to update - remove all data options and status * * @throws \Bitrix\Main\ArgumentNullException */ private static function clearOptions() { foreach (Option::getForModule('landing') as $key => $option) { if (strpos($key, self::OPTION_NAME) === 0) { Option::delete('landing', array('name' => $key)); } } } /** * Create option name by base name and unique ID * @return string */ private function getOptionName() { return self::OPTION_NAME . $this->status['UPDATER_ID']; } private function collectBlocks() { // dbg // self::log(''); $this->dataToUpdate = Option::get(self::$moduleId, $this->getOptionName()); $this->dataToUpdate = ($this->dataToUpdate !== '' ? @unserialize($this->dataToUpdate) : array()); // dbg self::log('data to update collected', $this->dataToUpdate); $this->codesToStep = array_unique(array_keys($this->dataToUpdate['BLOCKS'])); $this->codesToStep = array_slice($this->codesToStep, 0, self::STEP_PORTION); // load BLOCKS $filter = array( 'CODE' => $this->codesToStep, 'DELETED' => 'N', ); if ( isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) && $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] != 'Y' ) { $filter['PUBLIC'] = 'N'; } $resBlock = BlockTable::getList(array( 'filter' => $filter, 'select' => array( 'ID', 'SORT', 'CODE', 'ACTIVE', 'PUBLIC', 'DELETED', 'CONTENT', 'LID', 'SITE_ID' => 'LANDING.SITE_ID', ), 'order' => array( 'CODE' => 'ASC', 'ID' => 'ASC', ), )); while ($row = $resBlock->fetch()) { $this->blocksToUpdate[$row['CODE']][$row['ID']] = new Block($row['ID'], $row); if (count($this->blocksToUpdate) > self::STEP_PORTION) { unset($this->blocksToUpdate[$row['CODE']]); break; } // save sites ID for current blocks to reset cache later $this->sitesToUpdate[$row['ID']] = $row['SITE_ID']; } // dbg // self::log('blocks to update are found'); } private function processBlocks() { // dbg self::log('process blocks start'); $this->collectBlocks(); foreach ($this->blocksToUpdate as $code => $blocks) { foreach ($blocks as $block) { // dbg // self::log('update BLOCK BEFORE (check exist data)', array('code' => $code)); if (is_array($this->dataToUpdate['BLOCKS'][$code]) && !empty($this->dataToUpdate['BLOCKS'][$code])) { $this->updateBlock($block); // after processing block save site ID to update cache later (only if update needed) if ( isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) && $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] == 'Y' ) { $this->status['SITES_TO_UPDATE'][] = $this->sitesToUpdate[$block->getId()]; } } $this->status['STEPS']++; } } $this->finishStep(); } private function updateBlock(Block $block) { // dbg self::log('update BLOCK START', array( 'code' => $block->getCode(), 'id' => $block->getId(), 'dataToUpdate' => $this->dataToUpdate['BLOCKS'][$block->getCode()], )); $code = $block->getCode(); $doc = $block->getDom(); $wrapper = Block::getAnchor($block->getId()); // save to journal $eventLog = new \CEventLog; $eventLog->Add(array( "SEVERITY" => $eventLog::SEVERITY_SECURITY, "AUDIT_TYPE_ID" => 'LANDING_BLOCK_BEFORE_UPDATE', "MODULE_ID" => "landing", "ITEM_ID" => 'landing_block_' . $block->getId(), "DESCRIPTION" => $doc->getInnerHTML(), )); foreach ($this->dataToUpdate['BLOCKS'][$code]['NODES'] as $selector => $rules) { // apply to the all block or by selector if ($selector == $wrapper) { $resultList = array( array_pop($doc->getChildNodesArray()), ); } else { $resultList = $doc->querySelectorAll($selector); } // prepare ATTRS $nodeAttrs = array(); if (is_array($rules['ATTRS_ADD']) && !empty($rules['ATTRS_ADD'])) { $nodeAttrs = array_merge($nodeAttrs, $rules['ATTRS_ADD']); } if (is_array($rules['ATTRS_REMOVE']) && !empty($rules['ATTRS_REMOVE'])) { $nodeAttrs = array_merge($nodeAttrs, array_fill_keys(array_values($rules['ATTRS_REMOVE']), '')); } // PROCESS foreach ($resultList as $nth => $resultNode) { // FILTER // use until cant add some filters in DOM\Parser if (is_array($rules['FILTER']) && !empty($rules['FILTER'])) { $notFilterd = false; // By content. May have 'NOT' key if ( isset($rules['FILTER']['CONTENT']) && is_array($rules['FILTER']['CONTENT']) && ( $rules['FILTER']['CONTENT']['VALUE'] != $resultNode->getInnerHTML() || ( $rules['FILTER']['CONTENT']['NOT'] && $rules['FILTER']['CONTENT']['VALUE'] == $resultNode->getInnerHTML() ) ) ) { $notFilterd = true; } // by position in DOM if ( isset($rules['FILTER']['NTH']) && is_array($rules['FILTER']['NTH']) && isset($rules['FILTER']['NTH']['VALUE']) && $nth + 1 != $rules['FILTER']['NTH']['VALUE'] ) { $notFilterd = true; } if ($notFilterd) { continue; } } // CLASSES $classesChange = false; $nodeClasses = $resultNode->getClassList(); if (is_array($rules['CLASSES_REMOVE']) && !empty($rules['CLASSES_REMOVE'])) { $nodeClasses = array_diff($nodeClasses, $rules['CLASSES_REMOVE']); $classesChange = true; } if (is_array($rules['CLASSES_ADD']) && !empty($rules['CLASSES_ADD'])) { $nodeClasses = array_merge($nodeClasses, $rules['CLASSES_ADD']); $classesChange = true; } $nodeClasses = array_unique($nodeClasses); if ($classesChange) { $resultNode->setClassName(implode(' ', $nodeClasses)); } // ID if ($rules['ID_REMOVE'] && $rules['ID_REMOVE'] == 'Y') { $resultNode->removeAttribute('id'); } // ATTRS foreach ($nodeAttrs as $name => $value) { // reduce string (in attributes may be a complex data) $value = str_replace(array("\n", "\t"), "", $value); if ($value) { $resultNode->setAttribute($name, is_array($value) ? json_encode($value) : $value); } else { $resultNode->removeAttribute($name); } } // REMOVE NODE if (isset($rules['NODE_REMOVE']) && $rules['NODE_REMOVE'] === true) { $resultNode->getParentNode()->removeChild($resultNode); } // REPLACE CONTENT by regexp // be CAREFUL! if ( isset($rules['REPLACE_CONTENT']) && is_array($rules['REPLACE_CONTENT']) && array_key_exists('regexp', $rules['REPLACE_CONTENT']) && array_key_exists('replace', $rules['REPLACE_CONTENT']) ) { $innerHtml = $resultNode->getInnerHTML(); $innerHtml = preg_replace($rules['REPLACE_CONTENT']['regexp'], $rules['REPLACE_CONTENT']['replace'], $innerHtml); if(strlen($innerHtml) > 0) { $resultNode->setInnerHTML($innerHtml); } } } // add CONTAINER around nodes. if ( isset($rules['CONTAINER_ADD']) && is_array($rules['CONTAINER_ADD']) && isset($rules['CONTAINER_ADD']['CLASSES']) && !empty($resultList) ) { if (!is_array($rules['CONTAINER_ADD']['CLASSES'])) { $rules['CONTAINER_ADD']['CLASSES'] = [$rules['CONTAINER_ADD']['CLASSES']]; } // check if container exist $firstNode = $resultList[0]; $parentNode = $firstNode->getParentNode(); $parentClasses = $parentNode->getClassList(); if (empty(array_diff($rules['CONTAINER_ADD']['CLASSES'], $parentClasses))) { break; } // param TO_EACH - add container to each element. Default (false) - add container once to all nodes if (!isset($rules['CONTAINER_ADD']['TO_EACH']) || $rules['CONTAINER_ADD']['TO_EACH'] !== true) { $containerNode = new Element($rules['CONTAINER_ADD']['TAG'] ? $rules['CONTAINER_ADD']['TAG'] : 'div'); $containerNode->setOwnerDocument($doc); $containerNode->setClassName(implode(' ', $rules['CONTAINER_ADD']['CLASSES'])); $parentNode->insertBefore($containerNode, $firstNode); foreach ($resultList as $resultNode) { $parentNode->removeChild($resultNode); $containerNode->appendChild($resultNode); } } else { foreach ($resultList as $resultNode) { $containerNode = new Element($rules['CONTAINER_ADD']['TAG'] ? $rules['CONTAINER_ADD']['TAG'] : 'div'); $containerNode->setOwnerDocument($doc); $containerNode->setClassName(implode(' ', $rules['CONTAINER_ADD']['CLASSES'])); $parentNode->insertBefore($containerNode, $resultNode); $parentNode->removeChild($resultNode); $containerNode->appendChild($resultNode); } } } } // dbg // self::log('update block before save content'); $block->saveContent($doc->saveHTML()); // updates COMPONENTS params if (is_array($this->dataToUpdate['BLOCKS'][$code]['UPDATE_COMPONENTS'])) { foreach ($this->dataToUpdate['BLOCKS'][$code]['UPDATE_COMPONENTS'] as $selector => $params) { $block->updateNodes(array($selector => $params)); } } // if need remove PHP - we must use block content directly, not DOM parser if ( $this->dataToUpdate['BLOCKS'][$code]['CLEAR_PHP'] && $this->dataToUpdate['BLOCKS'][$code]['CLEAR_PHP'] == 'Y' ) { // dbg self::log('update block before clear PHP'); $content = $block->getContent(); $content = preg_replace('/<\?.*\?>/s', '', $content); $block->saveContent($content); } // change block SORT if ( $this->dataToUpdate['BLOCKS'][$code]['SET_SORT'] && is_numeric($this->dataToUpdate['BLOCKS'][$code]['SET_SORT']) ) { // dbg self::log('update block before set sort'); $block->setSort($this->dataToUpdate['BLOCKS'][$code]['SET_SORT']); } // dbg // self::log('update block before save'); $block->save(); // dbg self::log('update BLOCK FINISH', array('code' => $block->getCode())); } private function finishStep() { // dbg self::log('finish STEP (codes to step)', array( 'codesToStep' => $this->codesToStep, 'status' => $this->status, )); // processed blocks must be removed from data foreach ($this->codesToStep as $code) { unset($this->dataToUpdate['BLOCKS'][$code]); } Option::set('landing', $this->getOptionName(), serialize($this->dataToUpdate)); Option::set('landing', self::OPTION_STATUS_NAME, serialize($this->status)); } private function finishOption() { // clean cloud sites cache only if needed $this->updateSites(); // dbg // self::log('UPDATER FINISH OPTION before', array('optionName' => $this->getOptionName())); // finish current updater id, try next Option::delete('landing', array('name' => $this->getOptionName())); $this->status['SITES_TO_UPDATE'] = array(); $this->status['UPDATER_ID'] = ''; Option::set('landing', self::OPTION_STATUS_NAME, serialize($this->status)); // dbg self::log('UPDATER FINISH OPTION', array('optionName' => $this->getOptionName(), 'status' => $this->status)); } private function updateSites() { if ( isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) && $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] == 'Y' && Loader::includeModule('bitrix24') && false // dbg: need this? ) { foreach (array_unique($this->status['SITES_TO_UPDATE']) as $siteId) { if (intval($siteId)) { // Site::update($siteId, array()); } } } } /** * Before delete block handler. * @param Entity\Event $event Event instance. * @return Entity\EventResult */ public static function disableBlockDelete(Entity\Event $event) { if (\Bitrix\Landing\Update\Stepper::checkAgentActivity('\Bitrix\Landing\Update\Block\NodeAttributes')) { $result = new Entity\EventResult(); $result->setErrors(array( new Entity\EntityError( Loc::getMessage('LANDING_BLOCK_DISABLE_DELETE'), 'BLOCK_DISABLE_DELETE' ), )); return $result; } else { self::removeCustomEvents(); } } /** * Before publication landing handler. * @param \Bitrix\Main\Event $event Event instance. * @return Entity\EventResult */ public static function disablePublication(\Bitrix\Main\Event $event) { if (\Bitrix\Landing\Update\Stepper::checkAgentActivity('\Bitrix\Landing\Update\Block\NodeAttributes')) { $result = new Entity\EventResult; $result->setErrors(array( new \Bitrix\Main\Entity\EntityError( Loc::getMessage('LANDING_DISABLE_PUBLICATION'), 'LANDING_DISABLE_PUBLICATION' ), )); return $result; } else { self::removeCustomEvents(); } } /** * If agent not exist - we must broke events, to preserve infinity blocking publication and delete */ public static function removeCustomEvents() { $eventManager = \Bitrix\Main\EventManager::getInstance(); $eventManager->unregisterEventHandler( 'landing', '\Bitrix\Landing\Internals\Block::OnBeforeDelete', 'landing', '\Bitrix\Landing\Update\Block\NodeAttributes', 'disableBlockDelete'); $eventManager->unregisterEventHandler( 'landing', 'onLandingPublication', 'landing', '\Bitrix\Landing\Update\Block\NodeAttributes', 'disablePublication' ); } /** * Update form domain, when updated b24 connector * F.e., when user remove and new portal * Other method, because different params * * @param Event $event */ public static function updateFormDomainByConnector($event) { self::updateFormDomain(); } /** * Set data for NodeUpdater to updating form domain * * @param array $domains * @throws \Bitrix\Main\ArgumentException * @throws \Bitrix\Main\ArgumentNullException * @throws \Bitrix\Main\ArgumentOutOfRangeException * @throws \Bitrix\Main\ObjectPropertyException * @throws \Bitrix\Main\SystemException */ public static function updateFormDomain($domains = array()) { // method may call from event or manual if (is_array($domains) && $domains['new_domain']) { $newDomain = $domains['new_domain']; } else { $newDomain = Form::getOriginalFormDomain(); } // something wrong if (!$newDomain) { return; } // find all CRM FORM blocks $toUpdater = array( 'PARAMS' => array( 'UPDATE_PUBLISHED_SITES' => 'Y', 'BLOCKS' => array(), ), ); // collect form blocks by content $resBlock = BlockTable::getList(array( 'filter' => array( 'DELETED' => 'N', '%=CONTENT' => '%data-b24form-original-domain%', ), 'select' => array('ID', 'CODE'), )); foreach ($resBlock as $block) { $toUpdater['BLOCKS'][$block['CODE']] = array( 'NODES' => array( '.bitrix24forms ' => array( 'ATTRS_ADD' => array('data-b24form-original-domain' => $newDomain), ), ), ); } // register UPDATER $updaterUniqueId = time(); while (true) { if (\Bitrix\Main\Config\Option::get('landing', self::OPTION_NAME . $updaterUniqueId) == '') { break; } $updaterUniqueId++; } \Bitrix\Main\Config\Option::set('landing', self::OPTION_NAME . $updaterUniqueId, serialize($toUpdater)); \Bitrix\Main\Update\Stepper::bindClass('\Bitrix\Landing\Update\Block\NodeAttributes', 'landing', 10); } /** * Do nothing for fix bug. * @return string */ // dbg: try to fix. May del this // public static function execAgent() // { // return ''; // } /** * @param $name * @param array $data * @throws \Bitrix\Main\Db\SqlQueryException * * @return void */ private static function log($name, $data = array()) { // only for clouds if (!Manager::isB24()) { return; } $data = serialize($data); $connection = \Bitrix\Main\Application::getConnection(); if (!$connection->isTableExists('b_landing_nodeupdater_log')) { $connection->query(" CREATE TABLE `b_landing_nodeupdater_log` ( `ID` int NOT NULL AUTO_INCREMENT PRIMARY KEY, `NAME` varchar(255) NULL, `DATA` text NULL, `DATE` timestamp NULL DEFAULT NOW() ); "); } $name = $connection->getSqlHelper()->forSql($name); $data = $connection->getSqlHelper()->forSql($data); $query = "INSERT INTO `b_landing_nodeupdater_log` (`NAME`, `DATA`) VALUES ('$name', '$data');"; $dbRes = $connection->query($query); } /** * Code for updater.php see in landing/dev/updater */ } ?>