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 >
2016-02-20 21:27:17 +01:00
* @ copyright ( c ) 2003 - 16 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
2016-02-20 21:27:17 +01:00
namespace EGroupware\Api\Db ;
use EGroupware\Api ;
2016-03-09 20:45:21 +01:00
use ZipArchive ;
2016-02-28 10:38:36 +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
2015-01-30 13:48:13 +01:00
*
* Backing up bool columns now for all databases as 1 or 0 , but understanding PostgreSQL 't' or 'f' too .
2008-07-21 11:40:58 +02:00
*/
2016-02-20 21:27:17 +01:00
class Backup
2008-07-21 11:40:58 +02:00
{
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
*
2016-02-20 21:27:17 +01:00
* @ var Api\Db\Schema
2008-07-21 11:40:58 +02:00
*/
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 ;
2014-07-11 19:35:46 +02:00
/**
2016-05-06 13:13:19 +02:00
* Reference to schema_proc ' s Api\Db object
2014-07-11 19:35:46 +02:00
*
2016-02-20 21:27:17 +01:00
* @ var Api\Db
2014-07-11 19:35:46 +02:00
*/
var $db ;
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
{
2016-02-20 21:27:17 +01:00
$this -> schema_proc = new Api\Db\Schema ();
2008-07-21 11:40:58 +02:00
}
2004-10-16 01:06:34 +02:00
2008-07-21 11:40:58 +02:00
$this -> db = $this -> schema_proc -> m_odb ;
2014-07-15 13:07:26 +02:00
if ( ! $this -> db -> Link_ID ) $this -> db -> connect ();
2008-07-21 11:40:58 +02:00
$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
{
2016-04-02 21:55:08 +02:00
if ( ! ( $this -> files_dir = $this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='files_dir' " , __LINE__ , __FILE__ ) -> fetchColumn ()))
2009-08-27 14:46:12 +02:00
{
error_log ( __METHOD__ . " -> " . " No files Directory set/found " );
}
2016-04-02 21:55:08 +02:00
if ( ! ( $this -> backup_dir = $this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_dir' " , __LINE__ , __FILE__ ) -> fetchColumn ()))
2004-10-16 01:06:34 +02:00
{
2016-04-02 21:55:08 +02:00
$this -> backup_dir = $this -> files_dir . '/db_backup' ;
2004-10-16 01:06:34 +02:00
}
2016-04-02 21:55:08 +02:00
$this -> charset = $this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='system_charset' " , __LINE__ , __FILE__ ) -> fetchColumn ();
$this -> api_version = $this -> db -> select ( $GLOBALS [ 'egw_setup' ] -> applications_table , 'app_version' , array ( 'app_name' => array ( 'api' , 'phpgwapi' )),
__LINE__ , __FILE__ , 0 , 'ORDER BY app_name ASC' ) -> fetchColumn ();
// Backup settings
$this -> backup_mincount = $this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_mincount' " , __LINE__ , __FILE__ ) -> fetchColumn ();
2009-08-27 14:46:12 +02:00
// backup files too
2016-04-02 21:55:08 +02:00
$this -> backup_files = ( bool ) $this -> db -> query ( " SELECT config_value FROM { $GLOBALS [ 'egw_setup' ] -> config_table } WHERE config_app='phpgwapi' AND config_name='backup_files' " , __LINE__ , __FILE__ ) -> fetchColumn ();
2004-10-14 23:04:03 +02:00
}
2016-04-02 21:55:08 +02:00
if ( ! $this -> charset ) $this -> charset = 'utf-8' ;
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
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' ];
2016-04-02 21:55:08 +02:00
$this -> charset = Api\Translation :: charset ();
2008-07-21 11:40:58 +02:00
2016-04-02 21:55:08 +02:00
$this -> api_version = $GLOBALS [ 'egw_info' ][ 'apps' ][ isset ( $GLOBALS [ 'egw_info' ][ 'apps' ][ 'api' ]) ? 'api' : 'phpgwapi' ][ 'version' ];
2008-07-21 11:40:58 +02:00
}
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
*
2014-09-30 13:59:31 +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 ;
2014-07-11 19:35:46 +02:00
foreach ( $files as $file )
2009-08-27 14:46:12 +02:00
{
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 .
*
2014-09-30 13:59:31 +02:00
* @ param int $minCount Minimum number of backups to keep .
* @ param boolean $backupFiles 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
{
2016-03-21 22:21:40 +01:00
Api\Config :: save_value ( 'backup_mincount' , $this -> backup_mincount = ( int ) $minCount , 'phpgwapi' );
2011-01-20 23:17:06 +01:00
if ( ! is_null ( $backupFiles ))
{
2016-03-21 22:21:40 +01:00
Api\Config :: save_value ( 'backup_files' , $this -> backup_files = ( boolean ) $backupFiles , 'phpgwapi' );
2011-01-20 23:17:06 +01:00
}
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
2014-09-30 13:59:31 +02:00
* @ param boolean $convert_to_system_charset = true obsolet , it ' s now allways done
* @ param string $filename = '' gives the file name which is used in case of a zip archive .
* @ param boolean $protect_system_config = true should above system_config values be protected ( NOT overwritten )
* @ param int $insert_n_rows = 10 how many rows to insert in one sql statement
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
*/
2012-06-10 14:38:35 +02:00
function restore ( $f , $convert_to_system_charset = true , $filename = '' , $protect_system_config = true , $insert_n_rows = 10 )
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
2014-09-30 13:59:31 +02:00
if ( true ) $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 ;
}
}
2016-07-08 08:40:44 +02:00
// as MySQL 5.7+ has sql_mode STRICT_(ALL|TRANS)_TABLES enabled by default,
// it will no longer restore '0000-00-00 00:00:00' in timestamps it created before,
// so switching strict-mode off temporary for the restore (we dont create these!)
if ( substr ( $this -> db -> Type , 0 , 5 ) == 'mysql' )
{
2016-07-08 09:09:47 +02:00
$this -> db -> query ( " SET SESSION sql_mode=(SELECT REPLACE(REPLACE(@@sql_mode,'STRICT_ALL_TABLES',''),'STRICT_TRANS_TABLES','')) " , __LINE__ , __FILE__ );
2016-07-08 08:40:44 +02:00
}
else
{
$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
}
}
2014-10-07 23:04:58 +02:00
$this -> db_restore ( $f , $insert_n_rows );
if ( $convert_to_system_charset ) // store the changed charset
{
2016-03-21 22:21:40 +01:00
$this -> db -> insert ( Api\Config :: TABLE , array (
2014-10-07 23:04:58 +02:00
'config_value' => $this -> schema_proc -> system_charset ,
), array (
'config_app' => 'phpgwapi' ,
'config_name' => 'system_charset' ,
), __LINE__ , __FILE__ );
}
// restore protected system config
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__ );
}
// 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__ );
}
}
// zip?
if ( $type == 'zip' )
{
fclose ( $f );
unlink ( $name );
rmdir ( $dir . '/database_backup' );
}
if ( substr ( $this -> db -> Type , 0 , 5 ) != 'mysql' )
{
if ( ! $this -> db -> transaction_commit ())
{
return lang ( 'Restore failed' );
}
}
2016-02-28 10:38:36 +01:00
// generate an install_id if we dont have one (it breaks Api\Cache::flush() stalling the upgrade)
2014-10-07 23:04:58 +02:00
unset ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'install_id' ]);
2016-02-28 10:38:36 +01:00
if ( ! ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'install_id' ] = Api\Cache :: get_system_config ( 'install_id' , false )))
2014-10-07 23:04:58 +02:00
{
$GLOBALS [ 'egw_info' ][ 'server' ][ 'install_id' ] = md5 ( microtime ( true ) . $_SERVER [ 'HTTP_HOST' ]);
$this -> db -> insert ( 'egw_config' , array (
'config_value' => $GLOBALS [ 'egw_info' ][ 'server' ][ 'install_id' ],
), array (
'config_name' => 'install_id' ,
'config_app' => 'phpgwapi' ,
), __LINE__ , __FILE__ );
}
// flush instance cache
2016-02-28 10:38:36 +01:00
Api\Cache :: flush ( Api\Cache :: INSTANCE );
2014-10-07 23:04:58 +02:00
// search-and-register-hooks
2016-05-11 20:58:10 +02:00
Api\Hooks :: read ( true );
2014-10-07 23:04:58 +02:00
return '' ;
}
/**
* Restore data from a ( compressed ) csv file
*
* @ param resource $f file opened with fopen for reading
* @ param int | string $insert_n_rows = 10 how many rows to insert in one sql statement , or string with column - name used as unique key for insert
* @ returns int number of rows read from csv file
*/
function db_restore ( $f , $insert_n_rows = 10 )
{
$convert_to_system_charset = true ;
2016-06-26 19:08:14 +02:00
$table = null ;
2008-07-21 11:40:58 +02:00
$n = 0 ;
2014-07-11 19:35:46 +02:00
$rows = array ();
2008-07-21 11:40:58 +02:00
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
2014-12-08 20:16:44 +01:00
$ini_default_charset = version_compare ( PHP_VERSION , '5.6' , '<' ) ? 'mbstring.internal_encoding' : 'default_charset' ;
@ ini_set ( $ini_default_charset , $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
2014-07-25 11:13:19 +02:00
if ( $convert_to_system_charset && ! strcasecmp ( $this -> schema_proc -> system_charset , $charset ))
2008-11-23 14:08:06 +01:00
{
$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
2014-07-23 09:15:06 +02:00
$this -> schemas = json_php_unserialize ( trim ( substr ( $line , 8 )));
2008-07-21 11:40:58 +02:00
foreach ( $this -> schemas as $table_name => $schema )
2004-10-14 23:04:03 +02:00
{
2012-06-17 12:10:46 +02:00
// if column is longtext in current schema, convert text to longtext, in case user already updated column
foreach ( $schema [ 'fd' ] as $col => & $def )
{
2013-01-21 10:49:59 +01:00
if ( $def [ 'type' ] == 'text' && $this -> db -> get_column_attribute ( $col , $table_name , true , 'type' ) == 'longtext' )
2012-06-17 12:10:46 +02:00
{
$def [ 'type' ] = 'longtext' ;
}
}
2010-07-31 10:03:32 +02:00
//echo "<pre>$table_name => ".self::write_array($schema,1)."</pre>\n";
2012-06-17 12:10:46 +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
continue ;
}
if ( substr ( $line , 0 , 7 ) == 'table: ' )
{
2012-06-10 14:38:35 +02:00
if ( $rows ) // flush pending rows of last table
{
2014-11-13 12:51:49 +01:00
$this -> insert_multiple ( $table , $rows , $this -> schemas [ $table ]);
2012-06-10 14:38:35 +02:00
}
$rows = array ();
2008-07-21 11:40:58 +02:00
$table = substr ( $line , 7 );
2014-10-07 15:15:04 +02:00
if ( ! isset ( $this -> schemas [ $table ])) $this -> schemas [ $table ] = $this -> db -> get_table_definitions ( true , $table );
2014-10-07 23:04:58 +02:00
$auto_id = count ( $this -> schemas [ $table ][ 'pk' ]) == 1 ? $this -> schemas [ $table ][ 'pk' ][ 0 ] : null ;
2004-10-14 23:04:03 +02:00
2009-09-14 19:48:58 +02:00
$cols = self :: csv_split ( $line = fgets ( $f )); ++ $n ;
2012-06-17 12:10:46 +02:00
$blobs = array ();
foreach ( $this -> schemas [ $table ][ 'fd' ] as $col => $data )
{
if ( $data [ 'type' ] == 'blob' ) $blobs [] = $col ;
}
2015-01-30 13:48:13 +01:00
// check if we have an old PostgreSQL backup useing 't'/'f' for bool values
// --> convert them to MySQL and our new PostgreSQL format of 1/0
$bools = array ();
foreach ( $this -> schemas [ $table ][ 'fd' ] as $col => $def )
{
if ( $def [ 'type' ] === 'bool' ) $bools [] = $col ;
}
if ( $table == 'egw_cal_dates' ) error_log ( __METHOD__ . " () $table : bools= " . array2string ( $bools ) . " , schema[fd]= " . array2string ( $this -> schemas [ $table ][ 'fd' ]));
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
}
if ( $table ) // do we already reached the data part
{
2008-07-25 11:37:15 +02:00
$import = true ;
2015-01-30 13:48:13 +01:00
$data = self :: csv_split ( $line , $cols , $blobs , $bools );
2012-06-17 12:10:46 +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' ])
{
2016-03-05 11:47:20 +01:00
$data = Api\Translation :: convert ( $data , $charset );
2008-07-25 11:37:15 +02:00
}
2012-06-10 14:38:35 +02:00
if ( $insert_n_rows > 1 )
{
$rows [] = $data ;
if ( count ( $rows ) == $insert_n_rows )
{
2014-11-13 12:51:49 +01:00
$this -> insert_multiple ( $table , $rows , $this -> schemas [ $table ]);
2012-06-10 14:38:35 +02:00
$rows = array ();
}
}
2014-10-07 23:04:58 +02:00
// update existing table using given unique key in $insert_n_rows (also removing auto-id/sequence)
elseif ( ! is_numeric ( $insert_n_rows ))
{
$where = array ( $insert_n_rows => $data [ $insert_n_rows ]);
unset ( $data [ $insert_n_rows ]);
if ( $auto_id ) unset ( $data [ $auto_id ]);
$this -> db -> insert ( $table , $data , $where , __LINE__ , __FILE__ , false , false , $this -> schemas [ $table ]);
}
2012-06-10 14:38:35 +02:00
else
{
2014-11-13 12:51:49 +01:00
try {
$this -> db -> insert ( $table , $data , False , __LINE__ , __FILE__ , false , false , $this -> schemas [ $table ]);
}
2016-02-28 14:43:06 +01:00
catch ( Exception\InvalidSql $e ) {
2014-11-13 12:51:49 +01:00
echo " <p> " . $e -> getMessage () . " </p> \n " ;
}
2012-06-10 14:38:35 +02:00
}
2008-07-25 11:37:15 +02:00
}
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
}
}
}
}
2012-06-10 14:38:35 +02:00
if ( $rows ) // flush pending rows
{
2014-11-13 12:51:49 +01:00
$this -> insert_multiple ( $table , $rows , $this -> schemas [ $table ]);
2012-06-10 14:38:35 +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
}
}
}
2015-08-26 14:50:56 +02:00
// check if backup contained all indexes and create missing ones
$this -> schema_proc -> CheckCreateIndexes ();
2014-10-07 23:04:58 +02:00
return $n ;
2008-07-21 11:40:58 +02:00
}
2009-09-14 19:48:58 +02:00
2014-11-13 12:51:49 +01:00
/**
* Insert multiple rows ignoring doublicate entries
*
* @ param string $table
* @ param array $rows
* @ param array $schema
*/
private function insert_multiple ( $table , array $rows , array $schema )
{
try {
$this -> db -> insert ( $table , $rows , False , __LINE__ , __FILE__ , false , false , $schema );
}
2016-02-28 14:43:06 +01:00
catch ( Exception\InvalidSql $e )
2014-11-13 12:51:49 +01:00
{
// try inserting them one by one, ignoring doublicates
foreach ( $rows as $data )
{
try {
$this -> db -> insert ( $table , $data , False , __LINE__ , __FILE__ , false , false , $schema );
}
2016-02-28 14:43:06 +01:00
catch ( Exception\InvalidSql $e ) {
2014-11-13 12:51:49 +01:00
echo " <p> " . $e -> getMessage () . " </p> \n " ;
}
}
}
}
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
2012-06-17 12:10:46 +02:00
/**
* temp . replaces backslashes
*/
const BACKSLASH_TOKEN = '##!!**bAcKsLaSh**!!##' ;
/**
* temp . replaces NULL
*/
const NULL_TOKEN = '##!!**NuLl**!!##' ;
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
2014-09-30 13:59:31 +02:00
* @ param array $keys = null keys to use or null to use numeric ones
* @ param array $blobs = array () blob columns
2015-01-30 13:48:13 +01:00
* @ param array $bools = array () bool columns , values might be 't' / 'f' for old PostgreSQL backups
2009-09-14 19:48:58 +02:00
* @ return array
2008-07-21 11:40:58 +02:00
*/
2015-01-30 13:48:13 +01:00
public static function csv_split ( $line , $keys = null , $blobs = array (), $bools = array ())
2008-07-21 11:40:58 +02:00
{
2012-06-17 12:10:46 +02:00
if ( function_exists ( 'str_getcsv' )) // php5.3+
{
// we need to take care of literal "NULL" values, replacing them we a special token as str_getcsv removes enclosures around strings
// str_getcsv uses '""' for '"' instead of '\\"' and does not unescape '\\n', '\\r' or '\\\\' (two backslashes)
$fields = str_getcsv ( strtr ( $line , array (
'"NULL"' => self :: NULL_TOKEN ,
'\\\\' => self :: BACKSLASH_TOKEN ,
'\\"' => '""' ,
'\\n' => " \n " ,
'\\r' => " \r " )), ',' , '"' , '\0' );
// replace NULL-token again with 'NULL', 'NULL' with null and BACKSLASH-token with a backslash
foreach ( $fields as & $field )
{
switch ( $field )
{
case self :: NULL_TOKEN :
$field = 'NULL' ;
break ;
case 'NULL' :
$field = null ;
break ;
default :
$field = str_replace ( self :: BACKSLASH_TOKEN , '\\' , $field );
break ;
}
}
if ( $keys ) // if string keys are to be used --> combine keys and values
{
$fields = array_combine ( $keys , $fields );
// base64-decode blob columns, if they are base64 encoded
foreach ( $blobs as $key )
{
if ( ! is_null ( $fields [ $key ]) && ( $tmp = base64_decode ( $fields [ $key ], true )) !== false )
{
$fields [ $key ] = $tmp ;
}
}
2015-01-30 13:48:13 +01:00
// decode bool columns, they might be 't'/'f' for old PostgreSQL backups
foreach ( $bools as $key )
{
2016-02-20 21:27:17 +01:00
$fields [ $key ] = Api\Db :: from_bool ( $fields [ $key ]);
2015-01-30 13:48:13 +01:00
}
2012-06-17 12:10:46 +02:00
}
return $fields ;
}
// pre 5.3 implementation
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 ;
2014-07-11 19:35:46 +02:00
foreach ( $fields as $field )
2008-07-21 11:40:58 +02:00
{
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
}
2015-01-30 13:48:13 +01:00
elseif ( in_array ( $key , $bools ))
{
2016-02-20 21:27:17 +01:00
$arr [ $key ] = Api\Db :: from_bool ( $field );
2015-01-30 13:48:13 +01: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 ;
2015-01-30 13:48:13 +01:00
case 'bool' : // we use MySQL 0, 1 in csv, not PostgreSQL 't', 'f'
2016-02-20 21:27:17 +01:00
$data = ( int ) Api\Db :: from_bool ( $data );
2015-01-30 13:48:13 +01:00
break ;
2008-07-21 11:40:58 +02:00
default :
$data = '"' . str_replace ( array ( '\\' , " \n " , " \r " , '"' ), array ( '\\\\' , '\\n' , '\\r' , '\\"' ), $data ) . '"' ;
break ;
}
}
}
2004-10-14 23:04:03 +02:00
2012-06-30 17:18:49 +02:00
/**
* Number of rows to select per chunk , to not run into memory limit on huge tables
*/
2012-07-03 09:05:55 +02:00
const ROW_CHUNK = 10000 ;
2012-06-30 17:18:49 +02:00
2008-07-21 11:40:58 +02:00
/**
* Backup all data in the form of a ( compressed ) csv file
*
2016-03-09 21:25:22 +01:00
* @ param resource $f file opened with fopen for writing
* @ param boolean $lock_table = null true : allways , false : never , null : if no primary key
* default of null limits memory usage if there is no primary key , by locking table and use ROW_CHUCK
* @ todo use https :// github . com / maennchen / ZipStream - PHP to not assemble all files in memmory
2008-07-21 11:40:58 +02:00
*/
2016-03-09 21:25:22 +01:00
function backup ( $f , $lock_table = null )
2008-07-21 11:40:58 +02:00
{
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>'; // !
2016-03-21 22:21:40 +01:00
$res = $zip -> open ( $filename , ZipArchive :: CREATE );
2009-08-27 14:46:12 +02:00
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
2014-06-26 19:38:29 +02:00
fwrite ( $f , " \n schema: " . json_encode ( $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
2014-07-11 19:35:46 +02:00
// do we have a primary key?
// --> use it to order and limit rows, to kope with rows being added during backup
// otherwise new rows can cause rows being backed up twice and
// backups don't restore because of doublicate keys
$pk = $schema [ 'pk' ] && count ( $schema [ 'pk' ]) == 1 ? $schema [ 'pk' ][ 0 ] : null ;
2016-03-09 21:25:22 +01:00
if ( $lock_table || empty ( $pk ) && is_null ( $lock_table ))
{
2016-08-09 09:31:36 +02:00
$this -> db -> Link_ID -> RowLock ( $table , 'true' ); // PostgreSQL drive from ADOdb requires 2. param $where!
2016-03-09 21:25:22 +01:00
}
2014-07-11 19:35:46 +02:00
$total = $max = 0 ;
2012-06-30 17:18:49 +02:00
do {
$num_rows = 0 ;
2014-07-11 19:35:46 +02:00
// querying only chunks for 10000 rows, to not run into memory limit on huge tables
foreach ( $this -> db -> select ( $table , '*' ,
2014-08-26 16:03:18 +02:00
// limit by maximum primary key already received
empty ( $pk ) || ! $max ? false : $pk . ' > ' . $this -> db -> quote ( $max , $schema [ 'fd' ][ $pk ][ 'type' ]),
2014-07-11 19:35:46 +02:00
__LINE__ , __FILE__ ,
2016-03-09 21:25:22 +01:00
// if no primary key, either lock table, or query all rows
empty ( $pk ) ? ( $lock_table !== false ? $total : false ) : 0 ,
// if we have a primary key, order by it to ensure rows are not read double
empty ( $pk ) ? '' : 'ORDER BY ' . $this -> db -> name_quote ( $pk ) . ' ASC' ,
2014-07-11 19:35:46 +02:00
false , self :: ROW_CHUNK ) as $row )
2004-10-14 23:04:03 +02:00
{
2014-07-11 19:35:46 +02:00
if ( ! empty ( $pk )) $max = $row [ $pk ];
2012-06-30 17:18:49 +02:00
if ( $total === 0 ) fwrite ( $f , " \n table: $table\n " . implode ( ',' , array_keys ( $row )) . " \n " );
2016-03-09 20:45:21 +01:00
array_walk ( $row , array ( __CLASS__ , 'escape_data' ), $schema [ 'fd' ]);
2012-06-30 17:18:49 +02:00
fwrite ( $f , implode ( ',' , $row ) . " \n " );
++ $total ;
++ $num_rows ;
2004-10-14 23:04:03 +02:00
}
}
2016-03-09 21:25:22 +01:00
while (( ! empty ( $pk ) || $lock_table !== false ) && ! ( $total % self :: ROW_CHUNK ) && $num_rows );
if ( ! $pk ) $this -> db -> Link_ID -> RollbackLock ( $table );
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 ;
2014-07-11 19:35:46 +02:00
foreach ( $file_list as $file )
2009-08-27 14:46:12 +02:00
{
//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 ();
2014-07-11 19:35:46 +02:00
if (( $zip = new ZipArchive ())) {
2009-08-27 14:46:12 +02:00
$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
2014-09-30 13:59:31 +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 );
}
2015-02-06 15:33:33 +01:00
elseif ( $file == 'debug.txt' && stripos ( $f , 'activesync' ) !== false )
{
// skip activesync debug.txt on backupFiles
//error_log(__METHOD__.__LINE__.'->'.$f.'/'.$file);
array_shift ( $tlist );
}
2009-08-27 14:46:12 +02:00
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 ;
2016-03-13 12:22:44 +01:00
Api\Header\Content :: type ( 'schema-backup-' . date ( 'YmdHi' ) . '.inc.php' , 'text/plain' , bytes ( $def ));
2009-10-11 13:37:46 +02:00
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
{
2014-07-11 19:35:46 +02:00
if ( $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
}
2012-06-17 12:10:46 +02:00
2008-07-21 11:40:58 +02:00
/*
2012-06-17 12:10:46 +02:00
$line = '"de","NULL","ranking",NULL,NULL,"one backslash: \\\\ here","\\\\","use \\"yes\\", or \\"no, prefession\\"","benützen Sie \\"yes\\" oder \\"no, Beruf\\"",NULL' ;
2005-11-04 19:35:09 +01:00
echo " <p>line=' $line '</p> \n " ;
2016-03-09 20:45:21 +01:00
$fields = Backup :: csv_split ( $line );
2012-06-17 12:10:46 +02:00
echo " <pre> " . print_r ( $fields , true ) . " </pre> \n " ;
//echo count($fields)." fields\n";
2014-02-17 17:16:44 +01:00
*/