From 3dc0e31fa900cf805aea972431829ddd3b85db29 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Fri, 10 Feb 2012 10:39:27 +0000 Subject: [PATCH] * InfoLog/CalDAV: store unsupported iCal properties like custom fields incl. history logging, thought they are not displayed unless you explicitly add a custom field from them (prefixed with one #) r37832: fixed fatal error calling widgetExists on a non-object r37837: storing unsupported (valarm-)components like unsupported (X-)properties r37854: need to load custom fields now allways, as x-props and unsupported components are stored there --- etemplate/inc/class.bo_tracking.inc.php | 9 +- etemplate/inc/class.historylog_widget.inc.php | 177 +++++++++++------- infolog/inc/class.infolog_bo.inc.php | 8 +- infolog/inc/class.infolog_groupdav.inc.php | 3 +- infolog/inc/class.infolog_ical.inc.php | 70 ++++++- infolog/inc/class.infolog_so.inc.php | 11 +- 6 files changed, 200 insertions(+), 78 deletions(-) diff --git a/etemplate/inc/class.bo_tracking.inc.php b/etemplate/inc/class.bo_tracking.inc.php index f4c2d70255..771551fea7 100644 --- a/etemplate/inc/class.bo_tracking.inc.php +++ b/etemplate/inc/class.bo_tracking.inc.php @@ -355,7 +355,7 @@ abstract class bo_tracking } foreach($changed_fields as $name) { - $status = $this->field2history[$name]; + $status = isset($this->field2history[$name]) ? $this->field2history[$name] : $name; //error_log(__METHOD__.__LINE__." Name $name,".' Status:'.array2string($status)); if (is_array($status)) // 1:N relation --> remove common rows { @@ -423,6 +423,13 @@ abstract class bo_tracking //echo "

$name: ".array2string($data[$name]).' != '.array2string($old[$name])."

\n"; } } + foreach($data as $name => $value) + { + if ($name[0] == '#' && $name[1] == '#' && $value !== $old[$name]) + { + $changed_fields[] = $name; + } + } //error_log(__METHOD__."() changed_fields=".array2string($changed_fields)); return $changed_fields; } diff --git a/etemplate/inc/class.historylog_widget.inc.php b/etemplate/inc/class.historylog_widget.inc.php index 7d457a01ed..bc913b7e24 100644 --- a/etemplate/inc/class.historylog_widget.inc.php +++ b/etemplate/inc/class.historylog_widget.inc.php @@ -46,6 +46,94 @@ class historylog_widget // 'historylog-helper' => '', ); + static $status_widgets; + + /** + * pre-processing of the historylog-helper + * + * @param mixed &$value value / existing content, can be modified + * @param array &$cell array with the widget, can be modified for ui-independent widgets + * @return boolean true if extra label is allowed, false otherwise + */ + private function pre_process_helper(&$value, &$cell, etemplate $tmpl) + { + if (empty($value) && (string)$value !== '0') + { + $cell = etemplate::empty_cell(); + return true; + } + //echo $value.'/'.$cell['size']; _debug_array(self::$status_widgets); + $type = isset(self::$status_widgets[$cell['size']]) ? self::$status_widgets[$cell['size']] : 'label'; + $options = ''; + if (!is_array($type) && strpos($type,':') !== false) + { + list($type,$options) = explode(':',$type,2); + } + // For all select-cats, show missing entries as IDs + if($type == 'select-cat') + { + list($rows,$type1,$type2,$type3,$type4,$type5,$type6) = explode(',',$options); + $type6 = 2; + $options = implode(',',array($rows,$type1,$type2,$type3,$type4,$type5,$type6)); + } + $cell = etemplate::empty_cell($type,$cell['name'],array('readonly' => true,'size' => $options)); + // display unsupported iCal properties, which have multiple values or attributes, or multiple components + if ($type === 'label' && $value[1] === ':' && ($values = unserialize($value))) + { + if (isset($values['values'])) + { + foreach((array)$values['params'] as $name => $val) + { + $values['values'][] = $name.': '.$val; + } + $values = $values['values']; + } + $value = implode("\n", $values); + } + elseif (is_array($type)) + { + list($t) = explode(':',$type[0]); + if (isset($type[0]) && // numeric indexed array --> multiple values of 1:N releation + $tmpl->widgetExists($t)) + { + $cell['type'] = 'vbox'; + $cell['size'] = '0,,0,0'; + $value = explode(bo_tracking::ONE2N_SEPERATOR,$value); + foreach($type as $n => $t) + { + $opt = ''; + if(is_array($t)) + { + $sel_options = $t; + $t = 'select'; + } + else + { + list($t,$opt) = explode(':',$t); + } + $child = etemplate::empty_cell($t,$cell['name']."[$n]",array('readonly' => true,'no_lang' => true,'size' => $opt)); + $child['sel_options'] = $sel_options; + etemplate::add_child($cell,$child); + unset($sel_options); + unset($child); + } + } + else + { + $cell['sel_options'] = $cell['type']; + $cell['type'] = 'select'; + } + } + // For all times, show time in user time + elseif ($type == 'date-time' && $value) + { + $value = egw_time::server2user($value); + } + if ($cell['type'] == 'label') $cell['no_lang'] = 'true'; + + return true; + } + /** * pre-processing of the history logging extension * @@ -59,74 +147,13 @@ class historylog_widget */ function pre_process($name,&$value,&$cell,&$readonlys,&$extension_data,etemplate $tmpl) { - static $status_widgets; - - if ($cell['type'] == 'historylog-helper') + switch ($cell['type']) { - if (empty($value) && (string)$value !== '0') - { - $cell = etemplate::empty_cell(); - return true; - } - //echo $value.'/'.$cell['size']; _debug_array($status_widgets); - $type = isset($status_widgets[$cell['size']]) ? $status_widgets[$cell['size']] : 'label'; - $options = ''; - if (!is_array($type) && strpos($type,':') !== false) - { - list($type,$options) = explode(':',$type,2); - } - // For all select-cats, show missing entries as IDs - if($type == 'select-cat') - { - list($rows,$type1,$type2,$type3,$type4,$type5,$type6) = explode(',',$options); - $type6 = 2; - $options = implode(',',array($rows,$type1,$type2,$type3,$type4,$type5,$type6)); - } - $cell = etemplate::empty_cell($type,$cell['name'],array('readonly' => true,'size' => $options)); - if (is_array($type)) - { - list($t) = explode(':',$type[0]); - if (isset($type[0]) && // numeric indexed array --> multiple values of 1:N releation - $tmpl->widgetExists($t)) - { - $cell['type'] = 'vbox'; - $cell['size'] = '0,,0,0'; - $value = explode(bo_tracking::ONE2N_SEPERATOR,$value); - foreach($type as $n => $t) - { - $opt = ''; - if(is_array($t)) - { - $sel_options = $t; - $t = 'select'; - } - else - { - list($t,$opt) = explode(':',$t); - } - $child = etemplate::empty_cell($t,$cell['name']."[$n]",array('readonly' => true,'no_lang' => true,'size' => $opt)); - $child['sel_options'] = $sel_options; - etemplate::add_child($cell,$child); - unset($sel_options); - unset($child); - } - } - else - { - $cell['sel_options'] = $cell['type']; - $cell['type'] = 'select'; - } - } - // For all times, show time in user time - elseif ($type == 'date-time' && $value) - { - $value = egw_time::server2user($value); - } - if ($cell['type'] == 'label') $cell['no_lang'] = 'true'; - return true; + case 'historylog-helper': + return $this->pre_process_helper($value, $cell, $tmpl); } $app = is_array($value) ? $value['app'] : $GLOBALS['egw_info']['flags']['currentapp']; - $status_widgets = is_array($value) && isset($value['status-widgets']) ? $value['status-widgets'] : null; + self::$status_widgets = is_array($value) && isset($value['status-widgets']) ? $value['status-widgets'] : null; $id = is_array($value) ? $value['id'] : $value; $filter = is_array($value) ? $value['filter'] : array(); @@ -168,24 +195,34 @@ class historylog_widget { $tmpl->sel_options[$status]['#'.$cf_name] = lang($cf_data['label']); } - if (isset($status_widgets['#'.$cf_name])) continue; // app set a status widget --> use that + if (isset(self::$status_widgets['#'.$cf_name])) continue; // app set a status widget --> use that if(!is_array($cf_data['values']) || !$cf_data['values']) { - $status_widgets['#'.$cf_name] = $cf_data['type'] != 'text' ? $cf_data['type'] : 'label'; + self::$status_widgets['#'.$cf_name] = $cf_data['type'] != 'text' ? $cf_data['type'] : 'label'; } elseif($cf_data['values']['@']) { - $status_widgets['#'.$cf_name] = customfields_widget::_get_options_from_file($cf_data['values']['@']); + self::$status_widgets['#'.$cf_name] = customfields_widget::_get_options_from_file($cf_data['values']['@']); } elseif(count($cf_data['values'])) { - $status_widgets['#'.$cf_name] = $cf_data['values']; + self::$status_widgets['#'.$cf_name] = $cf_data['values']; } } if ($value) // autorepeated data-row only if there is data { + // add "labels" for unsupported iCal properties, we just remove the '##' prefix + foreach($value as &$row) + { + if ($row['status'][0] == '#' && $row['status'][1] == '#' && + isset($tmpl->sel_options[$status]) && !isset($tmpl->sel_options[$status][$row['status']])) + { + $tmpl->sel_options[$status][$row['status']] = substr($row['status'], 2); + } + } + $tpl->new_cell(2,'date-time','','${row}[user_ts]',array('readonly' => true)); $tpl->new_cell(2,'select-account','','${row}[owner]',array('readonly' => true)); @@ -201,7 +238,7 @@ class historylog_widget } // if $value[status-widgets] is set, use them together with the historylog-helper // to display new_ & old_value in the specified widget, otherwise use a label - if ($status_widgets) + if (self::$status_widgets) { $tpl->new_cell(2,'historylog-helper','','${row}[new_value]',array('size' => '$row_cont[status]','no_lang' => true,'readonly' => true)); $tpl->new_cell(2,'historylog-helper','','${row}[old_value]',array('size' => '$row_cont[status]','no_lang' => true,'readonly' => true)); diff --git a/infolog/inc/class.infolog_bo.inc.php b/infolog/inc/class.infolog_bo.inc.php index bfc8a59944..e174ebadac 100644 --- a/infolog/inc/class.infolog_bo.inc.php +++ b/infolog/inc/class.infolog_bo.inc.php @@ -694,10 +694,12 @@ class infolog_bo * @param boolean $user2server=true conversion between user- and server-time necessary * @param boolean $skip_notification=false true = do NOT send notification, false (default) = send notifications * @param boolean $throw_exception=false Throw an exception (if required fields are not set) + * @param string $purge_cfs=null null=dont, 'ical'=only iCal X-properties (cfs name starting with "#"), 'all'=all cfs * - * @return int/boolean info_id on a successfull write or false + * @return int|boolean info_id on a successfull write or false */ - function write(&$values_in, $check_defaults=true, $touch_modified=true, $user2server=true, $skip_notification=false, $throw_exception=false) + function write(&$values_in, $check_defaults=true, $touch_modified=true, $user2server=true, + $skip_notification=false, $throw_exception=false, $purge_cfs=null) { $values = $values_in; //echo "boinfolog::write()values="; _debug_array($values); @@ -904,7 +906,7 @@ class infolog_bo //_debug_array($values); // error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n".array2string($values)."\n",3,'/tmp/infolog'); - if (($info_id = $this->so->write($to_write,$check_modified))) + if (($info_id = $this->so->write($to_write, $check_modified, $purge_cfs))) { if (!isset($values['info_type']) || $status_only || empty($values['caldav_url'])) { diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index b3dbab3184..e3c0dc308a 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -126,7 +126,7 @@ class infolog_groupdav extends groupdav_handler 'filter' => $task_filter, 'info_type' => explode(',', $infolog_types), ); - error_log(__METHOD__."('$path', $user) returning ".array2string($ret)); + //error_log(__METHOD__."('$path', $user) returning ".array2string($ret)); return $ret; } @@ -208,6 +208,7 @@ class infolog_groupdav extends groupdav_handler 'filter' => $task_filter, 'date_format' => 'server', 'col_filter' => $filter, + 'custom_fields' => true, // otherwise custom fields get NOT loaded! ); if (!$calendar_data) diff --git a/infolog/inc/class.infolog_ical.inc.php b/infolog/inc/class.infolog_ical.inc.php index e2ed63e7d7..b8ab13c3b0 100644 --- a/infolog/inc/class.infolog_ical.inc.php +++ b/infolog/inc/class.infolog_ical.inc.php @@ -327,6 +327,34 @@ class infolog_ical extends infolog_bo } $vevent->setAttribute('PRIORITY', $priority); + // for CalDAV add all X-Properties previously parsed + if ($this->productManufacturer == 'groupdav') + { + foreach($taskData as $name => $value) + { + if (substr($name, 0, 2) == '##') + { + if ($name[2] == ':') + { + if ($value[1] == ':' && ($v = unserialize($value)) !== false) $value = $v; + foreach((array)$value as $compvData) + { + $comp = Horde_iCalendar::newComponent(substr($name,3), $vevent); + $comp->parsevCalendar($compvData,substr($name,3),'utf-8'); + $vevent->addComponent($comp); + } + } + elseif ($value[1] == ':' && ($attr = unserialize($value)) !== false) + { + $vevent->setAttribute(substr($name, 2), $attr['value'], $attr['params'], true, $attr['values']); + } + else + { + $vevent->setAttribute(substr($name, 2), $value); + } + } + } + } $vcal->addComponent($vevent); $retval = $vcal->exportvCalendar(); @@ -461,7 +489,7 @@ class infolog_ical extends infolog_bo { $taskData['caldav_name'] = $caldav_name; } - return $this->write($taskData, true, true, false); + return $this->write($taskData, true, true, false, false, false, 'ical'); } /** @@ -612,6 +640,7 @@ class infolog_ical extends infolog_bo $taskData['info_startdate'] = $component->getAttribute('DTSTART'); } $attribute['value'] += $taskData['info_startdate']; + $taskData['##DURATION'] = $attribute['value']; // fall throught case 'DUE': // even as EGroupware only displays the date, we can still store the full value @@ -647,6 +676,8 @@ class infolog_ical extends infolog_bo } break; + case 'X-INFOLOG-STATUS': + break; case 'STATUS': // check if we (still) have X-INFOLOG-STATUS set AND it would give an unchanged status (no change by the user) $taskData['info_status'] = $this->vtodo2status($attribute['value'], @@ -679,10 +710,47 @@ class infolog_ical extends infolog_bo case 'PERCENT-COMPLETE': $taskData['info_percent'] = (int) $attribute['value']; break; + + // ignore all PROPS, we dont want to store like X-properties or unsupported props + case 'DTSTAMP': + case 'SEQUENCE': + case 'CREATED': + case 'LAST-MODIFIED': + //case 'ATTENDEE': // todo: add real support for it + break; + + default: // X- attribute or other by EGroupware unsupported property + error_log(__METHOD__."() $attribute[name] = ".array2string($attribute)); + // for attributes with multiple values in multiple lines, merge the values + if (isset($taskData['##'.$attribute['name']])) + { + error_log(__METHOD__."() taskData['##$attribute[name]'] = ".array2string($taskData['##'.$attribute['name']])); + $attribute['values'] = array_merge( + is_array($taskData['##'.$attribute['name']]) ? $taskData['##'.$attribute['name']]['values'] : (array)$taskData['##'.$attribute['name']], + $attribute['values']); + } + $taskData['##'.$attribute['name']] = $attribute['params'] || count($attribute['values']) > 1 ? + serialize($attribute) : $attribute['value']; + break; } } break; } + // store included, but unsupported components like valarm as x-properties + foreach($component->getComponents() as $comp) + { + $name = '##:'.strtoupper($comp->getType()); + $compvData = $comp->exportvCalendar($comp,'utf-8'); + if (isset($taskData[$name])) + { + $taskData[$name] = array($taskData[$name]); + $taskData[$name][] = $compvData; + } + else + { + $taskData[$name] = $compvData; + } + } if ($this->log) { error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($_taskID)\n" . diff --git a/infolog/inc/class.infolog_so.inc.php b/infolog/inc/class.infolog_so.inc.php index 53b48f49fc..2e00cae78d 100644 --- a/infolog/inc/class.infolog_so.inc.php +++ b/infolog/inc/class.infolog_so.inc.php @@ -529,9 +529,10 @@ class infolog_so * * @param array $values with the data of the log-entry * @param int $check_modified=0 old modification date to check before update (include in WHERE) + * @param string $purge_cfs=null null=dont, 'ical'=only iCal X-properties (cfs name starting with "#"), 'all'=all cfs * @return int|boolean info_id, false on error or 0 if the entry has been updated in the meantime */ - function write($values,$check_modified=0) // did _not_ ensure ACL + function write($values, $check_modified=0, $purge_cfs=null) // did _not_ ensure ACL { if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) { @@ -604,6 +605,12 @@ class infolog_so //echo "

soinfolog.write values= "; _debug_array($values); // write customfields now + if ($purge_cfs) + { + $where = array('info_id' => $info_id); + if ($purge_cfs == 'ical') $where[] = "info_extra_name LIKE '#%'"; + $this->db->delete($this->extra_table,$where,__LINE__,__FILE__); + } $to_delete = array(); foreach($values as $key => $val) { @@ -627,7 +634,7 @@ class infolog_so $to_delete[] = substr($key,1); } } - if ($to_delete) + if ($to_delete && !$purge_cfs) { $this->db->delete($this->extra_table,array( 'info_id' => $info_id,