2004-10-14 23:04:03 +02:00
< ? php
2008-07-21 11:40:58 +02:00
/**
2012-03-15 13:55:59 +01:00
* EGroupware API : Database backups
2008-07-21 11:40:58 +02:00
*
* @ link http :// www . egroupware . org
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package api
* @ subpackage db
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2012-03-15 13:55:59 +01:00
* @ copyright ( c ) 2003 - 12 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-07-21 11:40:58 +02:00
* @ version $Id $
*/
2005-11-04 19:35:09 +01:00
2008-07-21 11:40:58 +02:00
/**
2012-03-15 13:55:59 +01:00
* DB independent backup and restore of EGroupware database
2008-07-21 11:40:58 +02:00
*/
class db_backup
{
2004-10-14 23:04:03 +02:00
/**
2008-07-21 11:40:58 +02:00
* replaces backslashes , used in cvs_split
*/
const BACKSLASH_TOKEN = '##!!**bAcKsLaSh**!!##' ;
2009-08-27 14:46:12 +02:00
/**
* Configuration table .
*/
const TABLE = 'egw_config' ;
2008-07-21 11:40:58 +02:00
/**
* Reference to schema_proc
*
* @ var schema_proc
*/
var $schema_proc ;
/**
* Reference to ADOdb ( connection ) object
*
* @ var ADOConnection
*/
var $adodb ;
/**
* DB schemas , as array tablename => schema
*
* @ var array
*/
var $schemas = array ();
/**
* Tables to exclude from the backup : sessions , diverse caches which get automatic rebuild
*
* @ var array
*/
var $exclude_tables = array (
'egw_sessions' , 'egw_app_sessions' , 'phpgw_sessions' , 'phpgw_app_sessions' , // eGW's session-tables
'phpgw_anglemail' , // email's cache
'egw_felamimail_cache' , 'egw_felamimail_folderstatus' , 'phpgw_felamimail_cache' , 'phpgw_felamimail_folderstatus' , // felamimail's cache
2009-08-27 14:46:12 +02:00
'egw_phpfreechat' , // as of the fieldnames of the table a restore would fail within egroupware, and chatcontent is of no particular intrest
2008-07-21 11:40:58 +02:00
);
/**
* regular expression to identify system - tables => ignored for schema + backup
2004-10-14 23:04:03 +02:00
*
2008-07-21 11:40:58 +02:00
* @ var string | boolean
2004-10-14 23:04:03 +02:00
*/
2008-07-21 11:40:58 +02:00
var $system_tables = false ;
/**
2009-08-27 14:46:12 +02:00
* Regular expression to identify eGW tables => if set only they are used
2008-07-21 11:40:58 +02:00
*
* @ var string | boolean
*/
var $egw_tables = false ;
2009-08-27 14:46:12 +02:00
/**
* Backup directory .
*
* @ var string
*/
var $backup_dir ;
/**
* Minimum number of backup files to keep . Zero for : Disable cleanup .
*
* @ var integer
*/
var $backup_mincount ;
/**
* Backup Files config value , will be overwritten by the availability of the ZibArchive libraries
*
* @ var boolean
*/
var $backup_files = false ;
2009-09-14 19:48:58 +02:00
2008-07-21 11:40:58 +02:00
/**
* Constructor
*/
function __construct ()
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( isset ( $GLOBALS [ 'egw_setup' ]) && is_object ( $GLOBALS [ 'egw_setup' ]) && ! isset ( $GLOBALS [ 'egw_setup' ] -> db ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$GLOBALS [ 'egw_setup' ] -> loaddb (); // we run inside setup, but db object is not loaded
}
if ( isset ( $GLOBALS [ 'egw_setup' ] -> oProc ) && is_object ( $GLOBALS [ 'egw_setup' ] -> oProc )) // schema_proc already instanciated, use it
{
$this -> schema_proc = $GLOBALS [ 'egw_setup' ] -> oProc ;
}
else
{
$this -> schema_proc = new schema_proc ();
}
2004-10-16 01:06:34 +02:00
2008-07-21 11:40:58 +02:00
$this -> db = $this -> schema_proc -> m_odb ;
$this -> adodb = $this -> db -> Link_ID ;
if ( isset ( $GLOBALS [ 'egw_setup' ]) && is_object ( $GLOBALS [ 'egw_setup' ])) // called from setup
{
if ( $GLOBALS [ 'egw_setup' ] -> config_table && $GLOBALS [ 'egw_setup' ] -> table_exist ( array ( $GLOBALS [ 'egw_setup' ] -> config_table )))
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_dir' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
if ( ! ( $this -> backup_dir = $this -> db -> f ( 0 )))
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='files_dir' " , __LINE__ , __FILE__ );
2004-10-16 01:06:34 +02:00
$this -> db -> next_record ();
2008-07-21 11:40:58 +02:00
$this -> backup_dir = $this -> db -> f ( 0 ) . '/db_backup' ;
2004-10-16 01:06:34 +02:00
}
2009-08-27 14:46:12 +02:00
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='files_dir' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
if ( ! ( $this -> files_dir = $this -> db -> f ( 0 )))
{
error_log ( __METHOD__ . " -> " . " No files Directory set/found " );
}
2008-07-21 11:40:58 +02:00
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='system_charset' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
$this -> charset = $this -> db -> f ( 0 );
if ( ! $this -> charset )
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> db -> query ( " SELECT content FROM { $GLOBALS [ 'egw_setup' ] -> lang_table } WHERE message_id='charset' AND app_name='common' AND lang!='en' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
$this -> charset = $this -> db -> f ( 0 );
2004-10-16 01:06:34 +02:00
}
2008-07-21 11:40:58 +02:00
$this -> db -> select ( $GLOBALS [ 'egw_setup' ] -> applications_table , 'app_version' , array ( 'app_name' => 'phpgwapi' ), __LINE__ , __FILE__ );
$this -> api_version = $this -> db -> next_record () ? $this -> db -> f ( 0 ) : false ;
2009-08-27 14:46:12 +02:00
/* Backup settings */
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_mincount' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
$this -> backup_mincount = $this -> db -> f ( 0 );
// backup files too
$this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_files' " , __LINE__ , __FILE__ );
$this -> db -> next_record ();
$this -> backup_files = ( bool ) $this -> db -> f ( 0 );
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
if ( ! $this -> charset ) $this -> charset = 'iso-8859-1' ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
else // called from eGW
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> schema_proc = CreateObject ( 'phpgwapi.schema_proc' );
if ( ! ( $this -> backup_dir = $GLOBALS [ 'egw_info' ][ 'server' ][ 'backup_dir' ]))
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> backup_dir = $GLOBALS [ 'egw_info' ][ 'server' ][ 'files_dir' ] . '/db_backup' ;
2004-10-16 01:06:34 +02:00
}
2009-08-27 14:46:12 +02:00
$this -> files_dir = $GLOBALS [ 'egw_info' ][ 'server' ][ 'files_dir' ];
$this -> backup_mincount = $GLOBALS [ 'egw_info' ][ 'server' ][ 'backup_mincount' ];
$this -> backup_files = $GLOBALS [ 'egw_info' ][ 'server' ][ 'backup_files' ];
2008-07-21 11:40:58 +02:00
$this -> charset = $GLOBALS [ 'egw' ] -> translation -> charset ();
$this -> api_version = $GLOBALS [ 'egw_info' ][ 'apps' ][ 'phpgwapi' ][ 'version' ];
}
2009-08-27 14:46:12 +02:00
// Set a default value if not set.
if ( ! isset ( $this -> backup_mincount ))
{
$this -> backup_mincount = 0 ; // Disabled if not set
}
if ( ! isset ( $this -> backup_files ))
{
$this -> backup_files = false ; // Disabled if not set
}
2008-07-21 11:40:58 +02:00
if ( ! is_dir ( $this -> backup_dir ) && is_writable ( dirname ( $this -> backup_dir )))
{
mkdir ( $this -> backup_dir );
}
switch ( $this -> db -> Type )
{
case 'sapdb' :
case 'maxdb' :
//$this->system_tables = '/^(sql_cursor.*|session_roles|activeconfiguration|cachestatistics|commandcachestatistics|commandstatistics|datastatistics|datavolumes|hotstandbycomponent|hotstandbygroup|instance|logvolumes|machineconfiguration|machineutilization|memoryallocatorstatistics|memoryholders|omslocks|optimizerinformation|sessions|snapshots|spinlockstatistics|version)$/i';
$this -> egw_tables = '/^(egw_|phpgw_|g2_)/i' ;
break ;
}
}
/**
2010-01-22 22:39:19 +01:00
* Opens the backup - file using the highest available compression
2008-07-21 11:40:58 +02:00
*
* @ param $name = false string / boolean filename to use , or false for the default one
* @ param $reading = false opening for reading ( 'rb' ) or writing ( 'wb' )
2009-08-27 14:46:12 +02:00
* @ return string / resource / zip error - msg of file - handle
2008-07-21 11:40:58 +02:00
*/
function fopen_backup ( $name = false , $reading = false )
{
2009-08-27 14:46:12 +02:00
//echo "function fopen_backup($name,$reading)<br>"; // !
2008-07-21 11:40:58 +02:00
if ( ! $name )
{
2009-08-27 14:46:12 +02:00
//echo '-> !$name<br>'; // !
2008-07-21 11:40:58 +02:00
if ( ! $this -> backup_dir || ! is_writable ( $this -> backup_dir ))
2004-10-16 01:06:34 +02:00
{
2009-08-27 14:46:12 +02:00
//echo ' -> !$this->backup_dir || !is_writable($this->backup_dir)<br>'; // !
2008-07-21 11:40:58 +02:00
return lang ( " backupdir '%1' is not writeable by the webserver " , $this -> backup_dir );
2004-10-16 01:06:34 +02:00
}
2008-07-21 11:40:58 +02:00
$name = $this -> backup_dir . '/db_backup-' . date ( 'YmdHi' );
}
else // remove the extension, to use the correct wrapper based on the extension
{
2009-08-27 14:46:12 +02:00
//echo '-> else<br>'; // !
2008-07-21 11:40:58 +02:00
$name = preg_replace ( '/\.(bz2|gz)$/i' , '' , $name );
}
$mode = $reading ? 'rb' : 'wb' ;
2009-08-27 14:46:12 +02:00
list ( , $type ) = explode ( '.' , basename ( $name ));
if ( $type == 'zip' && $reading && $this -> backup_files )
{
//echo '-> $type == "zip" && $reading<br>'; // !
if ( ! class_exists ( 'ZipArchive' , false ))
{
$this -> backup_files = false ;
//echo ' -> (new ZipArchive) == NULL<br>'; // !
2010-01-22 22:39:19 +01:00
return lang ( " Cant open %1, needs ZipArchive " , $name ) . " <br> \n " ;
2009-08-27 14:46:12 +02:00
}
if ( ! ( $f = fopen ( $name , $mode )))
{
//echo ' -> !($f = fopen($name, $mode))<br>'; // !
$lang_mode = $reading ? lang ( " reading " ) : lang ( " writing " );
return lang ( " Cant open '%1' for %2 " , $name , $lang_mode ) . " <br> " ;
}
return $f ;
}
if ( class_exists ( 'ZipArchive' , false ) && ! $reading && $this -> backup_files )
2008-07-21 11:40:58 +02:00
{
2009-08-27 14:46:12 +02:00
//echo '-> (new ZipArchive) != NULL && !$reading; '.$name.'<br>'; // !
if ( ! ( $f = fopen ( $name , $mode )))
{
//echo ' -> !($f = fopen($name, $mode))<br>'; // !
$lang_mode = $reading ? lang ( " reading " ) : lang ( " writing " );
return lang ( " Cant open '%1' for %2 " , $name , $lang_mode ) . " <br> " ;
}
return $f ;
}
2009-10-12 15:28:28 +02:00
if ( ! ( $f = fopen ( " compress.bzip2:// $name .bz2 " , $mode )) &&
! ( $f = fopen ( " compress.zlib:// $name .gz " , $mode )) &&
! ( $f = fopen ( $name , $mode ))
)
2009-08-27 14:46:12 +02:00
{
//echo '-> !($f = fopen("compress.bzip2://$name.bz2", $mode))<br>'; // !
$lang_mode = $reading ? lang ( " reading " ) : lang ( " writing " );
return lang ( " Cant open '%1' for %2 " , $name , $lang_mode ) . " <br> " ;
2008-07-21 11:40:58 +02:00
}
return $f ;
}
2009-08-27 14:46:12 +02:00
/**
* Remove old backups , leaving at least
* backup_mincount backup files in place . Only backup files with
* the regular name scheme are taken into account .
*
* @ param files_return Fills a given array of file names to display ( if given ) .
*/
function housekeeping ( & $files_return = false )
{
/* Stop housekeeping in case it is disabled. */
if ( $this -> backup_mincount == 0 )
{
return ;
}
/* Search the backup directory for matching files. */
$handle = @ opendir ( $this -> backup_dir );
$files = array ();
while ( $handle && ( $file = readdir ( $handle )))
{
/* Filter for only the files with the regular name ( un - renamed ) .
* Leave special backup files ( renamed ) in place .
* Note that this also excludes " . " and " .. " .
*/
if ( preg_match ( " /^db_backup-[0-9] { 12}( \ .bz2| \ .gz| \ .zip|) $ / " , $file ))
{
$files [ filectime ( $this -> backup_dir . '/' . $file )] = $file ;
}
}
if ( $handle ) closedir ( $handle );
/* Sort the files by ctime. */
krsort ( $files );
$count = 0 ;
foreach ( $files as $ctime => $file )
{
if ( $count >= $this -> backup_mincount ) //
{
$ret = unlink ( $this -> backup_dir . '/' . $file );
if (( $ret ) && ( is_array ( $files_return )))
{
array_push ( $files_return , $file );
}
}
$count ++ ;
}
}
/**
* Save the housekeeping configuration in the database and update the local variables .
*
2011-01-20 23:17:06 +01:00
* @ param int $mincount Minimum number of backups to keep .
* @ param boolean $backup_files include files in backup or not , default dont change !
2009-08-27 14:46:12 +02:00
*/
2011-01-20 23:17:06 +01:00
function saveConfig ( $minCount , $backupFiles = null )
2009-08-27 14:46:12 +02:00
{
2011-01-20 23:17:06 +01:00
config :: save_value ( 'backup_mincount' , $this -> backup_mincount = ( int ) $minCount , 'phpgwapi' );
if ( ! is_null ( $backupFiles ))
{
config :: save_value ( 'backup_files' , $this -> backup_files = ( boolean ) $backupFiles , 'phpgwapi' );
}
2009-08-27 14:46:12 +02:00
}
2011-01-20 23:17:06 +01:00
2010-08-15 17:46:23 +02:00
/**
* Certain config settings NOT to restore ( because they break a working system )
2011-01-20 23:17:06 +01:00
*
2010-08-15 17:46:23 +02:00
* @ var array
*/
static $system_config = array (
'files_dir' ,
'temp_dir' ,
'backup_dir' ,
2011-01-20 23:17:06 +01:00
'backup_files' ,
2010-08-15 17:46:23 +02:00
'webserver_url' ,
'aspell_path' ,
'hostname' ,
'httpproxy_server' ,
'httpproxy_port' ,
'httpproxy_server_username' ,
'httpproxy_server_password' ,
2010-08-19 10:07:06 +02:00
'system_charset' ,
2012-03-15 13:55:59 +01:00
'usecookies' ,
2011-05-07 19:09:52 +02:00
'install_id' , // do not restore install_id, as that would give two systems with identical install_id
2010-08-15 17:46:23 +02:00
);
2009-08-27 14:46:12 +02:00
2008-07-21 11:40:58 +02:00
/**
* Backup all data in the form of a ( compressed ) csv file
*
* @ param resource $f file opened with fopen for reading
2010-08-19 10:07:06 +02:00
* @ param boolean $convert_to_system_charset = true convert the restored data to the selected system - charset
2010-01-22 22:39:19 +01:00
* @ param string $filename = '' gives the file name which is used in case of a zip archive .
2010-08-15 17:46:23 +02:00
* @ param boolean $protect_system_config = true should above system_config values be protected ( NOT overwritten )
2010-01-22 22:39:19 +01:00
*
* @ returns An empty string or an error message in case of failure .
2008-07-21 11:40:58 +02:00
*/
2010-08-19 10:07:06 +02:00
function restore ( $f , $convert_to_system_charset = true , $filename = '' , $protect_system_config = true )
2008-07-21 11:40:58 +02:00
{
@ set_time_limit ( 0 );
ini_set ( 'auto_detect_line_endings' , true );
2011-01-20 23:17:06 +01:00
2010-08-19 10:07:06 +02:00
$convert_to_system_charset = true ; // enforce now utf-8 as system charset restores of old backups
2011-01-20 23:17:06 +01:00
2010-08-15 17:46:23 +02:00
if ( $protect_system_config )
{
$system_config = array ();
foreach ( $this -> db -> select ( self :: TABLE , '*' , array (
'config_app' => 'phpgwapi' ,
'config_name' => self :: $system_config ,
), __LINE__ , __FILE__ ) as $row )
{
$system_config [] = $row ;
}
}
2008-07-21 11:40:58 +02:00
$this -> db -> transaction_begin ();
2004-10-16 01:06:34 +02:00
2008-07-21 11:40:58 +02:00
// drop all existing tables
foreach ( $this -> adodb -> MetaTables ( 'TABLES' ) as $table )
{
if ( $this -> system_tables && preg_match ( $this -> system_tables , $table ) ||
$this -> egw_tables && ! preg_match ( $this -> egw_tables , $table ))
2004-10-16 01:06:34 +02:00
{
2008-07-21 11:40:58 +02:00
continue ;
2004-10-16 01:06:34 +02:00
}
2008-07-21 11:40:58 +02:00
$this -> schema_proc -> DropTable ( $table );
2004-10-16 01:06:34 +02:00
}
2009-08-27 14:46:12 +02:00
// it could be an old backup
list ( , $type ) = explode ( '.' , basename ( $filename ));
$dir = $this -> files_dir ; // $GLOBALS['egw_info']['server']['files_dir'];
// we may have to clean up old backup - left overs
if ( is_dir ( $dir . '/database_backup' ))
{
2009-09-14 19:48:58 +02:00
self :: remove_dir_content ( $dir . '/database_backup/' );
2009-08-27 14:46:12 +02:00
rmdir ( $dir . '/database_backup' );
}
2009-09-14 19:48:58 +02:00
2009-08-27 14:46:12 +02:00
$list = array ();
$name = " " ;
$zip = NULL ;
$_f = NULL ;
if ( $type == 'zip' )
{
2010-01-22 22:39:19 +01:00
// has already been verified to be available in fopen_backup
2009-08-27 14:46:12 +02:00
$zip = new ZipArchive ;
if (( $zip -> open ( $filename )) !== TRUE )
{
2010-01-22 22:39:19 +01:00
return lang ( " Cant open '%1' for %2 " , $filename , lang ( " reading " )) . " <br> \n " ;
2009-08-27 14:46:12 +02:00
}
2009-09-14 19:48:58 +02:00
self :: remove_dir_content ( $dir ); // removes the files-dir
2009-08-27 14:46:12 +02:00
$zip -> extractTo ( $dir );
$_f = $f ;
$list = $this -> get_file_list ( $dir . '/database_backup/' );
$name = $dir . '/database_backup/' . basename ( $list [ 0 ]);
if ( ! ( $f = fopen ( $name , 'rb' )))
{
2010-01-22 22:39:19 +01:00
return lang ( " Cant open '%1' for %2 " , $filename , lang ( " reading " )) . " <br> \n " ;
2009-08-27 14:46:12 +02:00
}
}
2010-08-19 10:07:06 +02:00
// do not stop if for whatever reason some sql statement fails
if ( $this -> db -> Halt_On_Error != 'no' )
{
$backup_db_halt_on_error = $this -> db -> Halt_On_Error ;
$this -> db -> Halt_On_Error = 'no' ;
}
2008-07-21 11:40:58 +02:00
$table = False ;
$n = 0 ;
while ( ! feof ( $f ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$line = trim ( fgets ( $f )); ++ $n ;
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
if ( empty ( $line )) continue ;
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
if ( substr ( $line , 0 , 9 ) == 'version: ' )
{
$api_version = trim ( substr ( $line , 9 ));
continue ;
}
if ( substr ( $line , 0 , 9 ) == 'charset: ' )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$charset = trim ( substr ( $line , 9 ));
// needed if mbstring.func_overload > 0, else eg. substr does not work with non ascii chars
@ ini_set ( 'mbstring.internal_encoding' , $charset );
2004-11-05 10:34:46 +01:00
2008-11-23 14:08:06 +01:00
// check if we really need to convert the charset, as it's not perfect and can do some damage
if ( $convert_to_system_charset && ! strcasecmp ( $convert_to_system_charset , $charset ))
{
$convert_to_system_charset = false ; // no conversation necessary
}
2008-07-21 11:40:58 +02:00
// set the DB's client encoding (for mysql only if api_version >= 1.0.1.019)
if (( ! $convert_to_system_charset || $this -> db -> capabilities [ 'client_encoding' ]) &&
( substr ( $this -> db -> Type , 0 , 5 ) != 'mysql' || ! is_object ( $GLOBALS [ 'egw_setup' ]) ||
$api_version && ! $GLOBALS [ 'egw_setup' ] -> alessthanb ( $api_version , '1.0.1.019' )))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> db -> Link_ID -> SetCharSet ( $charset );
if ( ! $convert_to_system_charset )
2005-11-04 19:35:09 +01:00
{
2008-07-21 11:40:58 +02:00
$this -> schema_proc -> system_charset = $charset ; // so schema_proc uses it for the creation of the tables
2005-11-04 19:35:09 +01:00
}
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
continue ;
}
if ( substr ( $line , 0 , 8 ) == 'schema: ' )
{
// create the tables in the backup set
$this -> schemas = unserialize ( trim ( substr ( $line , 8 )));
foreach ( $this -> schemas as $table_name => $schema )
2004-10-14 23:04:03 +02:00
{
2010-07-31 10:03:32 +02:00
//echo "<pre>$table_name => ".self::write_array($schema,1)."</pre>\n";
2008-07-21 11:40:58 +02:00
$this -> schema_proc -> CreateTable ( $table_name , $schema );
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
// make the schemas availible for the db-class
$GLOBALS [ 'egw_info' ][ 'apps' ][ 'all-apps' ][ 'table_defs' ] = & $this -> schemas ;
continue ;
}
if ( substr ( $line , 0 , 7 ) == 'table: ' )
{
$table = substr ( $line , 7 );
2004-10-14 23:04:03 +02:00
2009-09-14 19:48:58 +02:00
$cols = self :: csv_split ( $line = fgets ( $f )); ++ $n ;
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
if ( feof ( $f )) break ;
continue ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
if ( $convert_to_system_charset && ! $this -> db -> capabilities [ 'client_encoding' ])
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( $GLOBALS [ 'egw_setup' ])
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( ! is_object ( $GLOBALS [ 'egw_setup' ] -> translation -> sql ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$GLOBALS [ 'egw_setup' ] -> translation -> setup_translation_sql ();
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
$translation =& $GLOBALS [ 'egw_setup' ] -> translation -> sql ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
else
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$translation =& $GLOBALS [ 'egw' ] -> translation ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
}
if ( $table ) // do we already reached the data part
{
2008-07-25 11:37:15 +02:00
$import = true ;
2009-09-14 19:48:58 +02:00
$data = self :: csv_split ( $line , $cols );
2008-07-25 11:37:15 +02:00
if ( $table == 'egw_async' && in_array ( '##last-check-run##' , $data )) {
2010-08-15 17:46:23 +02:00
//echo '<p>'.lang("Line %1: '%2'<br><b>csv data does contain ##last-check-run## of table %3 ==> ignored</b>",$n,$line,$table)."</p>\n";
//echo 'data=<pre>'.print_r($data,true)."</pre>\n";
2008-07-25 11:37:15 +02:00
$import = false ;
}
2009-09-14 19:48:58 +02:00
if ( in_array ( $table , $this -> exclude_tables ))
2009-08-27 14:46:12 +02:00
{
echo '<p><b>' . lang ( " Table %1 is excluded from backup and restore. Data will not be restored. " , $table ) . " </b></p> \n " ;
$import = false ; // dont restore data of excluded tables
}
2010-08-15 17:46:23 +02:00
if ( $import )
{
2008-07-25 11:37:15 +02:00
if ( count ( $data ) == count ( $cols ))
2004-10-14 23:04:03 +02:00
{
2008-07-25 11:37:15 +02:00
if ( $convert_to_system_charset && ! $this -> db -> capabilities [ 'client_encoding' ])
{
$translation -> convert ( $data , $charset );
}
$this -> db -> insert ( $table , $data , False , __LINE__ , __FILE__ , 'all-apps' , true );
}
else
{
echo '<p>' . lang ( " Line %1: '%2'<br><b>csv data does not match column-count of table %3 ==> ignored</b> " , $n , $line , $table ) . " </p> \n " ;
echo 'data=<pre>' . print_r ( $data , true ) . " </pre> \n " ;
2004-10-14 23:04:03 +02:00
}
}
}
}
2008-07-21 11:40:58 +02:00
// updated the sequences, if the DB uses them
foreach ( $this -> schemas as $table => $schema )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
foreach ( $schema [ 'fd' ] as $column => $definition )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( $definition [ 'type' ] == 'auto' )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> schema_proc -> UpdateSequence ( $table , $column );
break ; // max. one per table
2004-10-14 23:04:03 +02:00
}
}
}
2008-07-21 11:40:58 +02:00
if ( $convert_to_system_charset ) // store the changed charset
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$this -> db -> insert ( $GLOBALS [ 'egw_setup' ] -> config_table , array (
'config_value' => $GLOBALS [ 'egw_setup' ] -> system_charset ,
), array (
'config_app' => 'phpgwapi' ,
'config_name' => 'system_charset' ,
), __LINE__ , __FILE__ );
}
2010-08-19 10:07:06 +02:00
// restore protected system config
2010-08-15 17:46:23 +02:00
if ( $protect_system_config )
{
foreach ( $system_config as $row )
{
$this -> db -> insert ( self :: TABLE , array ( 'config_value' => $row [ 'config_value' ]), array (
'config_name' => $row [ 'config_name' ],
'config_app' => $row [ 'config_app' ],
), __LINE__ , __FILE__ );
}
2012-03-15 13:55:59 +01:00
// check and reset cookie configuration, if it does not match current enviroment
// if $_SERVER[HTTP_HOST] does not end with cookiedomain --> delete cookiedomain
if (( $cookiedomain = $this -> db -> select ( self :: TABLE , 'config_value' , array (
'config_app' => 'phpgwapi' ,
'config_name' => 'cookiedomain' ,
), __LINE__ , __FILE__ ) -> fetchColumn ()) && isset ( $_SERVER [ 'HTTP_HOST' ]) &&
( list ( $hostname ) = explode ( ':' , $_SERVER [ 'HTTP_HOST' ])) &&
substr ( $hostname , - strlen ( $cookiedomain ) !== $cookiedomain ))
{
$this -> db -> delete ( self :: TABLE , array (
'config_app' => 'phpgwapi' ,
'config_name' => 'cookiedomain' ,
), __LINE__ , __FILE__ );
}
// if configured webserver_url does NOT start with cookiepath --> delete cookiepath
if (( $cookiepath = $this -> db -> select ( self :: TABLE , 'config_value' , array (
'config_app' => 'phpgwapi' ,
'config_name' => 'cookiepath' ,
), __LINE__ , __FILE__ ) -> fetchColumn ()) &&
substr ( parse_url ( $system_config [ 'webserver_url' ], PHP_URL_PATH ), 0 , strlen ( $cookiepath ) !== $cookiepath ))
{
$this -> db -> delete ( self :: TABLE , array (
'config_app' => 'phpgwapi' ,
'config_name' => 'cookiepath' ,
), __LINE__ , __FILE__ );
}
2010-08-15 17:46:23 +02:00
}
2010-08-19 10:07:06 +02:00
// restore original Halt_On_Error state (if changed)
if ( $backup_db_halt_on_error )
{
$this -> db -> Halt_On_Error = $backup_db_halt_on_error ;
}
// zip?
if ( $type == 'zip' )
{
fclose ( $f );
$f = $save_f ;
unlink ( $name );
rmdir ( $dir . '/database_backup' );
}
2010-01-22 22:39:19 +01:00
if ( ! $this -> db -> transaction_commit ())
{
return lang ( 'Restore failed' );
}
return '' ;
2008-07-21 11:40:58 +02:00
}
2009-09-14 19:48:58 +02:00
2009-08-27 14:46:12 +02:00
/**
* Removes a dir , no matter whether it is empty or full
*
* @ param strin $dir
*/
2009-09-14 19:48:58 +02:00
private static function remove_dir_content ( $dir )
2009-08-27 14:46:12 +02:00
{
$list = scandir ( $dir );
while ( $file = $list [ 0 ])
{
if ( is_dir ( $file ) && $file != '.' && $file != '..' )
2009-09-14 19:48:58 +02:00
self :: remove_dir_content ( $dir . '/' . $file );
2009-08-27 14:46:12 +02:00
if ( is_file ( $file ) && $file != '.' && $file != '..' )
unlink ( $dir . '/' . $file );
array_shift ( $list );
}
//rmdir($dir); // dont remove own dir
}
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
/**
* Split one line of a csv file into an array and does all unescaping
2009-09-14 19:48:58 +02:00
*
* @ param string $line line to split
* @ param array $keys = null keys to use or null to use numeric ones
* @ return array
2008-07-21 11:40:58 +02:00
*/
2009-09-14 19:48:58 +02:00
public static function csv_split ( $line , $keys = null )
2008-07-21 11:40:58 +02:00
{
$fields = explode ( ',' , trim ( $line ));
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
$str_pending = False ;
$n = 0 ;
foreach ( $fields as $i => $field )
{
if ( $str_pending !== False )
{
$field = $str_pending . ',' . $field ;
$str_pending = False ;
}
$key = $keys ? $keys [ $n ] : $n ;
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
if ( $field [ 0 ] == '"' )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( substr ( $field , - 1 ) !== '"' || $field === '"' || ! preg_match ( '/[^\\\\]+(\\\\\\\\)*"$/' , $field ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$str_pending = $field ;
continue ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
$arr [ $key ] = str_replace ( self :: BACKSLASH_TOKEN , '\\' , str_replace ( array ( '\\\\' , '\\n' , '\\r' , '\\"' ), array ( self :: BACKSLASH_TOKEN , " \n " , " \r " , '"' ), substr ( $field , 1 , - 1 )));
}
2010-11-08 14:24:01 +01:00
elseif ( $keys && strlen ( $field ) > 26 )
2008-07-21 11:40:58 +02:00
{
$arr [ $key ] = base64_decode ( $field );
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
else
{
$arr [ $key ] = $field == 'NULL' ? NULL : $field ;
}
++ $n ;
}
return $arr ;
}
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
/**
* escape data for csv
*/
2009-09-14 19:48:58 +02:00
public static function escape_data ( & $data , $col , $defs )
2008-07-21 11:40:58 +02:00
{
if ( is_null ( $data ))
{
$data = 'NULL' ;
}
else
{
switch ( $defs [ $col ][ 'type' ])
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
case 'int' :
case 'auto' :
case 'decimal' :
case 'date' :
case 'timestamp' :
break ;
case 'blob' :
$data = base64_encode ( $data );
break ;
default :
$data = '"' . str_replace ( array ( '\\' , " \n " , " \r " , '"' ), array ( '\\\\' , '\\n' , '\\r' , '\\"' ), $data ) . '"' ;
break ;
}
}
}
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
/**
* Backup all data in the form of a ( compressed ) csv file
*
* @ param f resource file opened with fopen for writing
*/
function backup ( $f )
{
2009-08-27 14:46:12 +02:00
//echo "function backup($f)<br>"; // !
2008-07-21 11:40:58 +02:00
@ set_time_limit ( 0 );
2009-08-27 14:46:12 +02:00
$dir = $this -> files_dir ; // $GLOBALS['egw_info']['server']['files_dir'];
// we may have to clean up old backup - left overs
if ( is_dir ( $dir . '/database_backup' ))
{
2009-09-14 19:48:58 +02:00
self :: remove_dir_content ( $dir . '/database_backup/' );
2009-08-27 14:46:12 +02:00
rmdir ( $dir . '/database_backup' );
}
2008-07-21 11:40:58 +02:00
2010-01-22 14:52:04 +01:00
$file_list = array ();
2009-08-27 14:46:12 +02:00
$name = $this -> backup_dir . '/db_backup-' . date ( 'YmdHi' );
$filename = $name . '.zip' ;
$zippresent = false ;
if ( class_exists ( 'ZipArchive' ) && $this -> backup_files )
{
$zip = new ZipArchive ;
if ( is_object ( $zip ))
{
$zippresent = true ;
//echo '-> is_object($zip); '.$filename.'<br>'; // !
$res = $zip -> open ( $filename , ZIPARCHIVE :: CREATE );
if ( $res !== TRUE )
{
//echo ' -> !$res<br>'; // !
2010-01-22 22:39:19 +01:00
return lang ( " Cant open '%1' for %2 " , $filename , lang ( " writing " )) . " <br> \n " ;
2009-08-27 14:46:12 +02:00
}
2010-01-22 14:52:04 +01:00
$file_list = $this -> get_file_list ( $dir );
2009-08-27 14:46:12 +02:00
}
}
2012-03-15 13:55:59 +01:00
fwrite ( $f , " EGroupware backup from " . date ( 'Y-m-d H:i:s' ) . " \n \n " );
2008-07-21 11:40:58 +02:00
fwrite ( $f , " version: $this->api_version\n\n " );
fwrite ( $f , " charset: $this->charset\n\n " );
$this -> schema_backup ( $f ); // add the schema in a human readable form too
/* not needed , already done by schema_backup
foreach ( $this -> adodb -> MetaTables ( 'TABLES' ) as $table )
{
if ( $this -> db -> Type == 'sapdb' || $this -> db -> Type == 'maxdb' )
{
$table = strtolower ( $table );
}
if ( ! ( $this -> schemas [ $table ] = $this -> schema_proc -> GetTableDefinition ( $table )))
{
unset ( $this -> schemas [ $table ]);
2004-10-14 23:04:03 +02:00
}
}
2008-07-21 11:40:58 +02:00
*/
fwrite ( $f , " \n schema: " . serialize ( $this -> schemas ) . " \n " );
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
foreach ( $this -> schemas as $table => $schema )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( in_array ( $table , $this -> exclude_tables )) continue ; // dont backup
$first_row = true ;
$this -> db -> select ( $table , '*' , false , __LINE__ , __FILE__ );
2009-08-27 14:46:12 +02:00
while ( $row = $this -> db -> row ( true ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
if ( $first_row )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
fwrite ( $f , " \n table: $table\n " . implode ( ',' , array_keys ( $row )) . " \n " );
$first_row = false ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
array_walk ( $row , array ( 'db_backup' , 'escape_data' ), $schema [ 'fd' ]);
fwrite ( $f , implode ( ',' , $row ) . " \n " );
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
}
2011-01-20 23:17:06 +01:00
if ( ! $zippresent ) // save without files
2009-08-27 14:46:12 +02:00
{
2010-01-22 22:39:19 +01:00
if ( $this -> backup_files )
{
echo '<center>' . lang ( " Cant open %1, needs ZipArchive " , $name ) . " <br> \n " . '</center>' ;
}
2009-08-27 14:46:12 +02:00
fclose ( $f );
if ( file_exists ( $name )) unlink ( $name );
return TRUE ;
}
2010-01-22 14:52:04 +01:00
// save files ....
2009-08-27 14:46:12 +02:00
//echo $name.'<br>';
$zip -> addFile ( $name , 'database_backup/' . basename ( $name ));
$count = 1 ;
foreach ( $file_list as $num => $file )
{
//echo substr($file,strlen($dir)+1).'<br>';
//echo $file.'<br>';
$zip -> addFile ( $file , substr ( $file , strlen ( $dir ) + 1 )); //,substr($file);
2009-09-14 19:48:58 +02:00
if (( $count ++ ) == 100 ) { // the file descriptor limit
2009-08-27 14:46:12 +02:00
$zip -> close ();
if ( $zip = new ZipArchive ()) {
$zip -> open ( $filename );
$count = 0 ;
}
2009-09-14 19:48:58 +02:00
}
2009-08-27 14:46:12 +02:00
}
$zip -> close ();
fclose ( $f );
unlink ( $name );
2008-07-21 11:40:58 +02:00
return true ;
}
2004-10-14 23:04:03 +02:00
2009-08-27 14:46:12 +02:00
/**
* gets a list of all files on $f
*
* @ param string file $f
2010-08-15 17:46:23 +02:00
* @ param int $cnt = 0
* @ param string $path_name = ''
2009-08-27 14:46:12 +02:00
*
* @ return array ( list of files )
*/
2010-08-15 17:46:23 +02:00
function get_file_list ( $f , $cnt = 0 , $path_name = '' )
2009-08-27 14:46:12 +02:00
{
//chdir($f);
//echo "Processing $f <br>";
if ( $path_name == '' ) $path_name = $f ;
$tlist = scandir ( $f );
$list = array ();
$i = $cnt ;
while ( $file = $tlist [ 0 ]) // remove all '.' and '..' and transfer to $list
{
if ( $file == '.' || $file == '..' )
{
array_shift ( $tlist );
}
else
{
if ( is_dir ( $f . '/' . $file ))
{
$nlist = $this -> get_file_list ( $f . '/' . $file , $i );
$list += $nlist ;
$i += count ( $nlist );
array_shift ( $tlist );
}
else
{
$list [ $i ++ ] = $path_name . '/' . array_shift ( $tlist );
}
}
}
return $list ;
}
2008-07-21 11:40:58 +02:00
/**
* Backup all schemas in the form of a setup / tables_current . inc . php file
*
* @ param resource | boolean $f
*/
function schema_backup ( $f = False )
{
foreach ( $this -> adodb -> MetaTables ( 'TABLES' ) as $table )
{
if ( $this -> system_tables && preg_match ( $this -> system_tables , $table ) ||
$this -> egw_tables && ! preg_match ( $this -> egw_tables , $table ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
continue ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
if ( $this -> db -> Type == 'sapdb' || $this -> db -> Type == 'maxdb' )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$table = strtolower ( $table );
}
if ( ! ( $this -> schemas [ $table ] = $this -> schema_proc -> GetTableDefinition ( $table )))
{
unset ( $this -> schemas [ $table ]);
}
if (( $this -> db -> Type == 'sapdb' || $this -> db -> Type == 'maxdb' ) && $table == 'phpgw_anglemail' )
{
// sapdb does not differ between text and blob
$this -> schemas [ $table ][ 'fd' ][ 'content' ][ 'type' ] = 'blob' ;
2004-10-14 23:04:03 +02:00
}
}
2008-07-21 11:40:58 +02:00
$def = " \t \$ phpgw_baseline = " ;
2009-09-14 19:48:58 +02:00
$def .= self :: write_array ( $this -> schemas , 1 );
2008-07-21 11:40:58 +02:00
$def .= " ; \n " ;
if ( $f )
{
fwrite ( $f , $def );
}
else
2004-10-14 23:04:03 +02:00
{
2012-03-15 13:55:59 +01:00
$def = " <?php \n \t /* EGroupware schema-backup from " . date ( 'Y-m-d H:i:s' ) . " */ \n \n " . $def ;
2009-10-11 13:37:46 +02:00
html :: content_header ( 'schema-backup-' . date ( 'YmdHi' ) . '.inc.php' , 'text/plain' , bytes ( $def ));
echo $def ;
2008-07-21 11:40:58 +02:00
}
}
/**
* Dump an array as php source
*
* copied from etemplate / inc / class . db_tools . inc . php
*/
2009-09-14 19:48:58 +02:00
private static function write_array ( $arr , $depth , $parent = '' )
2008-07-21 11:40:58 +02:00
{
if ( in_array ( $parent , array ( 'pk' , 'fk' , 'ix' , 'uc' )))
{
$depth = 0 ;
}
if ( $depth )
{
$tabs = " \n " ;
for ( $n = 0 ; $n < $depth ; ++ $n )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$tabs .= " \t " ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
++ $depth ;
}
$def = " array( $tabs " . ( $tabs ? " \t " : '' );
2004-10-14 23:04:03 +02:00
2008-07-21 11:40:58 +02:00
$n = 0 ;
foreach ( $arr as $key => $val )
{
if ( ! is_int ( $key ))
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$def .= " ' $key ' => " ;
}
if ( is_array ( $val ))
{
2009-09-14 19:48:58 +02:00
$def .= self :: write_array ( $val , $parent == 'fd' ? 0 : $depth , $key );
2008-07-21 11:40:58 +02:00
}
else
{
if ( ! $only_vals && $key === 'nullable' )
2004-10-14 23:04:03 +02:00
{
2008-07-21 11:40:58 +02:00
$def .= $val ? 'True' : 'False' ;
2004-10-14 23:04:03 +02:00
}
else
{
2008-07-21 11:40:58 +02:00
$def .= " ' $val ' " ;
2004-10-14 23:04:03 +02:00
}
}
2008-07-21 11:40:58 +02:00
if ( $n < count ( $arr ) - 1 )
{
$def .= " , $tabs " . ( $tabs ? " \t " : '' );
}
++ $n ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
$def .= " $tabs ) " ;
return $def ;
2004-10-14 23:04:03 +02:00
}
2008-07-21 11:40:58 +02:00
}
/*
2005-11-04 19:35:09 +01:00
$line = '"de","ranking","use \\"yes\\", or \\"no, prefession\\"","benützen Sie \\"yes\\" oder \\"no, Beruf\\""' ;
echo " <p>line=' $line '</p> \n " ;
echo " <pre> " . print_r ( db_backup :: csv_split ( $line ), true ) . " </pre> \n " ;
2008-07-25 11:37:15 +02:00
*/