mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 01:13:25 +01:00
* All apps/REST API: fix custom-fields of type "date-time" to be stored timezone aware, if no format is specified
So far date-time values were stored in user-time, now they are stored in UTC with a "Z" suffix" to be able to still read old user-time values unchanged.
This commit is contained in:
parent
24a5ac6558
commit
0453aede6c
@ -149,9 +149,10 @@ class JsBase
|
|||||||
*
|
*
|
||||||
* @param array $contact
|
* @param array $contact
|
||||||
* @param ?string $app default self::APP
|
* @param ?string $app default self::APP
|
||||||
|
* @param ?string $timezone optional timezone-name to use für date-time types, default UTC
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function customfields(array $contact, ?string $app=null)
|
protected static function customfields(array $contact, ?string $app=null, ?string $timezone=null)
|
||||||
{
|
{
|
||||||
$fields = [];
|
$fields = [];
|
||||||
foreach(Api\Storage\Customfields::get($app ?? static::APP) as $name => $data)
|
foreach(Api\Storage\Customfields::get($app ?? static::APP) as $name => $data)
|
||||||
@ -162,7 +163,7 @@ class JsBase
|
|||||||
switch($data['type'])
|
switch($data['type'])
|
||||||
{
|
{
|
||||||
case 'date-time':
|
case 'date-time':
|
||||||
$value = Api\DateTime::to($value, Api\DateTime::RFC3339);
|
$value = empty($timezone) ? self::UTCDateTime($value) : self::DateTime($value, $timezone);
|
||||||
break;
|
break;
|
||||||
case 'float':
|
case 'float':
|
||||||
$value = (double)$value;
|
$value = (double)$value;
|
||||||
@ -193,9 +194,10 @@ class JsBase
|
|||||||
*
|
*
|
||||||
* @param array $cfs name => object with attribute data and optional type, label, values
|
* @param array $cfs name => object with attribute data and optional type, label, values
|
||||||
* @param ?string $app default self::APP
|
* @param ?string $app default self::APP
|
||||||
|
* @param ?string $timeZone timezone-name given in JSON data
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected static function parseCustomfields(array $cfs, ?string $app=null)
|
protected static function parseCustomfields(array $cfs, ?string $app=null, ?string $timeZone=null)
|
||||||
{
|
{
|
||||||
$contact = [];
|
$contact = [];
|
||||||
$definitions = Api\Storage\Customfields::get($app ?? static::APP);
|
$definitions = Api\Storage\Customfields::get($app ?? static::APP);
|
||||||
@ -216,7 +218,7 @@ class JsBase
|
|||||||
switch($definition['type'])
|
switch($definition['type'])
|
||||||
{
|
{
|
||||||
case 'date-time':
|
case 'date-time':
|
||||||
$data['value'] = Api\DateTime::to($data['value'], 'object');
|
$data['value'] = self::parseDateTime($data['value'], $timeZone);
|
||||||
break;
|
break;
|
||||||
case 'float':
|
case 'float':
|
||||||
$data['value'] = (double)$data['value'];
|
$data['value'] = (double)$data['value'];
|
||||||
|
@ -68,7 +68,7 @@ class JsCalendar extends JsBase
|
|||||||
'priority' => self::Priority($event['priority']),
|
'priority' => self::Priority($event['priority']),
|
||||||
'categories' => self::categories($event['category']),
|
'categories' => self::categories($event['category']),
|
||||||
'privacy' => $event['public'] ? 'public' : 'private',
|
'privacy' => $event['public'] ? 'public' : 'private',
|
||||||
'egroupware.org:customfields' => self::customfields($event),
|
'egroupware.org:customfields' => self::customfields($event, null, $event['tzid']),
|
||||||
] + self::Locations($event);
|
] + self::Locations($event);
|
||||||
|
|
||||||
if (!empty($event['recur_type']) || $exceptions)
|
if (!empty($event['recur_type']) || $exceptions)
|
||||||
@ -113,12 +113,12 @@ class JsCalendar extends JsBase
|
|||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
// apply patch on JsEvent
|
// apply patch on JsEvent
|
||||||
$data = self::patch($data, $old ? self::getJsCalendar($old, false) : [], !$old || !$strict);
|
$data = self::patch($data, $old ? self::JsEvent($old, false) : [], !$old || !$strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||||
|
|
||||||
$event = [];
|
$event = $old ? ['id' => $old['id']] : [];
|
||||||
foreach ($data as $name => $value)
|
foreach ($data as $name => $value)
|
||||||
{
|
{
|
||||||
switch ($name)
|
switch ($name)
|
||||||
@ -181,7 +181,7 @@ class JsCalendar extends JsBase
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'egroupware.org:customfields':
|
case 'egroupware.org:customfields':
|
||||||
$event = array_merge($event, self::parseCustomfields($value, $strict));
|
$event = array_merge($event, self::parseCustomfields($value, 'calendar', $data['timeZone']));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'prodId':
|
case 'prodId':
|
||||||
@ -256,7 +256,7 @@ class JsCalendar extends JsBase
|
|||||||
'egroupware.org:completed' => $entry['info_datecomplete'] ?
|
'egroupware.org:completed' => $entry['info_datecomplete'] ?
|
||||||
self::DateTime($entry['info_datecompleted'], Api\DateTime::$user_timezone->getName()) : null,
|
self::DateTime($entry['info_datecompleted'], Api\DateTime::$user_timezone->getName()) : null,
|
||||||
] + self::Locations(['location' => $entry['info_location'] ?? null]) + self::relatedToParent($entry['info_id_parent']) + [
|
] + self::Locations(['location' => $entry['info_location'] ?? null]) + self::relatedToParent($entry['info_id_parent']) + [
|
||||||
'egroupware.org:customfields' => self::customfields($entry, 'infolog'),
|
'egroupware.org:customfields' => self::customfields($entry, 'infolog', Api\DateTime::$user_timezone->getName()),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!empty($entry['##RRULE']))
|
if (!empty($entry['##RRULE']))
|
||||||
@ -301,12 +301,12 @@ class JsCalendar extends JsBase
|
|||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
// apply patch on JsEvent
|
// apply patch on JsEvent
|
||||||
$data = self::patch($data, $old ? self::getJsTask($old, false) : [], !$old || !$strict);
|
$data = self::patch($data, $old ? self::JsTask($old, false) : [], !$old || !$strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
if (!isset($data['uid'])) $data['uid'] = null; // to fail below, if it does not exist
|
||||||
|
|
||||||
$event = [];
|
$event = $old ? ['info_id' => $old['info_id']] : [];
|
||||||
foreach ($data as $name => $value)
|
foreach ($data as $name => $value)
|
||||||
{
|
{
|
||||||
switch ($name)
|
switch ($name)
|
||||||
@ -368,7 +368,7 @@ class JsCalendar extends JsBase
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'egroupware.org:customfields':
|
case 'egroupware.org:customfields':
|
||||||
$event = array_merge($event, self::parseCustomfields($value, 'infolog'));
|
$event = array_merge($event, self::parseCustomfields($value, 'infolog', $data['timeZone']));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'egroupware.org:type':
|
case 'egroupware.org:type':
|
||||||
|
@ -273,8 +273,9 @@ class Storage
|
|||||||
$this->contact_repository = 'sql-ldap';
|
$this->contact_repository = 'sql-ldap';
|
||||||
}
|
}
|
||||||
$this->somain = new Sql($db);
|
$this->somain = new Sql($db);
|
||||||
|
$this->somain->add_cf_timestamps();
|
||||||
|
|
||||||
// remove some columns, absolutly not necessary to search in sql
|
// remove some columns, absolutely not necessary to search in sql
|
||||||
$this->columns_to_search = array_diff(array_values($this->somain->db_cols),$this->sql_cols_not_to_search);
|
$this->columns_to_search = array_diff(array_values($this->somain->db_cols),$this->sql_cols_not_to_search);
|
||||||
}
|
}
|
||||||
$this->grants = $this->get_grants($this->user,$contact_app);
|
$this->grants = $this->get_grants($this->user,$contact_app);
|
||||||
|
@ -33,7 +33,7 @@ use DateInterval;
|
|||||||
* - Api\DateTime::user2server($time,$type=null)
|
* - Api\DateTime::user2server($time,$type=null)
|
||||||
* (Replacing in 1.6 and previous used adding of tz_offset, which is only correct for current time)
|
* (Replacing in 1.6 and previous used adding of tz_offset, which is only correct for current time)
|
||||||
*
|
*
|
||||||
* An other static method allows to format any time in several ways: Api\DateTime::to($time,$type) (exceed date($type,$time)).
|
* Another static method allows to format any time in several ways: Api\DateTime::to($time,$type) (exceed date($type,$time)).
|
||||||
*
|
*
|
||||||
* The constructor of Api\DateTime understand - in addition to DateTime - integer timestamps, array with values for
|
* The constructor of Api\DateTime understand - in addition to DateTime - integer timestamps, array with values for
|
||||||
* keys: ('year', 'month', 'day') or 'full' plus 'hour', 'minute' and optional 'second' or a DateTime object as parameter.
|
* keys: ('year', 'month', 'day') or 'full' plus 'hour', 'minute' and optional 'second' or a DateTime object as parameter.
|
||||||
@ -122,7 +122,7 @@ class DateTime extends \DateTime
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch(Exception $e) {
|
catch(Exception $e) {
|
||||||
// if string is nummeric, ignore the exception and treat string as timestamp
|
// if string is numeric, ignore the exception and treat string as timestamp
|
||||||
if (!is_numeric($time)) throw $e;
|
if (!is_numeric($time)) throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,27 @@ class Storage extends Storage\Base
|
|||||||
$this->customfields = Storage\Customfields::get($app, false, null, $db);
|
$this->customfields = Storage\Customfields::get($app, false, null, $db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all CFs of type date-time to $this->timestamps to get automatically converted to and from usertime
|
||||||
|
*/
|
||||||
|
function convert_all_timestamps()
|
||||||
|
{
|
||||||
|
parent::convert_all_timestamps();
|
||||||
|
|
||||||
|
$this->add_cf_timestamps();
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_cf_timestamps()
|
||||||
|
{
|
||||||
|
foreach($this->customfields ?? [] as $name => $cf)
|
||||||
|
{
|
||||||
|
if ($cf['type'] === 'date-time' && !in_array($field=$this->get_cf_field($name), $this->timestamps))
|
||||||
|
{
|
||||||
|
$this->timestamps[] = $field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read all customfields of the given id's
|
* Read all customfields of the given id's
|
||||||
*
|
*
|
||||||
@ -203,6 +224,12 @@ class Storage extends Storage\Base
|
|||||||
{
|
{
|
||||||
$entry[$field][] = $row[$this->extra_value];
|
$entry[$field][] = $row[$this->extra_value];
|
||||||
}
|
}
|
||||||
|
// old date-time CFs are stored in user-time, new ones in UTC with "Z" suffix, we always return them now as DateTime objects
|
||||||
|
elseif (($this->customfields[$row[$this->extra_key]]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[$row[$this->extra_key]]['values']['format'])) // but only if they have no format specified
|
||||||
|
{
|
||||||
|
$entry[$field] = new DateTime($row[$this->extra_value], DateTime::$user_timezone);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$entry[$field] = $row[$this->extra_value];
|
$entry[$field] = $row[$this->extra_value];
|
||||||
@ -224,7 +251,7 @@ class Storage extends Storage\Base
|
|||||||
|
|
||||||
Customfields::handle_files($this->app, $id, $data, $this->customfields);
|
Customfields::handle_files($this->app, $id, $data, $this->customfields);
|
||||||
|
|
||||||
foreach (array_keys((array)$this->customfields) as $name)
|
foreach ((array)$this->customfields as $name => $cf)
|
||||||
{
|
{
|
||||||
if (!isset($data[$field = $this->get_cf_field($name)])) continue;
|
if (!isset($data[$field = $this->get_cf_field($name)])) continue;
|
||||||
|
|
||||||
@ -240,10 +267,17 @@ class Storage extends Storage\Base
|
|||||||
$this->db->delete($this->extra_table,$where,__LINE__,__FILE__,$this->app);
|
$this->db->delete($this->extra_table,$where,__LINE__,__FILE__,$this->app);
|
||||||
if (empty($data[$field])) continue; // nothing else to do for empty values
|
if (empty($data[$field])) continue; // nothing else to do for empty values
|
||||||
}
|
}
|
||||||
foreach($is_multiple && !is_array($data[$field]) ? explode(',',$data[$field]) :
|
foreach($is_multiple && is_string($data[$field]) ? explode(',',$data[$field]) :
|
||||||
// regular custom fields (!$is_multiple) eg. addressbook store multiple values comma-separated
|
// regular custom fields (!$is_multiple) eg. addressbook store multiple values comma-separated
|
||||||
(array)(!$is_multiple && is_array($data[$field]) ? implode(',', $data[$field]) : $data[$field]) as $value)
|
[!$is_multiple && is_array($data[$field]) ? implode(',', $data[$field]) : $data[$field]] as $value)
|
||||||
{
|
{
|
||||||
|
// store type date-time in UTC with "Z" suffix, to be able to distinguish them from old date-time stored in user-time!
|
||||||
|
if ($cf['type'] === 'date-time' && empty($cf['values']['format'])) // but only if they have no format specified
|
||||||
|
{
|
||||||
|
$time = new DateTime($value, DateTime::$server_timezone);
|
||||||
|
$time->setTimezone(new \DateTimeZone('UTC'));
|
||||||
|
$value = $time->format('Y-m-d H:i:s').'Z';
|
||||||
|
}
|
||||||
if (!$this->db->insert($this->extra_table,array($this->extra_value => $value)+$extra_cols,$where,__LINE__,__FILE__,$this->app))
|
if (!$this->db->insert($this->extra_table,array($this->extra_value => $value)+$extra_cols,$where,__LINE__,__FILE__,$this->app))
|
||||||
{
|
{
|
||||||
return $this->db->Errno;
|
return $this->db->Errno;
|
||||||
@ -320,7 +354,17 @@ class Storage extends Storage\Base
|
|||||||
|
|
||||||
if ($ret == 0 && $this->customfields)
|
if ($ret == 0 && $this->customfields)
|
||||||
{
|
{
|
||||||
|
// if we have date-time custom-fields, we have to convert them from user-time
|
||||||
|
if ($this->timestamps && $this->is_cf(end($this->timestamps)))
|
||||||
|
{
|
||||||
|
$this->data2db(); // save has already reverted timezone of timestamps again back to user-time
|
||||||
$this->save_customfields($this->data);
|
$this->save_customfields($this->data);
|
||||||
|
$this->db2data();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->save_customfields($this->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
@ -121,14 +121,14 @@ class Base
|
|||||||
* Possible values:
|
* Possible values:
|
||||||
* - 'ts'|'integer' convert every timestamp to an integer unix timestamp
|
* - 'ts'|'integer' convert every timestamp to an integer unix timestamp
|
||||||
* - 'string' convert every timestamp to a 'Y-m-d H:i:s' string
|
* - 'string' convert every timestamp to a 'Y-m-d H:i:s' string
|
||||||
* - 'object' convert every timestamp to a Api\DateTime object
|
* - 'object' convert every timestamp to an Api\DateTime object
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $timestamp_type;
|
public $timestamp_type;
|
||||||
/**
|
/**
|
||||||
* Offset in secconds between user and server-time, it need to be add to a server-time to get the user-time
|
* Offset in seconds between user and server-time, it need to be add to a server-time to get the user-time
|
||||||
* or substracted from a user-time to get the server-time
|
* or subtracted from a user-time to get the server-time
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
* @deprecated use Api\DateTime methods instead, as the offset between user and server time is only valid for current time
|
* @deprecated use Api\DateTime methods instead, as the offset between user and server time is only valid for current time
|
||||||
|
@ -1018,11 +1018,11 @@ class calendar_bo
|
|||||||
$timestamps = array('start','end','modified','created','recur_enddate','recurrence','recur_date','deleted');
|
$timestamps = array('start','end','modified','created','recur_enddate','recurrence','recur_date','deleted');
|
||||||
}
|
}
|
||||||
// we convert here from the server-time timestamps to user-time and (optional) to a different date-format!
|
// we convert here from the server-time timestamps to user-time and (optional) to a different date-format!
|
||||||
foreach ($timestamps as $ts)
|
foreach (array_merge($timestamps, $this->getCfTtimestamps()) as $ts)
|
||||||
{
|
{
|
||||||
if (!empty($event[$ts]))
|
if (!empty($event[$ts]))
|
||||||
{
|
{
|
||||||
$event[$ts] = $this->date2usertime((int)$event[$ts],$date_format);
|
$event[$ts] = $this->date2usertime($event[$ts], $date_format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// same with the recur exceptions and rdates
|
// same with the recur exceptions and rdates
|
||||||
@ -1054,6 +1054,22 @@ class calendar_bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCfTtimestamps()
|
||||||
|
{
|
||||||
|
static $cf_timestamps=null;
|
||||||
|
if (!isset($cf_timestamps))
|
||||||
|
{
|
||||||
|
$cf_timestamps = array_map(static function($name)
|
||||||
|
{
|
||||||
|
return '#' . $name;
|
||||||
|
}, array_keys(array_filter($this->customfields?:[], static function ($cf)
|
||||||
|
{
|
||||||
|
return $cf['type'] === 'date-time';
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
return $cf_timestamps;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert a date from server to user-time
|
* convert a date from server to user-time
|
||||||
*
|
*
|
||||||
|
@ -1601,7 +1601,7 @@ class calendar_boupdate extends calendar_bo
|
|||||||
$timestamps = array('start','end','modified','created','recur_enddate','recurrence');
|
$timestamps = array('start','end','modified','created','recur_enddate','recurrence');
|
||||||
}
|
}
|
||||||
// we run all dates through date2ts, to adjust to server-time and the possible date-formats
|
// we run all dates through date2ts, to adjust to server-time and the possible date-formats
|
||||||
foreach($timestamps as $ts)
|
foreach(array_merge($timestamps, $this->getCfTtimestamps()) as $ts)
|
||||||
{
|
{
|
||||||
// we convert here from user-time to timestamps in server-time!
|
// we convert here from user-time to timestamps in server-time!
|
||||||
if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 0;
|
if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 0;
|
||||||
|
@ -133,6 +133,8 @@ class calendar_so
|
|||||||
*/
|
*/
|
||||||
protected static $tz_cache = array();
|
protected static $tz_cache = array();
|
||||||
|
|
||||||
|
protected $customfields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor of the socal class
|
* Constructor of the socal class
|
||||||
*/
|
*/
|
||||||
@ -147,6 +149,7 @@ class calendar_so
|
|||||||
$vname = $name.'_table';
|
$vname = $name.'_table';
|
||||||
$this->all_tables[] = $this->$vname = $this->cal_table.'_'.$name;
|
$this->all_tables[] = $this->$vname = $this->cal_table.'_'.$name;
|
||||||
}
|
}
|
||||||
|
$this->customfields = Api\Storage\Customfields::get('calendar');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -511,9 +514,18 @@ class calendar_so
|
|||||||
|
|
||||||
// custom fields
|
// custom fields
|
||||||
foreach($this->db->select($this->extra_table,'*',array('cal_id'=>$ids),__LINE__,__FILE__,false,'','calendar') as $row)
|
foreach($this->db->select($this->extra_table,'*',array('cal_id'=>$ids),__LINE__,__FILE__,false,'','calendar') as $row)
|
||||||
|
{
|
||||||
|
// old date-time CFs are stored in user-time, new ones in UTC with "Z" suffix, we always return them now as DateTime objects
|
||||||
|
if (($this->customfields[$row['cal_extra_name']]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[$row['cal_extra_name']]['values']['format'])) // but only if they have no format specified)
|
||||||
|
{
|
||||||
|
$events[$row['cal_id']]['#'.$row['cal_extra_name']] = new Api\DateTime($row['cal_extra_value'], Api\DateTime::$user_timezone);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$events[$row['cal_id']]['#'.$row['cal_extra_name']] = $row['cal_extra_value'];
|
$events[$row['cal_id']]['#'.$row['cal_extra_name']] = $row['cal_extra_value'];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// alarms
|
// alarms
|
||||||
if (is_array($ids))
|
if (is_array($ids))
|
||||||
@ -1507,7 +1519,7 @@ ORDER BY cal_user_type, cal_usre_id
|
|||||||
|
|
||||||
$event['cal_category'] = implode(',',$categories);
|
$event['cal_category'] = implode(',',$categories);
|
||||||
|
|
||||||
// make sure recurring events never reference to an other recurrent event
|
// make sure recurring events never reference to another recurrent event
|
||||||
if (!empty($event['recur_type'])) $event['cal_reference'] = 0;
|
if (!empty($event['recur_type'])) $event['cal_reference'] = 0;
|
||||||
|
|
||||||
if ($cal_id)
|
if ($cal_id)
|
||||||
@ -1611,7 +1623,7 @@ ORDER BY cal_user_type, cal_usre_id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // write information about recuring event, if recur_type is present in the array
|
else // write information about recurring event, if recur_type is present in the array
|
||||||
{
|
{
|
||||||
// fetch information about the currently saved (old) event
|
// fetch information about the currently saved (old) event
|
||||||
$old_min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
|
$old_min = (int) $this->db->select($this->dates_table,'MIN(cal_start)',array('cal_id'=>$cal_id),__LINE__,__FILE__,false,'','calendar')->fetchColumn();
|
||||||
@ -1763,6 +1775,14 @@ ORDER BY cal_user_type, cal_usre_id
|
|||||||
}
|
}
|
||||||
if ($value)
|
if ($value)
|
||||||
{
|
{
|
||||||
|
// store type date-time in UTC with "Z" suffix, to be able to distinguish them from old date-time stored in user-time!
|
||||||
|
if (($this->customfields[substr($name, 1)]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[substr($name, 1)]['values']['format'])) // but only if they have no format specified)
|
||||||
|
{
|
||||||
|
$time = new Api\DateTime($value, Api\DateTime::$server_timezone);
|
||||||
|
$time->setTimezone(new DateTimeZone('UTC'));
|
||||||
|
$value = $time->format('Y-m-d H:i:s').'Z';
|
||||||
|
}
|
||||||
$this->db->insert($this->extra_table,array(
|
$this->db->insert($this->extra_table,array(
|
||||||
'cal_extra_value' => is_array($value) ? implode(',',$value) : $value,
|
'cal_extra_value' => is_array($value) ? implode(',',$value) : $value,
|
||||||
),array(
|
),array(
|
||||||
|
@ -141,7 +141,7 @@ class infolog_bo
|
|||||||
var $tracking;
|
var $tracking;
|
||||||
/**
|
/**
|
||||||
* Maximum number of line characters (-_+=~) allowed in a mail, to not stall the layout.
|
* Maximum number of line characters (-_+=~) allowed in a mail, to not stall the layout.
|
||||||
* Longer lines / biger number of these chars are truncated to that max. number or chars.
|
* Longer lines / bigger number of these chars are truncated to that max. number or chars.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
@ -270,6 +270,11 @@ class infolog_bo
|
|||||||
$this->customfields[$name] = $field;
|
$this->customfields[$name] = $field;
|
||||||
$save_config = true;
|
$save_config = true;
|
||||||
}
|
}
|
||||||
|
// add date-time CFs to timestamps to ensure TZ conversation
|
||||||
|
if ($field['type'] === 'date-time')
|
||||||
|
{
|
||||||
|
$this->timestamps[] = '#'.$field['name'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!empty($save_config)) Api\Config::save_value('customfields',$this->customfields,'infolog');
|
if (!empty($save_config)) Api\Config::save_value('customfields',$this->customfields,'infolog');
|
||||||
}
|
}
|
||||||
@ -554,9 +559,6 @@ class infolog_bo
|
|||||||
*/
|
*/
|
||||||
function time2time(&$values, $fromTZId=false, $toTZId=null, $type='ts')
|
function time2time(&$values, $fromTZId=false, $toTZId=null, $type='ts')
|
||||||
{
|
{
|
||||||
|
|
||||||
if ($fromTZId === $toTZId) return;
|
|
||||||
|
|
||||||
$tz = Api\DateTime::$server_timezone;
|
$tz = Api\DateTime::$server_timezone;
|
||||||
|
|
||||||
if ($fromTZId)
|
if ($fromTZId)
|
||||||
@ -634,9 +636,9 @@ class infolog_bo
|
|||||||
*
|
*
|
||||||
* @param int|array $info_id integer id or array with id's or array with column=>value pairs of the entry to read
|
* @param int|array $info_id integer id or array with id's or array with column=>value pairs of the entry to read
|
||||||
* @param boolean $run_link_id2from = true should link_id2from run, default yes,
|
* @param boolean $run_link_id2from = true should link_id2from run, default yes,
|
||||||
* need to be set to false if called from link-title to prevent an infinit recursion
|
* need to be set to false if called from link-title to prevent an infinite recursion
|
||||||
* @param string $date_format = 'ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time,
|
* @param string $date_format = 'ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time,
|
||||||
* 'array'=array or string with date-format
|
* 'array'=array or string with date-format, 'object' DateTime objects
|
||||||
* @param boolean $ignore_acl = false if true, do NOT check access, default false
|
* @param boolean $ignore_acl = false if true, do NOT check access, default false
|
||||||
*
|
*
|
||||||
* @return array|boolean infolog entry, null if not found or false if no permission to read it
|
* @return array|boolean infolog entry, null if not found or false if no permission to read it
|
||||||
|
@ -69,6 +69,10 @@ class infolog_so
|
|||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
var $tz_offset;
|
var $tz_offset;
|
||||||
|
/**
|
||||||
|
* @var array $customfields if defined
|
||||||
|
*/
|
||||||
|
protected array $customfields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@ -84,6 +88,8 @@ class infolog_so
|
|||||||
$this->user = $GLOBALS['egw_info']['user']['account_id'];
|
$this->user = $GLOBALS['egw_info']['user']['account_id'];
|
||||||
|
|
||||||
$this->tz_offset = $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
|
$this->tz_offset = $GLOBALS['egw_info']['user']['preferences']['common']['tz_offset'];
|
||||||
|
|
||||||
|
$this->customfields = Api\Storage\Customfields::get('infolog');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,9 +453,18 @@ class infolog_so
|
|||||||
// Cast back to integer
|
// Cast back to integer
|
||||||
$this->data['info_id_parent'] = (int)$this->data['info_id_parent'];
|
$this->data['info_id_parent'] = (int)$this->data['info_id_parent'];
|
||||||
foreach($this->db->select($this->extra_table,'info_extra_name,info_extra_value',array('info_id'=>$this->data['info_id']),__LINE__,__FILE__) as $row)
|
foreach($this->db->select($this->extra_table,'info_extra_name,info_extra_value',array('info_id'=>$this->data['info_id']),__LINE__,__FILE__) as $row)
|
||||||
|
{
|
||||||
|
// old date-time CFs are stored in user-time, new ones in UTC with "Z" suffix, we always return them now as DateTime objects
|
||||||
|
if (($this->customfields[$row['info_extra_name']]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[$row['info_extra_name']]['values']['format'])) // but only if they have no format specified)
|
||||||
|
{
|
||||||
|
$this->data['#'.$row['info_extra_name']] = new Api\DateTime($row['info_extra_value'], Api\DateTime::$user_timezone);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$this->data['#'.$row['info_extra_name']] = $row['info_extra_value'];
|
$this->data['#'.$row['info_extra_name']] = $row['info_extra_value'];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//error_log(__METHOD__.'('.array2string($where).') returning '.array2string($this->data));
|
//error_log(__METHOD__.'('.array2string($where).') returning '.array2string($this->data));
|
||||||
return $this->data;
|
return $this->data;
|
||||||
}
|
}
|
||||||
@ -663,6 +678,14 @@ class infolog_so
|
|||||||
|
|
||||||
if ($val)
|
if ($val)
|
||||||
{
|
{
|
||||||
|
// store type date-time in UTC with "Z" suffix, to be able to distinguish them from old date-time stored in user-time!
|
||||||
|
if (($this->customfields[substr($key, 1)]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[substr($key, 1)]['values']['format'])) // but only if they have no format specified)
|
||||||
|
{
|
||||||
|
$time = new Api\DateTime($val, Api\DateTime::$server_timezone);
|
||||||
|
$time->setTimezone(new DateTimeZone('UTC'));
|
||||||
|
$val = $time->format('Y-m-d H:i:s').'Z';
|
||||||
|
}
|
||||||
$this->db->insert($this->extra_table,array(
|
$this->db->insert($this->extra_table,array(
|
||||||
// store multivalued CalDAV properties as serialized array, everything else get comma-separated
|
// store multivalued CalDAV properties as serialized array, everything else get comma-separated
|
||||||
'info_extra_value' => is_array($val) ? ($key[1] == '#' ? json_encode($val) : implode(',',$val)) : $val,
|
'info_extra_value' => is_array($val) ? ($key[1] == '#' ? json_encode($val) : implode(',',$val)) : $val,
|
||||||
@ -1053,11 +1076,20 @@ class infolog_so
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach($this->db->select($this->extra_table,'*',$where,__LINE__,__FILE__) as $row)
|
foreach($this->db->select($this->extra_table,'*',$where,__LINE__,__FILE__) as $row)
|
||||||
|
{
|
||||||
|
// old date-time CFs are stored in user-time, new ones in UTC with "Z" suffix, we always return them now as DateTime objects
|
||||||
|
if (($this->customfields[$row['info_extra_name']]['type']??null) === 'date-time' &&
|
||||||
|
empty($this->customfields[$row['info_extra_name']]['values']['format'])) // but only if they have no format specified)
|
||||||
|
{
|
||||||
|
$ids[$row['info_id']]['#'.$row['info_extra_name']] = new Api\DateTime($row['info_extra_value'], Api\DateTime::$user_timezone);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
$ids[$row['info_id']]['#'.$row['info_extra_name']] = $row['info_extra_value'];
|
$ids[$row['info_id']]['#'.$row['info_extra_name']] = $row['info_extra_value'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$query['start'] = $query['total'] = 0;
|
$query['start'] = $query['total'] = 0;
|
||||||
|
@ -51,14 +51,6 @@ class timesheet_bo extends Api\Storage
|
|||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
var $user;
|
var $user;
|
||||||
/**
|
|
||||||
* Timestaps that need to be adjusted to user-time on reading or saving
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
var $timestamps = array(
|
|
||||||
'ts_start','ts_created', 'ts_modified'
|
|
||||||
);
|
|
||||||
/**
|
/**
|
||||||
* Start of today in user-time
|
* Start of today in user-time
|
||||||
*
|
*
|
||||||
@ -170,6 +162,8 @@ class timesheet_bo extends Api\Storage
|
|||||||
{
|
{
|
||||||
parent::__construct(TIMESHEET_APP,self::TABLE,self::EXTRA_TABLE,'','ts_extra_name','ts_extra_value','ts_id');
|
parent::__construct(TIMESHEET_APP,self::TABLE,self::EXTRA_TABLE,'','ts_extra_name','ts_extra_value','ts_id');
|
||||||
|
|
||||||
|
$this->convert_all_timestamps();
|
||||||
|
|
||||||
$this->config_data = Api\Config::read(TIMESHEET_APP);
|
$this->config_data = Api\Config::read(TIMESHEET_APP);
|
||||||
$this->quantity_sum = $this->config_data['quantity_sum'] == 'true';
|
$this->quantity_sum = $this->config_data['quantity_sum'] == 'true';
|
||||||
|
|
||||||
@ -1069,7 +1063,7 @@ class timesheet_bo extends Api\Storage
|
|||||||
{
|
{
|
||||||
$data =& $this->data;
|
$data =& $this->data;
|
||||||
}
|
}
|
||||||
// allways store ts_project to be able to search for it, even if no custom project is set
|
// always store ts_project to be able to search for it, even if no custom project is set
|
||||||
if (empty($data['ts_project']) && !is_null($data['ts_project']))
|
if (empty($data['ts_project']) && !is_null($data['ts_project']))
|
||||||
{
|
{
|
||||||
$data['ts_project'] = $data['pm_id'] ? Link::title('projectmanager', $data['pm_id']) : '';
|
$data['ts_project'] = $data['pm_id'] ? Link::title('projectmanager', $data['pm_id']) : '';
|
||||||
|
@ -123,7 +123,7 @@ class JsTimesheet extends Api\CalDAV\JsBase
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'start':
|
case 'start':
|
||||||
$timesheet['ts_start'] = Api\DateTime::server2user($value, 'ts');
|
$timesheet['ts_start'] = self::parseDateTime($value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'duration':
|
case 'duration':
|
||||||
|
Loading…
Reference in New Issue
Block a user