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 (); $theme_path = $this->themesFolder . '/' . $theme_name; 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 $config = $this->getConfiguration($theme_path); $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, 'StylesheetFile' => isset($config['stylesheet_file']) ? $config['stylesheet_file'] : '', ); $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; $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]); } /** @var LanguageImportHelper $language_import_helper */ $language_import_helper = $this->Application->recallObject('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 (); foreach ($this->Application->ModuleInfo as $module_name => $module_info) { $xml_file = $theme_path . '/' . $module_info['TemplatePath'] . '_install/theme.xml'; if ( $module_name == 'In-Portal' || !file_exists($xml_file) ) { continue; } $theme = simplexml_load_file($xml_file); if ( $theme === false ) { // broken xml OR no aliases defined continue; } foreach ($theme as $design) { /** @var SimpleXMLElement $design */ $template_path = trim($design); $module_override = (string)$design['module']; if ( $module_override ) { // allow to put template mappings form all modules into single theme.xml file $module_folder = $this->Application->findModule('Name', $module_override, 'TemplatePath'); } else { // no module specified -> use module based on theme.xml file location $module_folder = $module_info['TemplatePath']; } // only store alias, when template exists on disk if ( $this->getTemplateId($template_path, $theme_id) ) { $alias = '#' . $module_folder . strtolower($design->getName()) . '#'; // remember alias in global theme mapping $template_aliases[$alias] = $template_path; // store alias in theme file record to use later in design dropdown $this->updateTemplate($template_path, $theme_id, Array ('TemplateAlias' => $alias)); } } } return $template_aliases; } /** * Returns theme configuration. * * @param string $theme_path Absolute path to theme. * * @return array */ protected function getConfiguration($theme_path) { $xml_file = $theme_path . '/_install/theme.xml'; if ( !file_exists($xml_file) ) { return array(); } $theme = simplexml_load_file($xml_file); if ( $theme === false ) { // broken xml OR no aliases defined return array(); } $ret = array(); foreach ( $theme->attributes() as $name => $value ) { $ret[(string)$name] = (string)$value; } return $ret; } /** * Returns ID of given physical template (relative to theme) given from ThemeFiles table * @param string $template_path * @param int $theme_id * @return int * @access public */ public function getTemplateId($template_path, $theme_id) { $template_path = $this->Application->getPhysicalTemplate($template_path); $sql = 'SELECT FileId FROM ' . TABLE_PREFIX . 'ThemeFiles WHERE ' . $this->getTemplateWhereClause($template_path, $theme_id); return $this->Conn->GetOne($sql); } /** * Updates template record with a given data * * @param string $template_path * @param int $theme_id * @param Array $fields_hash * @return void * @access public */ public function updateTemplate($template_path, $theme_id, $fields_hash) { $where_clause = $this->getTemplateWhereClause($template_path, $theme_id); $this->Conn->doUpdate($fields_hash, TABLE_PREFIX . 'ThemeFiles', $where_clause); } /** * Returns where clause to get associated record from ThemeFiles table for given template path * @param string $template_path * @param int $theme_id * @return string * @access protected */ protected function getTemplateWhereClause($template_path, $theme_id) { $folder = dirname($template_path); $where_clause = Array ( 'ThemeId = ' . $theme_id, 'FilePath = ' . $this->Conn->qstr($folder == '.' ? '' : '/' . $folder), 'FileName = ' . $this->Conn->qstr(basename($template_path) . '.tpl'), ); return '(' . implode(') AND (', $where_clause) . ')'; } /** * Installs given module language pack and refreshed it from all themes * * @param string $module_name */ function synchronizeModule($module_name) { $sql = 'SELECT `Name`, ThemeId FROM ' . TABLE_PREFIX . 'Themes'; $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 . 'Themes', 'ThemeId = ' . $theme_id); } } /** * Searches for new templates (missing in db) in specified 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 DirectoryIterator $file_info */ 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 (); try { $iterator = new DirectoryIterator($this->themesFolder . '/'); /** @var DirectoryIterator $file_info */ foreach ($iterator as $file_info) { $filename = $file_info->getFilename(); if ( $file_info->isDir() && !$file_info->isDot() && $filename != '.svn' && $filename != 'CVS' ) { $theme_id = $this->refreshTheme($filename); if ( $theme_id ) { $themes_found[] = $theme_id; // increment serial of updated themes $this->Application->incrementCacheSerial('theme', $theme_id); } } } } catch ( UnexpectedValueException $e ) { } $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'); } /** * 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. $theme_id = $this->Application->GetDefaultThemeId(true); } 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(); } $template_crc = kUtil::crc32(mb_strtolower($template)); $sql = 'SELECT ' . $this->Application->getUnitOption('c', 'IDField') . ' FROM ' . $this->Application->getUnitOption('c', 'TableName') . ' WHERE ( (NamedParentPathHash = ' . $template_crc . ') OR (`Type` = ' . PAGE_TYPE_TEMPLATE . ' AND CachedTemplateHash = ' . $template_crc . ') ) AND (ThemeId = ' . $theme_id . ($theme_id > 0 ? ' OR ThemeId = 0' : '') . ')'; return $this->Conn->GetOne($sql); } }