themesFolder = FULL_PATH.'/themes'; } /** * Updates file system changes to database for selected theme * * @param string $theme_name * * @return mixed returns ID of created/used theme or false, if none created */ function refreshTheme($theme_name) { if (!file_exists($this->themesFolder . '/' . $theme_name)) { // requested theme was not found on hdd return false; } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT * FROM ' . $table_name . ' WHERE Name = ' . $this->Conn->qstr($theme_name); $theme_info = $this->Conn->GetRow($sql); if ($theme_info) { $theme_id = $theme_info[$id_field]; $theme_enabled = $theme_info['Enabled']; } else { $theme_id = $theme_enabled = false; } $this->themeFiles = Array (); if ($theme_id) { if (!$theme_enabled) { // don't process existing theme files, that are disabled return $theme_id; } // reset found mark for every themes file (if theme is not new) $sql = 'UPDATE '.TABLE_PREFIX.'ThemeFiles SET FileFound = 0 WHERE ThemeId = '.$theme_id; $this->Conn->Query($sql); // get all theme files from db $sql = 'SELECT FileId, CONCAT(FilePath, "/", FileName) AS FullPath FROM '.TABLE_PREFIX.'ThemeFiles WHERE ThemeId = '.$theme_id; $this->themeFiles = $this->Conn->GetCol($sql, 'FullPath'); } else { // theme was not found in db, but found on hdd -> create new $theme_info = Array ( 'Name' => $theme_name, 'Enabled' => 0, 'Description' => $theme_name, 'PrimaryTheme' => 0, 'CacheTimeout' => 3600, // not in use right now 'StylesheetId' => 0, // not in use right now 'LanguagePackInstalled' => 0 ); $this->Conn->doInsert($theme_info, $table_name); $theme_id = $this->Conn->getInsertID(); if (!$theme_enabled) { // don't process newly created theme files, because they are disabled return $theme_id; } } $this->_themeNames[$theme_id] = $theme_name; $theme_path = $this->themesFolder.'/'.$theme_name; $this->FindThemeFiles('', $theme_path, $theme_id); // search from base theme directory // delete file records from db, that were not found on hdd $sql = 'DELETE FROM '.TABLE_PREFIX.'ThemeFiles WHERE ThemeId = '.$theme_id.' AND FileFound = 0'; $this->Conn->Query($sql); // install language packs, associated with theme (if any found and wasn't aready installed) if (!$theme_info['LanguagePackInstalled']) { $this->installThemeLanguagePack($theme_path); $fields_hash = Array ( 'LanguagePackInstalled' => 1, ); $this->Conn->doUpdate($fields_hash, $table_name, $id_field . ' = ' . $theme_id); } $fields_hash = Array ( 'TemplateAliases' => serialize( $this->getTemplateAliases($theme_id, $theme_path) ), ); $this->Conn->doUpdate($fields_hash, $table_name, $id_field . ' = ' . $theme_id); return $theme_id; } /** * Installs module(-s) language pack for given theme * * @param string $theme_path * @param string|bool $module_name * @return void */ function installThemeLanguagePack($theme_path, $module_name = false) { if ( $module_name === false ) { $modules = $this->Application->ModuleInfo; } else { $modules = Array ($module_name => $this->Application->ModuleInfo[$module_name]); } $language_import_helper =& $this->Application->recallObject('LanguageImportHelper'); /* @var $language_import_helper LanguageImportHelper */ foreach ($modules as $module_name => $module_info) { if ( $module_name == 'In-Portal' ) { continue; } $lang_file = $theme_path . '/' . $module_info['TemplatePath'] . '_install/english.lang'; if ( file_exists($lang_file) ) { $language_import_helper->performImport($lang_file, '|0|', '', LANG_SKIP_EXISTING); } } } /** * Returns template aliases from "/_install/theme.xml" files in theme * * @param int $theme_id * @param string $theme_path * @return Array * @access protected */ protected function getTemplateAliases($theme_id, $theme_path) { $template_aliases = Array (); $xml_parser =& $this->Application->recallObject('kXMLHelper'); /* @var $xml_parser kXMLHelper */ foreach ($this->Application->ModuleInfo as $module_name => $module_info) { if ( $module_name == 'In-Portal' ) { continue; } $xml_file = $theme_path . '/' . $module_info['TemplatePath'] . '_install/theme.xml'; if ( file_exists($xml_file) ) { $xml_data = file_get_contents($xml_file); $root_node =& $xml_parser->Parse($xml_data); if ( !is_object($root_node) || !is_a($root_node, 'kXMLNode') || !$root_node->Children ) { // broken xml OR no aliases defined continue; } $current_node =& $root_node->firstChild; do { $template_path = trim($current_node->Data); $alias = '#' . $module_info['TemplatePath'] . strtolower($current_node->Name) . '#'; // remember alias in global theme mapping $template_aliases[$alias] = $template_path; // store alias in theme file record to use later in design dropdown $t_parts = Array ('path' => dirname($template_path) == '.' ? '' : '/' . dirname($template_path), 'file' => basename($template_path),); $sql = 'UPDATE ' . TABLE_PREFIX . 'ThemeFiles SET TemplateAlias = ' . $this->Conn->qstr($alias) . ' WHERE (ThemeId = ' . $theme_id . ') AND (FilePath = ' . $this->Conn->qstr($t_parts['path']) . ') AND (FileName = ' . $this->Conn->qstr($t_parts['file'] . '.tpl') . ')'; $this->Conn->Query($sql); } while ( ($current_node =& $current_node->NextSibling()) ); } } return $template_aliases; } /** * Installs given module language pack and refreshed it from all themes * * @param string $module_name */ function syncronizeModule($module_name) { $sql = 'SELECT `Name`, ThemeId FROM ' . TABLE_PREFIX . 'Theme'; $themes = $this->Conn->GetCol($sql, 'ThemeId'); if (!$themes) { return ; } foreach ($themes as $theme_id => $theme_name) { $theme_path = $this->themesFolder . '/' . $theme_name; // install language pack $this->installThemeLanguagePack($theme_path, $module_name); // update TemplateAliases mapping $fields_hash = Array ( 'TemplateAliases' => serialize( $this->getTemplateAliases($theme_id, $theme_path) ), ); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'Theme', 'ThemeId = ' . $theme_id); } } /** * Searches for new templates (missing in db) in spefied folder * * @param string $folder_path subfolder of searchable theme * @param string $theme_path theme path from web server root * @param int $theme_id id of theme we are scanning * @param int $auto_structure_mode */ function FindThemeFiles($folder_path, $theme_path, $theme_id, $auto_structure_mode = 1) { $ignore_regexp = $this->getIgnoreRegexp($theme_path . $folder_path); $iterator = new DirectoryIterator($theme_path . $folder_path . '/'); /* @var $file_info DirectoryIterator */ foreach ($iterator as $file_info) { $filename = $file_info->getFilename(); $auto_structure = preg_match($ignore_regexp, $filename) ? 2 : $auto_structure_mode; $file_path = $folder_path . '/' . $filename; // don't pass path to theme top folder! if ( $file_info->isDir() && !$file_info->isDot() && $filename != 'CVS' && $filename != '.svn' ) { $this->FindThemeFiles($file_path, $theme_path, $theme_id, $auto_structure); } elseif ( pathinfo($filename, PATHINFO_EXTENSION) == 'tpl' ) { $meta_info = $this->_getTemplateMetaInfo(trim($file_path, '/'), $theme_id); $file_id = isset($this->themeFiles[$file_path]) ? $this->themeFiles[$file_path] : false; $file_description = array_key_exists('desc', $meta_info) ? $meta_info['desc'] : ''; if ($file_id) { // file was found in db & on hdd -> mark as existing $fields_hash = Array ( 'FileFound' => 1, 'Description' => $file_description, 'FileType' => $auto_structure, 'FileMetaInfo' => serialize($meta_info), ); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ThemeFiles', 'FileId = ' . $file_id); } else { // file was found on hdd, but missing in db -> create new file record $fields_hash = Array ( 'ThemeId' => $theme_id, 'FileName' => $filename, 'FilePath' => $folder_path, 'Description' => $file_description, 'FileType' => $auto_structure, // 1 - built-in, 0 - custom (not in use right now), 2 - skipped in structure 'FileMetaInfo' => serialize($meta_info), 'FileFound' => 1, ); $this->Conn->doInsert($fields_hash, TABLE_PREFIX.'ThemeFiles'); $this->themeFiles[$file_path] = $this->Conn->getInsertID(); } // echo 'FilePath: ['.$folder_path.']; FileName: ['.$filename.']; IsNew: ['.($file_id > 0 ? 'NO' : 'YES').']
'; } } } /** * Returns single regular expression to match all ignore patters, that are valid for given folder * * @param string $folder_path * @return string */ protected function getIgnoreRegexp($folder_path) { // always ignore design and element templates $ignore = '\.des\.tpl$|\.elm\.tpl$'; $sms_ignore_file = $folder_path . '/.smsignore'; if ( file_exists($sms_ignore_file) ) { $manual_patterns = array_map('trim', file($sms_ignore_file)); foreach ($manual_patterns as $manual_pattern) { $ignore .= '|' . str_replace('/', '\\/', $manual_pattern); } } return '/' . $ignore . '/'; } /** * Returns template information (name, description, path) from it's header comment * * @param string $template * @param int $theme_id * @return Array */ function _getTemplateMetaInfo($template, $theme_id) { static $init_made = false; if (!$init_made) { $this->Application->InitParser(true); $init_made = true; } $template = 'theme:' . $this->_themeNames[$theme_id] . '/' . $template; $template_file = $this->Application->TemplatesCache->GetRealFilename($template); // ".tpl" was added before return $this->parseTemplateMetaInfo($template_file); } function parseTemplateMetaInfo($template_file) { if (!file_exists($template_file)) { // when template without info it's placed in top category return Array (); } $template_data = file_get_contents($template_file); if (substr($template_data, 0, 6) == '*/ $comment_end = strpos($template_data, '##-->'); if ($comment_end === false) { // badly formatted comment return Array (); } $comment = trim( substr($template_data, 6, $comment_end - 6) ); if (preg_match_all('/<(NAME|DESC|SECTION)>(.*?)<\/(NAME|DESC|SECTION)>/is', $comment, $regs)) { $ret = Array (); foreach ($regs[1] as $param_order => $param_name) { $ret[ strtolower($param_name) ] = trim($regs[2][$param_order]); } if (array_key_exists('section', $ret) && $ret['section']) { $category_path = explode('||', $ret['section']); $category_path = array_map('trim', $category_path); $ret['section'] = implode('||', $category_path); } return $ret; } } return Array (); } /** * Updates file system changes to database for all themes (including new ones) * */ function refreshThemes() { $themes_found = Array(); $skip_filenames = Array ('.', '..', 'CVS', '.svn'); $fh = opendir($this->themesFolder.'/'); while (($filename = readdir($fh))) { if (in_array($filename, $skip_filenames)) { continue; } if (is_dir($this->themesFolder.'/'.$filename)) { $theme_id = $this->refreshTheme($filename); if ($theme_id) { $themes_found[] = $theme_id; // increment serial of updated themes $this->Application->incrementCacheSerial('theme', $theme_id); } } } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); // 1. only one theme found -> enable it and make primary /*if (count($themes_found) == 1) { $sql = 'UPDATE ' . $table_name . ' SET Enabled = 1, PrimaryTheme = 1 WHERE ' . $id_field . ' = ' . current($themes_found); $this->Conn->Query($sql); }*/ // 2. if none themes found -> delete all from db OR delete all except of found themes $sql = 'SELECT '.$id_field.' FROM '.$table_name; if ($themes_found) { $sql .= ' WHERE '.$id_field.' NOT IN ('.implode(',', $themes_found).')'; } $theme_ids = $this->Conn->GetCol($sql); $this->deleteThemes($theme_ids); foreach ($theme_ids as $theme_id) { // increment serial of deleted themes $this->Application->incrementCacheSerial('theme', $theme_id); } $this->Application->incrementCacheSerial('theme'); $this->Application->incrementCacheSerial('theme-file'); $minify_helper =& $this->Application->recallObject('MinifyHelper'); /* @var $minify_helper MinifyHelper */ $minify_helper->delete(); } /** * Deletes themes with ids passed from db * * @param Array $theme_ids */ function deleteThemes($theme_ids) { if (!$theme_ids) { return ; } $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'DELETE FROM '.$table_name.' WHERE '.$id_field.' IN ('.implode(',', $theme_ids).')'; $this->Conn->Query($sql); $sql = 'DELETE FROM '.TABLE_PREFIX.'ThemeFiles WHERE '.$id_field.' IN ('.implode(',', $theme_ids).')'; $this->Conn->Query($sql); } /** * Returns current theme (also works in admin) * * @return int */ function getCurrentThemeId() { static $theme_id = null; if (isset($theme_id)) { return $theme_id; } if ($this->Application->isAdmin) { // get theme, that user selected in catalog $theme_id = $this->Application->RecallVar('theme_id'); if ($theme_id === false) { // query, because "m_theme" is always empty in admin $id_field = $this->Application->getUnitOption('theme', 'IDField'); $table_name = $this->Application->getUnitOption('theme', 'TableName'); $sql = 'SELECT ' . $id_field . ' FROM ' . $table_name . ' WHERE (PrimaryTheme = 1) AND (Enabled = 1)'; $theme_id = $this->Conn->GetOne($sql); } return $theme_id; } // use current theme, because it's available on Front-End $theme_id = $this->Application->GetVar('m_theme'); if (!$theme_id) { // happens in mod-rewrite mode, then requested template is not found $theme_id = $this->Application->GetDefaultThemeId(); } return $theme_id; } /** * Returns page id based on given template * * @param string $template * @param int $theme_id * @return int */ function getPageByTemplate($template, $theme_id = null) { if (!isset($theme_id)) { // during mod-rewrite url parsing current theme // is not available to kHTTPQuery class, so don't use it $theme_id = (int)$this->getCurrentThemeId(); } $sql = 'SELECT ' . $this->Application->getUnitOption('c', 'IDField') . ' FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' WHERE ( (NamedParentPath = ' . $this->Conn->qstr('Content/' . $template) . ') OR (`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplate = ' . $this->Conn->qstr($template) . ') ) AND (ThemeId = ' . $theme_id . ($theme_id > 0 ? ' OR ThemeId = 0' : '') . ')'; return $this->Conn->GetOne($sql); } }