_initMade) { return ; } // 1. get block information $block_info = $this->Application->GetVar('block'); list ($this->_blockName, $this->_functionName) = explode(':', $block_info); $this->_parseTemplate($object); if (array_key_exists($this->_functionName, $this->Application->Parser->ElementLocations)) { $this->_blockLocation = $this->Application->Parser->ElementLocations[$this->_functionName]; } $this->_initMade = true; } function _getSourceTemplate() { // get source template $t = $this->Application->GetVar('source'); if (!$this->Application->TemplatesCache->TemplateExists($t)) { /** @var CategoriesEventHandler $cms_handler */ $cms_handler = $this->Application->recallObject('st_EventHandler'); $t = ltrim($cms_handler->GetDesignTemplate($t), '/'); } $this->_sourceTemplate = $t; } function _getThemeName() { $config = $this->Application->getUnitConfig('theme'); $theme_id = (int)$this->Application->GetVar('theme_id'); $sql = 'SELECT Name FROM ' . $config->getTableName() . ' WHERE ' . $config->getIDField() . ' = ' . $theme_id; return $this->Conn->GetOne($sql); } /** * Render source template to get parse errors OR it's element locations * * @param kDBItem $object * @param string $append * @return bool */ function _parseTemplate(&$object, $append = '') { try { // 1. parse template $this->Application->InitParser( $this->_getThemeName() ); // we have no parser when saving block content $this->_getSourceTemplate(); // 2. design templates have leading "/" in the beginning $this->Application->Parser->Run($this->_sourceTemplate . $append); } catch (ParserException $e) { $this->_parseErrors[] = Array ('msg' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()); } if ($this->_parseErrors) { if ($this->_isMainTemplate()) { // 2.1. delete temporary file, that was parsed $filename = $this->_getTemplateFile(false, $append . '.tpl'); if (!unlink($filename)) { $error_file = $this->_getTemplateFile(true, $append . '.tpl'); $object->SetError('FileContents', 'template_delete_failed', '+Failed to delete temporary template "' . $error_file . '"'); return false; } } else { // 2.2. restore backup if (!rename($this->_getTemplateFile(false, '.tpl.bak'), $this->_getTemplateFile(false))) { $error_file = $this->_getTemplateFile(true); $object->SetError('FileContents', 'template_restore_failed', '+Failed to restore template "' . $error_file . '" from backup.'); return false; } } return false; } return true; } /** * Move elements in template and save changes, when possible * * @param Array $target_order * @return bool */ function moveTemplateElements($target_order) { // 2. parse template $this->Application->InitParser(); // we have no parser when saving block content $this->_getSourceTemplate(); $filename = $this->Application->TemplatesCache->GetRealFilename($this->_sourceTemplate) . '.tpl'; if (!is_writable($filename)) { // we can't save changes, don't bother calculating new template contents return false; } $data = file_get_contents($filename); $line_ending = strpos($data, "\r") !== false ? "\r\n" : "\n"; // 1. get location of movable areas $mask = ''; $start_pos = 0; $elements = $area = Array (); $areas = $this->_getDivPairs($data, 'movable-area'); foreach ($areas as $area_index => $area) { // 1.1. get locations of all movable elements inside given area $area_content = substr($area['data'], $area['open_len'], -$area['close_len']); $elements = array_merge($elements, $this->_getDivPairs($area_content, 'movable-element', $area_index, $area['open_pos'] + $area['open_len'])); // 1.2. prepare mask to place movable elements into (don't include movable area div ifself) $mask .= "\t" . substr($data, $start_pos, $area['open_pos'] + $area['open_len'] - $start_pos) . $line_ending . "\t\t" . '#AREA' . $area_index . '#' . $line_ending; $start_pos = $area['close_pos'] - $area['close_len']; } $mask = trim($mask . "\t" . substr($data, $area['close_pos'] - $area['close_len'])); if (!$elements) { // no elements found return false; } foreach ($areas as $area_index => $area) { $area_content = ''; $target_elements = $target_order[$area_index]; foreach ($target_order[$area_index] as $old_location) { $area_content .= $elements[$old_location]['data'] . $line_ending . "\t\t"; } $mask = str_replace('#AREA' . $area_index . '#', trim($area_content), $mask); } $fp = fopen($filename, 'w'); fwrite($fp, $mask); fclose($fp); return true; } /** * Extracts div pairs with given class from given text * * @param string $data * @param string $class * @param int $area * @param int $offset * @return Array */ function _getDivPairs(&$data, $class, $area = null, $offset = 0) { preg_match_all('/(]*>)|(<\/div>)/s', $data, $divs, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); $deep_level = 0; $pairs = Array (); $skip_count = Array (); // by deep level! foreach ($divs as $div) { if (strpos($div[0][0], '/') === false) { // opening div $skip_count[$deep_level] = 0; if (strpos($div[0][0], $class) !== false) { // ours opening (this deep level) -> save $pair = Array ('open_pos' => $div[0][1], 'open_len' => strlen($div[0][0])); } else { // not ours opening -> skip next closing (this deep level) $skip_count[$deep_level]++; } $deep_level++; } else { // closing div $deep_level--; if ($skip_count[$deep_level] == 0) { // nothing to skip (this deep level) -> save $pair['close_len'] = strlen($div[0][0]); $pair['close_pos'] = $div[0][1] + $pair['close_len']; $pair['data'] = substr($data, $pair['open_pos'], $pair['close_pos'] - $pair['open_pos']); if (isset($area)) { $pair['open_pos'] += $offset; $pair['close_pos'] += $offset; // index indicates area $pairs['a' . $area . 'e' . count($pairs)] = $pair; } else { $pairs[] = $pair; } } else { // skip closing div as requested $skip_count[$deep_level]--; } } } return $pairs; } /** * Returns information about parser element locations in template * * @param string $info_type * @return mixed */ function blockInfo($info_type) { switch ($info_type) { case 'block_name': return $this->_blockName; break; case 'function_name': return $this->_functionName; break; case 'start_pos': case 'end_pos': case 'template': if (!array_key_exists($info_type, $this->_blockLocation)) { // invalid block name return 'invalid block name'; } return $this->_blockLocation[$info_type]; break; case 'template_file': return $this->_getTemplateFile(true); break; case 'content': $template_body = file_get_contents( $this->_getTemplateFile() ); $length = $this->_blockLocation['end_pos'] - $this->_blockLocation['start_pos']; return substr($template_body, $this->_blockLocation['start_pos'], $length); break; } return 'undefined'; } /** * Main template being edited (parse copy, instead of original) * * @return bool */ function _isMainTemplate() { return $this->_blockLocation['template'] == $this->_sourceTemplate; } /** * Returns filename, that contains template, where block is located * * @param bool $relative * @param string $extension * @return string */ function _getTemplateFile($relative = false, $extension = '.tpl') { $filename = $this->Application->TemplatesCache->GetRealFilename( $this->_blockLocation['template'] ) . $extension; if ($relative) { $filename = preg_replace('/^' . preg_quote(FULL_PATH, '/') . '/', '', $filename, 1); } return $filename; } /** * Saves new version of block to template, where it's located * * @param kDBItem $object */ function saveBlock(&$object) { $main_template = $this->_isMainTemplate(); $filename = $this->_getTemplateFile(false); // 1. get new template content $new_template_body = $this->_getNewTemplateContent($object, $filename, $lines_before); if (is_bool($new_template_body) && ($new_template_body === true)) { // when nothing changed -> stop processing return true; } // 2. backup original template if (!$main_template && !copy($filename, $filename . '.bak')) { // backup failed $error_file = $this->_getTemplateFile(true, '.tpl.bak'); $object->SetError('FileContents', 'template_backup_failed', '+Failed to create backup template "' . $error_file . '" backup.'); return false; } // 3. save changed template $save_filename = $this->_getTemplateFile(false, $main_template ? '.tmp.tpl' : '.tpl'); $fp = fopen($save_filename, 'w'); if (!$fp) { // backup template create failed OR existing template save $error_file = $this->_getTemplateFile(true, $main_template ? '.tmp.tpl' : '.tpl'); $object->SetError('FileContents', 'template_changes_save_failed', '+Failed to save template "' . $error_file . '" changes.'); return false; } fwrite($fp, $new_template_body); fclose($fp); // 3. parse template to check for errors $this->_parseTemplate($object, $main_template ? '.tmp' : ''); if ($this->_parseErrors) { $error_msg = Array (); foreach ($this->_parseErrors as $error_data) { if (preg_match('/line ([\d]+)/', $error_data['msg'], $regs)) { // another line number inside message -> patch it $error_data['msg'] = str_replace('line ' . $regs[1], 'line ' . ($regs[1] - $lines_before), $error_data['msg']); } $error_msg[] = $error_data['msg'] . ' at line ' . ($error_data['line'] - $lines_before); } $object->SetError('FileContents', 'template_syntax_error', '+Template syntax errors:
' . implode('
', $error_msg)); return false; } if ($main_template) { // 4.1. replace original file with temporary if (!rename($this->_getTemplateFile(false, '.tmp.tpl'), $filename)) { // failed to save new content to original template $error_file = $this->_getTemplateFile(true); $object->SetError('FileContents', 'template_save_failed', '+Failed to save template "' . $error_file . '".'); return false; } } else { // 4.2. delete backup unlink( $this->_getTemplateFile(false, '.tpl.bak') ); } return true; } /** * Returns new template content of "true", when nothing is changed * * @param kDBItem $object * @param string $filename * @param int $lines_before * @return mixed */ function _getNewTemplateContent(&$object, $filename, &$lines_before) { $new_content = $object->GetDBField('FileContents'); $template_body = file_get_contents($filename); $lines_before = substr_count(substr($template_body, 0, $this->_blockLocation['start_pos']), "\n"); $new_template_body = substr($template_body, 0, $this->_blockLocation['start_pos']) . $new_content . substr($template_body, $this->_blockLocation['end_pos']); return crc32($template_body) == crc32($new_template_body) ? true : $new_template_body; } }