diff --git a/calendar/inc/class.calendar_timezones.inc.php b/calendar/inc/class.calendar_timezones.inc.php index dd19243d24..71a8c0ae6c 100644 --- a/calendar/inc/class.calendar_timezones.inc.php +++ b/calendar/inc/class.calendar_timezones.inc.php @@ -84,7 +84,7 @@ class calendar_timezones * - calendar_timezone::tz2id('Europe/Berlin','component') returns VTIMEZONE component for given TZID * * @param string $tzid TZID - * @param string $what='id' what to return, default id, null for whole array + * @param string $what ='id' what to return, default id, null for whole array * @return int tz_id or null if not found */ public static function tz2id($tzid,$what='id') @@ -105,7 +105,7 @@ class calendar_timezones if (!isset($id) && stripos($tzid, 'America/') === 0 && count($parts = explode('/', $tzid)) == 2) { if (($data = $GLOBALS['egw']->db->select(self::TABLE,'*',array( - 'tz_tzid LIKE '.$GLOBALS['egw']->db->quote($parts[0].'/%/'.$part[1]), + 'tz_tzid LIKE '.$GLOBALS['egw']->db->quote($parts[0].'/%/'.$parts[1]), ),__LINE__,__FILE__,false,'','calendar')->fetch())) { $id = $data['tz_id']; @@ -129,7 +129,7 @@ class calendar_timezones * - calendar_timezone::id2tz($id,'component') returns VTIMEZONE component for the given id * * @param int $id - * @param string $what='tzid' what data to return or null for whole data array, with keys 'id', 'tzid', 'component', 'alias', 'latitude', 'longitude' + * @param string $what ='tzid' what data to return or null for whole data array, with keys 'id', 'tzid', 'component', 'alias', 'latitude', 'longitude' * @return mixed false: if not found */ public static function id2tz($id,$what='tzid') @@ -177,17 +177,21 @@ class calendar_timezones // check for updated timezones once per session if (!egw_cache::getSession(__CLASS__, 'tzs_checked')) { + $updated = false; try { $msg = self::import_sqlite($updated); if ($updated) error_log($msg); // log that timezones have been updated - $msg = self::import_tz_aliases($updated); - if ($updated) error_log($msg); // log that timezone aliases have been updated } - catch (Exception $e) + catch (egw_exception_wrong_userinput $e) { - _egw_log_exception($e); // log the exception to error_log, but do not stall program execution + unset($e); + $msg = self::import_db_backup($updated); + if ($updated) error_log($msg); // log that timezones have been updated } + $alias_msg = self::import_tz_aliases($updated); + if ($updated) error_log($alias_msg); // log that timezone aliases have been updated + egw_cache::setSession(__CLASS__, 'tzs_checked', true); } } @@ -196,10 +200,10 @@ class calendar_timezones * Import timezones from sqlite file * * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date - * @param string $file='calendar/setup/timezones.sqlite' filename relative to EGW_SERVER_ROOT + * @param string $file ='calendar/setup/timezones.sqlite' filename relative to EGW_SERVER_ROOT * @return string message about update * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version - * @throws egw_exception_assertion_failed if no PDO sqlite support + * @throws egw_exception_wrong_userinput if no PDO sqlite support * @throws egw_exception_wrong_userinput for broken sqlite extension */ public static function import_sqlite(&$updated=null, $file='calendar/setup/timezones.sqlite') @@ -271,15 +275,46 @@ class calendar_timezones } /** - * Import timezone aliases + * Import timezone via db_backup of egw_cal_timezones * * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date - * @param string $file='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT - * @param boolean $check_mtime=true true: check version and only act, if it's different + * @param string $file ='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT * @return string message about update * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version */ - public static function import_tz_aliases(&$updated=null,$file='calendar/setup/tz_aliases.inc.php',$check_mtime=true) + public static function import_db_backup(&$updated=null,$file='calendar/setup/timezones.db_backup') + { + $path = EGW_SERVER_ROOT.'/'.$file; + + if (!file_exists($path) || !is_readable($path)) + { + throw new egw_exception_wrong_parameter(__METHOD__."('$file') not found or readable!"); + } + $config = config::read('phpgwapi'); + $tz_version = date('Y-m-d H:i:s', filemtime($path)); + if ($tz_version === $config['tz_version']) + { + $updated = false; + return lang('Nothing to update, version is already %1.',$tz_version); + } + $db_backup = new db_backup(); + $rows = $db_backup->db_restore($db_backup->fopen_backup($path, true), 'tz_tzid'); + + config::save_value('tz_version', $tz_version, 'phpgwapi'); + + $updated = true; + return lang('Timezones updated to version %1 (%2 records updated).', $tz_version, $rows-8); // -8 because of header-lines + } + + /** + * Import timezone aliases + * + * @param boolean &$updated=null on return true if update was neccessary, false if tz's were already up to date + * @param string $file ='calendar/setup/tz_aliases.inc.php' filename relative to EGW_SERVER_ROOT + * @return string message about update + * @throws egw_exception_wrong_parameter if $file is not readable or wrong format/version + */ + public static function import_tz_aliases(&$updated=null,$file='calendar/setup/tz_aliases.inc.php') { $path = EGW_SERVER_ROOT.'/'.$file; @@ -294,6 +329,7 @@ class calendar_timezones $updated = false; return lang('Nothing to update, version is already %1.',$tz_aliases_mtime); } + $tz_aliases = array(); include($path); // sets $tz_aliases $updates = 0; @@ -328,10 +364,17 @@ class calendar_timezones { throw new egw_exception_no_permission_admin(); } - $GLOBALS['egw']->framework->render( - '
$table_name => ".self::write_array($schema,1)."\n"; - $this->schema_proc->CreateTable($table_name, $schema); - } - continue; - } - if (substr($line,0,7) == 'table: ') - { - if ($rows) // flush pending rows of last table - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - $rows = array(); - $table = substr($line,7); - if (!isset($this->schemas[$table])) $this->schemas[$table] = $this->db->get_table_definitions(true, $table); - - $cols = self::csv_split($line=fgets($f)); ++$n; - $blobs = array(); - foreach($this->schemas[$table]['fd'] as $col => $data) - { - if ($data['type'] == 'blob') $blobs[] = $col; - } - - if (feof($f)) break; - continue; - } - if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) - { - if ($GLOBALS['egw_setup']) - { - if (!is_object($GLOBALS['egw_setup']->translation->sql)) - { - $GLOBALS['egw_setup']->translation->setup_translation_sql(); - } - } - } - if ($table) // do we already reached the data part - { - $import = true; - $data = self::csv_split($line, $cols, $blobs); - - if ($table == 'egw_async' && in_array('##last-check-run##',$data)) - { - //echo '
'.lang("Line %1: '%2'
csv data does contain ##last-check-run## of table %3 ==> ignored",$n,$line,$table)."
'.print_r($data,true)."\n"; - $import = false; - } - if (in_array($table,$this->exclude_tables)) - { - echo '
'.lang("Table %1 is excluded from backup and restore. Data will not be restored.",$table)."
\n"; - $import = false; // dont restore data of excluded tables - } - if ($import) - { - if (count($data) == count($cols)) - { - if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) - { - $data = translation::convert($data,$charset); - } - if ($insert_n_rows > 1) - { - $rows[] = $data; - if (count($rows) == $insert_n_rows) - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - $rows = array(); - } - } - else - { - $this->db->insert($table,$data,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - } - else - { - echo ''.lang("Line %1: '%2'
csv data does not match column-count of table %3 ==> ignored",$n,$line,$table)."
'.print_r($data,true)."\n"; - } - } - } - } - if ($rows) // flush pending rows - { - $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); - } - // updated the sequences, if the DB uses them - foreach($this->schemas as $table => $schema) - { - foreach($schema['fd'] as $column => $definition) - { - if ($definition['type'] == 'auto') - { - $this->schema_proc->UpdateSequence($table,$column); - break; // max. one per table - } - } - } + $this->db_restore($f, $insert_n_rows); if ($convert_to_system_charset) // store the changed charset { @@ -652,6 +504,177 @@ class db_backup 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; + $table = False; + $n = 0; + $rows = array(); + while(!feof($f)) + { + $line = trim(fgets($f)); ++$n; + + if (empty($line)) continue; + + if (substr($line,0,9) == 'version: ') + { + $api_version = trim(substr($line,9)); + continue; + } + if (substr($line,0,9) == 'charset: ') + { + $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); + + // 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($this->schema_proc->system_charset, $charset)) + { + $convert_to_system_charset = false; // no conversation necessary + } + // 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'))) + { + $this->db->Link_ID->SetCharSet($charset); + if (!$convert_to_system_charset) + { + $this->schema_proc->system_charset = $charset; // so schema_proc uses it for the creation of the tables + } + } + continue; + } + if (substr($line,0,8) == 'schema: ') + { + // create the tables in the backup set + $this->schemas = json_php_unserialize(trim(substr($line,8))); + foreach($this->schemas as $table_name => $schema) + { + // if column is longtext in current schema, convert text to longtext, in case user already updated column + foreach($schema['fd'] as $col => &$def) + { + if ($def['type'] == 'text' && $this->db->get_column_attribute($col, $table_name, true, 'type') == 'longtext') + { + $def['type'] = 'longtext'; + } + } + //echo "
$table_name => ".self::write_array($schema,1)."\n"; + $this->schema_proc->CreateTable($table_name, $schema); + } + continue; + } + if (substr($line,0,7) == 'table: ') + { + if ($rows) // flush pending rows of last table + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + $rows = array(); + $table = substr($line,7); + if (!isset($this->schemas[$table])) $this->schemas[$table] = $this->db->get_table_definitions(true, $table); + $auto_id = count($this->schemas[$table]['pk']) == 1 ? $this->schemas[$table]['pk'][0] : null; + + $cols = self::csv_split($line=fgets($f)); ++$n; + $blobs = array(); + foreach($this->schemas[$table]['fd'] as $col => $data) + { + if ($data['type'] == 'blob') $blobs[] = $col; + } + + if (feof($f)) break; + continue; + } + if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) + { + if ($GLOBALS['egw_setup']) + { + if (!is_object($GLOBALS['egw_setup']->translation->sql)) + { + $GLOBALS['egw_setup']->translation->setup_translation_sql(); + } + } + } + if ($table) // do we already reached the data part + { + $import = true; + $data = self::csv_split($line, $cols, $blobs); + + if ($table == 'egw_async' && in_array('##last-check-run##',$data)) + { + //echo '
'.lang("Line %1: '%2'
csv data does contain ##last-check-run## of table %3 ==> ignored",$n,$line,$table)."
'.print_r($data,true)."\n"; + $import = false; + } + if (in_array($table,$this->exclude_tables)) + { + echo '
'.lang("Table %1 is excluded from backup and restore. Data will not be restored.",$table)."
\n"; + $import = false; // dont restore data of excluded tables + } + if ($import) + { + if (count($data) == count($cols)) + { + if ($convert_to_system_charset && !$this->db->capabilities['client_encoding']) + { + $data = translation::convert($data,$charset); + } + if ($insert_n_rows > 1) + { + $rows[] = $data; + if (count($rows) == $insert_n_rows) + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + $rows = array(); + } + } + // 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]); + } + else + { + $this->db->insert($table,$data,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + } + else + { + echo ''.lang("Line %1: '%2'
csv data does not match column-count of table %3 ==> ignored",$n,$line,$table)."
'.print_r($data,true)."\n"; + } + } + } + } + if ($rows) // flush pending rows + { + $this->db->insert($table,$rows,False,__LINE__,__FILE__,false,false,$this->schemas[$table]); + } + // updated the sequences, if the DB uses them + foreach($this->schemas as $table => $schema) + { + foreach($schema['fd'] as $column => $definition) + { + if ($definition['type'] == 'auto') + { + $this->schema_proc->UpdateSequence($table,$column); + break; // max. one per table + } + } + } + return $n; + } + /** * Removes a dir, no matter whether it is empty or full * diff --git a/phpgwapi/inc/class.egw_db.inc.php b/phpgwapi/inc/class.egw_db.inc.php index aca8154484..8cb611a875 100644 --- a/phpgwapi/inc/class.egw_db.inc.php +++ b/phpgwapi/inc/class.egw_db.inc.php @@ -1750,7 +1750,7 @@ class egw_db $this->select($table,'count(*)',$where,$line,$file); if ($this->next_record() && $this->f(0)) { - return !!$this->update($table,$data,$where,$line,$file,$app); + return !!$this->update($table,$data,$where,$line,$file,$app,$use_prepared_statement,$table_def); } break; }