time(), 'files' => array(), 'dirs' => array(), 'themes' => array()); if ($dist) { $snapshot['db_scheme'] = fn_get_contents($dir_root . '/install/database/scheme.sql'); // remove myslqdump comments $snapshot['db_scheme'] = preg_replace('|/\*!.+\*/;\n|imSU', '', $snapshot['db_scheme']); // form list of tables preg_match_all('/create table `(.+)`/imSU', $snapshot['db_scheme'], $tables); $snapshot['db_tables'] = !empty($tables[1]) ? $tables[1] : array(); } $new_snapshot = self::make($dir_root, $folders, array('config.local.php')); self::arrayMerge($snapshot, $new_snapshot); foreach ($folders as $folder_name) { $path = $dir_root . '/' . $folder_name; $new_snapshot = self::make($path); self::arrayMerge($snapshot, $new_snapshot); } foreach ($themes as $theme_name) { if (is_numeric($theme_name) && $theme_name === strval($theme_name + 0)) { continue; // this is company subfolder } $path = "$themes_dir/$theme_name"; if ($dist) { $new_snapshot = self::make($path, array(), array(), array($themes_dir => $themes_dir_to), true); } else { $new_snapshot = self::make($path, array(), array(), array(), true); } $snapshot['themes'][$theme_name]['files'] = $snapshot['themes'][$theme_name]['dirs'] = array(); self::arrayMerge($snapshot['themes'][$theme_name], $new_snapshot); } $snapshot['addons'] = fn_get_dir_contents(Registry::get('config.dir.addons')); fn_mkdir(Registry::get('config.dir.snapshots')); $snapshot_filename = fn_strtolower(PRODUCT_VERSION . '_' . (PRODUCT_STATUS ? (PRODUCT_STATUS . '_') : '') . PRODUCT_EDITION . ($dist ? '_dist' : '')); $snapshot_filecontent = ''; fn_put_contents(Registry::get('config.dir.snapshots') . "{$snapshot_filename}.php", $snapshot_filecontent); } /** * Makes diff between original and latest snapshots * @param array $params $_REQUEST params * @return array array with changed data */ public static function changes($params) { $results = array(); $creation_time = 0; $changes_tree = array(); $db_diff = ''; $db_d_diff = ''; $dist_filename = self::getName('dist'); $snapshot_filename = self::getName('current'); if (is_file($dist_filename)) { if (is_file($snapshot_filename)) { include($snapshot_filename); include($dist_filename); list($added, $changed, $deleted) = self::diff($snapshot, $snapshot_dist); foreach ($snapshot['themes'] as $theme_name => $data) { $data_dist = self::buildTheme($theme_name, $snapshot_dist); list($theme_added, $theme_changed, $theme_deleted) = self::diff($data, $data_dist); self::arrayMerge($added, $theme_added); self::arrayMerge($changed, $theme_changed); self::arrayMerge($deleted, $theme_deleted); } $tree = self::buildTree(array('added' => $added, 'changed' => $changed, 'deleted' => $deleted)); $tables = db_get_fields('SHOW TABLES'); $creation_time = $snapshot['time']; $changes_tree = $tree; if (!empty($snapshot_dist['db_scheme'])) { $db_scheme = ''; foreach ($tables as $table) { if (!in_array($table, $snapshot_dist['db_tables'])) { continue; } $db_scheme .= "\nDROP TABLE IF EXISTS `" . $table . "`;\n"; $__scheme = db_get_row("SHOW CREATE TABLE $table"); $__scheme = array_pop($__scheme); $replaced_scheme = preg_replace('/ AUTO_INCREMENT=[0-9]*/i', '', $__scheme); if (!empty($replaced_scheme) && is_string($replaced_scheme)) { $__scheme = $replaced_scheme; } $db_scheme .= $__scheme . ";"; } $db_scheme = str_replace('default', 'DEFAULT', $db_scheme); $snapshot_dist['db_scheme'] = str_replace('default', 'DEFAULT', $snapshot_dist['db_scheme']); $db_diff = self::textDiff($snapshot_dist['db_scheme'], $db_scheme); } if (isset($params['db_ready']) && $params['db_ready'] == 'Y') { $snapshot_dir = Registry::get('config.dir.snapshots'); $s_r = fn_get_contents($snapshot_dir . 'cmp_release.sql'); $s_c = fn_get_contents($snapshot_dir . 'cmp_current.sql'); @ini_set('memory_limit', '255M'); $db_d_diff = self::textDiff($s_r, $s_c); } } $dist_filename = ''; } return array( 'creation_time' => $creation_time, 'changes_tree' => $changes_tree, 'db_diff' => $db_diff, 'db_d_diff' => $db_d_diff, 'dist_filename' => $dist_filename ); } /** * Creates database and imports dump there * @param string $db_name db name * @return boolean true on success, false - otherwise */ public static function createDb($db_name = '') { $snapshot_dir = Registry::get('config.dir.snapshots'); $dbdump_filename = empty($db_name) ? 'cmp_current.sql' : 'cmp_release.sql'; if (!fn_mkdir($snapshot_dir)) { fn_set_notification('E', __('error'), __('text_cannot_create_directory', array( '[directory]' => fn_get_rel_dir($snapshot_dir) ))); return false; } $dump_file = $snapshot_dir . $dbdump_filename; if (is_file($dump_file)) { if (!is_writable($dump_file)) { fn_set_notification('E', __('error'), __('dump_file_not_writable')); return false; } } $fd = @fopen($snapshot_dir . $dbdump_filename, 'w'); if (!$fd) { fn_set_notification('E', __('error'), __('dump_cant_create_file')); return false; } if (!empty($db_name)) { Database::changeDb($db_name); } // set export format db_query("SET @SQL_MODE = 'MYSQL323'"); fn_start_scroller(); $create_statements = array(); $insert_statements = array(); $status_data = db_get_array("SHOW TABLE STATUS"); $dbdump_tables = array(); foreach ($status_data as $k => $v) { $dbdump_tables[] = $v['Name']; } // get status data $t_status = db_get_hash_array("SHOW TABLE STATUS", 'Name'); foreach ($dbdump_tables as $k => $table) { fn_echo('
' . __('backupping_data') . ': ' . $table . '  '); $total_rows = db_get_field("SELECT COUNT(*) FROM $table"); $index = db_get_array("SHOW INDEX FROM $table"); $order_by = array(); foreach ($index as $kk => $vv) { if ($vv['Key_name'] == 'PRIMARY') { $order_by[] = '`' . $vv['Column_name'] . '`'; } } if (!empty($order_by)) { $order_by = 'ORDER BY ' . implode(',', $order_by); } else { $order_by = ''; } // Define iterator if (!empty($t_status[$table]) && $t_status[$table]['Avg_row_length'] < DB_MAX_ROW_SIZE) { $it = DB_ROWS_PER_PASS; } else { $it = 1; } for ($i = 0; $i < $total_rows; $i = $i + $it) { $table_data = db_get_array("SELECT * FROM $table $order_by LIMIT $i, $it"); foreach ($table_data as $_tdata) { $_tdata = fn_add_slashes($_tdata, true); $values = array(); foreach ($_tdata as $v) { $values[] = ($v !== null) ? "'$v'" : 'NULL'; } fwrite($fd, "INSERT INTO $table (`" . implode('`, `', array_keys($_tdata)) . "`) VALUES (" . implode(', ', $values) . ");\n"); } fn_echo(' .'); } } fn_stop_scroller(); if (!empty($db_name)) { Settings::instance()->reloadSections(); } if (fn_allowed_for('ULTIMATE')) { $companies = fn_get_short_companies(); asort($companies); $settings['company_root'] = Settings::instance()->getList(); foreach ($companies as $k=>$v) { $settings['company_'.$k] = Settings::instance()->getList(0, 0, false, $k); } } else { $settings['company_root'] = Settings::instance()->getList(); } if (!empty($db_name)) { Database::changeDb(Registry::get('config.db_name')); } $settings = self::processSettings($settings, ''); $settings = self::formatSettings($settings['data']); ksort($settings); $data = print_r($settings, true); fwrite($fd,$data); fclose($fd); @chmod($snapshot_dir . $dbdump_filename, DEFAULT_FILE_PERMISSIONS); return true; } /** * Gets the array of core addons (that was included into distributive) * * @return array List of core addons */ public static function getCoreAddons() { $addons = array(); $dist_snapshot_filename = self::getName('dist'); if (is_file($dist_snapshot_filename)) { include($dist_snapshot_filename); if (!empty($snapshot_dist['addons'])) { $addons = $snapshot_dist['addons']; } } return $addons; } /** * Merges arrays * @param array &$array source array * @param array $additional data to merge into array */ private static function arrayMerge(&$array, $additional) { foreach ($array as $key => $v) { if (is_array($array[$key])) { $array[$key] = array_merge($array[$key], !empty($additional[$key]) ? $additional[$key] : array()); } } } /** * Builds files tree * @param array $changes hashes list * @return array built tree */ private static function buildTree($changes) { $tree = array(); foreach ($changes as $action => $dataset) { foreach ($dataset as $types => $data) { $type = substr($types, 0, -1); foreach ($data as $path) { $parent = ''; $dirs = explode('/', $path); $dirs_size = count($dirs); $elm = & $tree; $level = 0; foreach ($dirs as $key => $name) { if ($name == '') { $name = '/'; } if ($key + 1 < $dirs_size) { $new_key = md5("dir-$name-$parent"); if (!isset($elm[$new_key]['content'])) { $elm[$new_key] = array( 'name' => $name, 'type' => 'dir', 'level' => $level, 'content' => array(), ); } $elm = & $elm[$new_key]['content']; } if ($key + 1 == $dirs_size) { $new_key = md5("$type-$name-$parent"); $elm[$new_key]['name'] = $name; $elm[$new_key]['type'] = $type; $elm[$new_key]['level'] = $level; $elm[$new_key]['action'] = $action; } $parent = $new_key; $level++; } } } } self::sortTree($tree); return $tree; } /** * Gets snapshot file name * @param string $type snapshot type * @return string snapshot file name */ private static function getName($type = 'dist') { $snapshot_filename = Registry::get('config.dir.snapshots') . fn_strtolower(PRODUCT_VERSION . '_' . (PRODUCT_STATUS ? (PRODUCT_STATUS . '_') : '') . PRODUCT_EDITION); if ($type == 'dist') { $snapshot_filename .= '_dist.php'; } else { $snapshot_filename .= '.php'; } return $snapshot_filename; } /** * Diffs data in array * @param array $current current snapshot data * @param array $dist original snapshot data * @return array diff'ed data */ private static function diff($current, $dist) { $deleted = $added = array('files' => array(), 'dirs' => array()); $changed['files'] = array(); $deleted['files'] = array_diff($dist['files'], $current['files']); $deleted['dirs'] = array_diff($dist['dirs'], $current['dirs']); $added['files'] = array_diff($current['files'], $dist['files']); $added['dirs'] = array_diff($current['dirs'], $dist['dirs']); $tmp['files'] = array_diff_assoc($current['files'], $dist['files']); $changed['files'] = array_diff($tmp['files'], $added['files']); return array($added, $changed, $deleted); } /** * Sorts tree * @param array &$tree tree */ private static function sortTree(&$tree) { foreach ($tree as $key => &$elm) { if (!empty($elm['content'])) { if (count($elm['content'] > 1)) { uasort($tree[$key]['content'], function($a, $b) { $a1 = (!empty($a['type']) ? $a['type'] : 'file') . (!empty($a['name']) ? $a['name'] : ''); $b1 = (!empty($b['type']) ? $b['type'] : 'file') . (!empty($b['name']) ? $b['name'] : ''); if ($a1 == $b1) { return 0; } return ($a1 < $b1) ? -1 : 1; }); } self::sortTree($tree[$key]['content']); } } } /** * Generates hashes list * @param string $path files path * @param array $dirs_list list of directories should be included in the result * @param array $skip_files list of files should be excluded * @param array $path_replace new path prefix * @param boolean $themes include themes or not * @return array hashes list */ private static function make($path, $dirs_list = array(), $skip_files = array(), $path_replace = array(), $themes = false) { $results = array('files' => array(), 'dirs' => array()); $dir_root_strlen = strlen(Registry::get('config.dir.root')); if (is_dir($path)) { if ($dh = opendir($path)) { while (($file = readdir($dh)) !== false) { if ($file == '.' || $file == '..' || $file{0} == '.') { continue; } $full_file_path = $_full_file_path = $path . '/' . $file; if ($path_replace) { $_find = key($path_replace); $_replace = $path_replace[$_find]; if (substr($full_file_path, 0, strlen($_find)) == $_find) { $_full_file_path = substr_replace($full_file_path, $_replace, 0, strlen($_find)); } } $short_file_path = $_short_file_path = substr($_full_file_path, $dir_root_strlen); if ($themes) { $_ar = explode('/', $short_file_path); $_short_file_path = implode('/', array_slice($_ar, 3)); } if (is_file($full_file_path) && !in_array($file, $skip_files)) { $results['files'][md5($_short_file_path . md5_file($full_file_path))] = $short_file_path; } elseif (is_dir($full_file_path)) { $hash = md5($_short_file_path); if (!empty($dirs_list)) { if (in_array($file, $dirs_list)) { $results['dirs'][$hash] = $short_file_path; } } else { $results['dirs'][$hash] = $short_file_path; $new_results = self::make($full_file_path, array(), array(), $path_replace, $themes); self::arrayMerge($results, $new_results); } } } closedir($dh); } } return $results; } /** * Processes settings * @param array $data data * @param string $key key * @return array processed settings */ private static function processSettings($data, $key) { $res = array(); foreach ($data as $k=>$v) { if (is_array($v)) { $tmp = self::processSettings($v, $k); $res[$tmp['key']] = $tmp['data']; } else { if ($k == 'name') { $key = $v; } //remove dynamic data if ($k != 'object_id' && $k != 'section_id' && $k != 'section_tab_id') { $res[$k] = $v; } } } return array('key'=>$key, 'data'=>$res); } /** * Formats settings * @param array $data data * @param string $path path * @param int $lev level * @return formatted settings */ private static function formatSettings($data, $path = array(), $lev = 0) { $res = array(); foreach ($data as $k=>$v) { if (is_array($v) && $lev < 3) { $path[$lev] = $k; $tmp = self::formatSettings($v, $path, $lev + 1); $res = array_merge($res, $tmp); } elseif ($lev == 3) { $path[$lev] = $k; $res[implode('.', $path)] = $v; } } return $res; } /** * Makes diff betweed 2 strings * @param sring $source original data * @param string $dest new data * @param boolean $side_by_side side-by-side diff if set to true * @return string diff */ private static function textDiff($source, $dest, $side_by_side = false) { $diff = new \Text_Diff('auto', array(explode("\n", $source), explode("\n", $dest))); $renderer = new \Text_Diff_Renderer_inline(); $renderer->_leading_context_lines = 3; $renderer->_trailing_context_lines = 3; if ($side_by_side == false) { $renderer->_split_level = 'words'; } $res = $renderer->render($diff); if ($side_by_side == true) { $res = $renderer->sideBySide($res); } return $res; } /** * Builds theme files list * @param string $theme_name theme name * @param array &$snapshot_dist snapshot data * @return array theme files list */ private static function buildTheme($theme_name, &$snapshot_dist) { $theme = !empty($snapshot_dist['themes'][$theme_name]) ? $snapshot_dist['themes'][$theme_name] : array('files' => array(), 'dirs' => array()); $base = $snapshot_dist['themes'][Registry::get('config.base_theme')]; $len = strlen('/design/themes/' . Registry::get('config.base_theme')); foreach ($base as $type => $dataset) { foreach ($dataset as $key => $filename) { $base[$type][$key] = substr_replace($filename, '/design/themes/' . $theme_name, 0, $len); if (in_array($base[$type][$key], $theme[$type])) { unset($base[$type][$key]); } } } self::arrayMerge($base, $theme); return $base; } }