path = $this->Application->ConfigValue('Backup_Path'); } /** * Backup all data * * @return bool */ function initBackup() { $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ if (!$file_helper->CheckFolder($this->path) || !is_writable($this->path)) { $this->Application->SetVar('error_msg', $this->Application->Phrase('la_Text_backup_access')); return false; } $tables = $this->_getBackupTables(); $backup_progress = Array ( 'table_num' => 0, 'table_names' => $tables, 'table_count' => count($tables), 'record_count' => 0, 'file_name' => $this->getBackupFile( adodb_mktime() ), ); $this->Application->RemoveVar('adm.backupcomplete_filename'); $this->Application->RemoveVar('adm.backupcomplete_filesize'); $fp = fopen($backup_progress['file_name'], 'a'); // write down module versions, used during backup foreach ($this->Application->ModuleInfo as $module_name => $module_info) { fwrite($fp, '# ' . $module_name . ' Version: ' . $module_info['Version'] . ";\n"); } fwrite($fp, "#------------------------------------------\n\n"); // write down table structure $out = Array (); foreach ($tables as $table) { $out[] = $this->_getCreateTableSql($table); } fwrite($fp, implode("\n", $out) . "\n"); fclose($fp); $this->Application->StoreVar('adm.backup_status', serialize($backup_progress)); return true; } /** * Returns tables, that can be backed up * * @return Array * @access protected */ protected function _getBackupTables() { $ret = Array (); $tables = $this->Conn->GetCol('SHOW TABLES LIKE "' . TABLE_PREFIX . '%"'); foreach ($tables as $table) { if ( strpos($table, 'ses_') === false ) { $ret[] = $table; } } return $ret; } public function performBackup() { $backup_progress = unserialize($this->Application->RecallVar('adm.backup_status')); $current_table = $backup_progress['table_names'][ $backup_progress['table_num'] ]; // get records $a_records = $this->_getInsertIntoSql($current_table, $backup_progress['record_count'], self::BACKUP_PER_STEP); if ( $a_records['num'] < self::BACKUP_PER_STEP ) { $backup_progress['table_num']++; $backup_progress['record_count'] = 0; } else { $backup_progress['record_count'] += self::BACKUP_PER_STEP; } if ( $a_records['sql'] ) { $fp = fopen($backup_progress['file_name'], 'a'); fwrite($fp, $a_records['sql']); fclose($fp); } $percent = ($backup_progress['table_num'] / $backup_progress['table_count']) * 100; if ( $percent >= 100 ) { $percent = 100; $this->Application->StoreVar('adm.backupcomplete_filename', $backup_progress['file_name']); $this->Application->StoreVar('adm.backupcomplete_filesize', round(filesize($backup_progress['file_name']) / 1024 / 1024, 2)); // Mbytes } else { $this->Application->StoreVar('adm.backup_status', serialize($backup_progress)); } return round($percent); } /** * Returns sql, that will insert given amount of data into given table * * @param string $table * @param int $start * @param int $limit * @return Array * @access protected */ protected function _getInsertIntoSql($table, $start, $limit) { $sql = 'SELECT * FROM ' . $table . ' ' . $this->Conn->getLimitClause($start, $limit); $a_data = $this->Conn->Query($sql); if ( !$a_data ) { return Array ('num' => 0, 'sql' => '',); } $ret = ''; $fields_sql = $this->_getFieldsSql( $a_data[0] ); foreach ($a_data AS $a_row) { $values_sql = ''; foreach ($a_row as $field_value) { $values_sql .= $this->Conn->qstr($field_value) . ','; } $values_sql = substr($values_sql, 0, -1); $sql = 'INSERT INTO ' . $table . ' (' . $fields_sql . ') VALUES (' . $values_sql . ');'; $sql = str_replace("\n", "\\n", $sql); $sql = str_replace("\r", "\\r", $sql); $ret .= $sql . "\n"; } if ( strlen(TABLE_PREFIX) ) { $ret = str_replace('INSERT INTO ' . TABLE_PREFIX, 'INSERT INTO ', $ret); } return Array ('num' => count($a_data), 'sql' => $ret); } /** * Builds field list part of INSERT INTO statement based on given fields * * @param Array $row * @return string */ protected function _getFieldsSql($row) { $ret = ''; $a_fields = array_keys($row); foreach ($a_fields AS $field_name) { $ret .= '`' . $field_name . '`,'; } return substr($ret, 0, -1); } /** * Returns sql, that will create given table * * @param string $table * @param string $crlf * @return string */ protected function _getCreateTableSql($table, $crlf = "\n") { $this->Conn->Query('SET SQL_QUOTE_SHOW_CREATE = 0'); $tmp_res = $this->Conn->Query('SHOW CREATE TABLE ' . $table); if ( !is_array($tmp_res) || !isset($tmp_res[0]) ) { return ''; } // add drop table & create table $schema_create = 'DROP TABLE IF EXISTS ' . $table . ';' . $crlf; $schema_create .= '# --------------------------------------------------------' . $crlf; $schema_create .= str_replace("\n", $crlf, $tmp_res[0]['Create Table']); // remove table prefix from dump if ( strlen(TABLE_PREFIX) ) { $schema_create = str_replace('DROP TABLE IF EXISTS ' . TABLE_PREFIX, 'DROP TABLE ', $schema_create); $schema_create = str_replace('CREATE TABLE ' . TABLE_PREFIX, 'CREATE TABLE ', $schema_create); } // remove any table properties (e.g. auto-increment, collation, etc) $last_brace_pos = strrpos($schema_create, ')'); if ( $last_brace_pos !== false ) { $schema_create = substr($schema_create, 0, $last_brace_pos + 1); } $schema_create .= "\n# --------------------------------------------------------\n"; return $schema_create; } public function initRestore() { $file = $this->getBackupFile(); $restoreProgress = Array ( 'file_pos' => 0, 'file_name' => $file, 'file_size' => filesize($file), ); $this->Application->RemoveVar('adm.restore_success'); $this->Application->StoreVar('adm.restore_status', serialize($restoreProgress)); } /** * Restores part of backup file * * @return int * @access public */ public function performRestore() { $restore_progress = unserialize($this->Application->RecallVar('adm.restore_status')); $filename = $restore_progress['file_name']; $file_offset = $restore_progress['file_pos']; $size = filesize($filename); if ( $file_offset > $size ) { return self::FAILED_READING_BACKUP_FILE; } $fp = fopen($filename, "r"); if ( !$fp ) { return self::FAILED_READING_BACKUP_FILE; } if ( $file_offset > 0 ) { fseek($fp, $file_offset); } else { $end_of_sql = FALSE; $sql = ""; while ( !feof($fp) && !$end_of_sql ) { $line = fgets($fp); if ( substr($line, 0, 11) == "INSERT INTO" ) { $end_of_sql = TRUE; } else { $sql .= $line; $file_offset = ftell($fp) - strlen($line); } } if ( strlen($sql) ) { $error = $this->runSchemaText($sql); if ( $error != '' ) { $this->Application->StoreVar('adm.restore_error', $error); return self::SQL_ERROR_DURING_RESTORE; } } fseek($fp, $file_offset); } $lines_read = 0; $all_sqls = Array (); while ( $lines_read < self::RESTORE_PER_STEP && !feof($fp) ) { $sql = fgets($fp); if ( strlen($sql) ) { $all_sqls[] = $sql; $lines_read++; } } $file_offset = !feof($fp) ? ftell($fp) : $size; fclose($fp); if ( count($all_sqls) > 0 ) { $error = $this->runSQLText($all_sqls); if ( $error != '' ) { $this->Application->StoreVar('adm.restore_error', $error); return self::SQL_ERROR_DURING_RESTORE; } } $restore_progress['file_pos'] = $file_offset; $this->Application->StoreVar('adm.restore_status', serialize($restore_progress)); return round($file_offset / $size * 100); } /** * Adds table prefix to given sql set * * @param string $sql * @return void * @access protected */ protected function addTablePrefix(&$sql) { $table_prefix = 'restore' . TABLE_PREFIX; if ( strlen($table_prefix) > 0 ) { $replacements = Array ('INSERT INTO ', 'UPDATE ', 'ALTER TABLE ', 'DELETE FROM ', 'REPLACE INTO '); foreach ($replacements as $replacement) { $sql = str_replace($replacement, $replacement . $table_prefix, $sql); } } $sql = str_replace('CREATE TABLE ', 'CREATE TABLE IF NOT EXISTS ' . $table_prefix, $sql); $sql = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS ' . $table_prefix, $sql); } /** * Run given schema sqls and return error, if any * * @param $sql * @return string * @access protected */ protected function runSchemaText($sql) { $this->addTablePrefix($sql); $commands = explode("# --------------------------------------------------------", $sql); if ( count($commands) > 0 ) { $commands = array_map('trim', $commands); foreach ($commands as $cmd) { if ( strlen($cmd) > 0 ) { $this->Conn->Query($cmd); if ( $this->Conn->hasError() ) { return $this->Conn->getErrorMsg() . " COMMAND:
$cmd
"; } } } } return ''; } /** * Runs given sqls and return error message, if any * * @param $all_sqls * @return string * @access protected */ protected function runSQLText($all_sqls) { $line = 0; while ( $line < count($all_sqls) ) { $sql = $all_sqls[$line]; if ( strlen(trim($sql)) > 0 && substr($sql, 0, 1) != "#" ) { $this->addTablePrefix($sql); $sql = trim($sql); if ( strlen($sql) > 0 ) { $this->Conn->Query($sql); if ( $this->Conn->hasError() ) { return $this->Conn->getErrorMsg() . " COMMAND:
$sql
"; } } } $line++; } return ''; } /** * Replaces current tables with restored tables * * @return void * @access public */ public function replaceRestoredFiles() { // gather restored table names $tables = $this->Conn->GetCol('SHOW TABLES'); $mask_restore_table = '/^restore' . TABLE_PREFIX . '(.*)$/'; foreach ($tables as $table) { if ( preg_match($mask_restore_table, $table) ) { $old_table = substr($table, 7); $this->Conn->Query('DROP TABLE IF EXISTS ' . $old_table); $this->Conn->Query('CREATE TABLE ' . $old_table . ' LIKE ' . $table); $this->Conn->Query('INSERT INTO ' . $old_table . ' SELECT * FROM ' . $table); $this->Conn->Query('DROP TABLE ' . $table); } } } /** * Deletes current backup file * * @return void * @access public */ public function delete() { @unlink($this->getBackupFile()); } /** * Returns path to current backup file * * @param int|null $backup_date * @return string * @access protected */ protected function getBackupFile($backup_date = null) { if ( !isset($backup_date) ) { $backup_date = $this->Application->GetVar('backupdate'); } return $this->path . '/dump' . $backup_date . '.txt'; } /** * Returns list of backup files, available for restore * * @return Array * @access public */ public function getBackupFiles() { $file_helper =& $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $ret = Array (); $backup_path = $this->Application->ConfigValue('Backup_Path'); $file_helper->CheckFolder($backup_path); $backup_files = glob($backup_path . DIRECTORY_SEPARATOR . 'dump*.txt'); if ( !$backup_files ) { return Array (); } foreach ($backup_files as $backup_file) { $ret[] = Array ( 'filedate' => preg_replace('/^dump([\d]+)\.txt$/', '\\1', basename($backup_file)), 'filesize' => filesize($backup_file)); } rsort($ret); return $ret; } }