From 4bb16b2bec49f4d62fc744e453bcc17d5f8cf554 Mon Sep 17 00:00:00 2001 From: nathangray Date: Mon, 11 Jul 2016 13:08:41 -0600 Subject: [PATCH] Add flag in calendar import definitions (CSV & iCal) to not import conflicting events --- calendar/inc/class.calendar_ical.inc.php | 45 +++++++++- .../inc/class.calendar_import_csv.inc.php | 52 ++++++++++-- .../inc/class.calendar_import_ical.inc.php | 34 +++++++- .../class.calendar_wizard_import_csv.inc.php | 10 ++- .../class.calendar_wizard_import_ical.inc.php | 84 +++++++++++++++++++ .../templates/default/import.conditions.xet | 12 +++ 6 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 calendar/inc/class.calendar_wizard_import_ical.inc.php create mode 100644 calendar/templates/default/import.conditions.xet diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index 72cd3c7a59..cb93e3a969 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -168,7 +168,13 @@ class calendar_ical extends calendar_boupdate var $log = false; var $logfile="/tmp/log-vcal"; - + /** + * Conflict callback + * If set, conflict checking will be enabled, and the event as well as + * conflicts are passed as parameters to this callback + */ + var $conflict_callback = null; + /** * Constructor * @@ -1386,7 +1392,7 @@ class calendar_ical extends calendar_boupdate // to not loose him, as EGroupware knows events without owner/ORGANIZER as participant if (isset($event_info['stored_event']['participants'][$event['owner']]) && !isset($event['participants'][$event['owner']])) { - $event['participant'][$event['owner']] = $event_info['stored_event']['participants'][$event['owner']]; + $event['participants'][$event['owner']] = $event_info['stored_event']['participants'][$event['owner']]; } } else // common adjustments for new events @@ -1765,6 +1771,41 @@ class calendar_ical extends calendar_boupdate return $updated_id === 0 ? 0 : $return_id; } + /** + * Override parent update function to handle conflict checking callback, if set + * + * @param array &$event event-array, on return some values might be changed due to set defaults + * @param boolean $ignore_conflicts =false just ignore conflicts or do a conflict check and return the conflicting events. + * Set to false if $this->conflict_callback is set + * @param boolean $touch_modified =true NOT USED ANYMORE (was only used in old csv-import), modified&modifier is always updated! + * @param boolean $ignore_acl =false should we ignore the acl + * @param boolean $updateTS =true update the content history of the event + * @param array &$messages=null messages about because of missing ACL removed participants or categories + * @param boolean $skip_notification =false true: send NO notifications, default false = send them + * @return mixed on success: int $cal_id > 0, on error or conflicts false. + * Conflicts are passed to $this->conflict_callback + */ + public function update(&$event,$ignore_conflicts=false,$touch_modified=true,$ignore_acl=false,$updateTS=true,&$messages=null, $skip_notification=false) + { + if($this->conflict_callback !== null) + { + // calendar_ical overrides search(), which breaks conflict checking + // so we make sure to use the original from parent + static $bo; + if(!$bo) + { + $bo = new calendar_boupdate(); + } + $conflicts = $bo->conflicts($event); + if(is_array($conflicts) && count($conflicts) > 0) + { + call_user_func_array($this->conflict_callback, array(&$event, &$conflicts)); + return false; + } + } + return parent::update($event, $ignore_conflicts, $touch_modified, $ignore_acl, $updateTS, $messages, $skip_notification); + } + /** * Sync alarms of current user: add alarms added on client and remove the ones removed * diff --git a/calendar/inc/class.calendar_import_csv.inc.php b/calendar/inc/class.calendar_import_csv.inc.php index 8dbed1d120..85063052ae 100644 --- a/calendar/inc/class.calendar_import_csv.inc.php +++ b/calendar/inc/class.calendar_import_csv.inc.php @@ -97,7 +97,7 @@ class calendar_import_csv extends importexport_basic_import_csv { { $record->owner = $options['owner']; } - + // Handle errors in length or start/end date if($record->start > $record->end) { @@ -148,7 +148,8 @@ class calendar_import_csv extends importexport_basic_import_csv { else { // Search app via link query - $result = Link::query($resource['app'], $search, $options); + $link_options = array(); + $result = Link::query($resource['app'], $search, $link_options); if($result) { @@ -294,13 +295,39 @@ class calendar_import_csv extends importexport_basic_import_csv { } if ( $this->dry_run ) { //print_r($_data); + // User is interested in conflict checks, do so for dry run + // Otherwise, conflicts are just ignored and imported anyway + if($this->definition->plugin_options['skip_conflicts']) + { + $conflicts = $this->bo->conflicts($_data); + if($conflicts) + { + $this->conflict_warning($record_num, $conflicts); + $this->results['skipped']++; + return false; + } + } $this->results[$_action]++; return true; } else { - $result = $this->bo->save( $_data, $this->is_admin); - if(!$result) { + $messages = null; + $result = $this->bo->update( $_data, + !$this->definition->plugin_options['skip_conflicts'], + true, $this->is_admin, true, $messages, + $this->definition->plugin_options['no_notification'] + ); + if(!$result) + { $this->errors[$record_num] = lang('Unable to save'); - } else { + } + else if (is_array($result)) + { + $this->conflict_warning($record_num, $result); + $this->results['skipped']++; + return false; + } + else + { $this->results[$_action]++; // This does nothing (yet?) but update the identifier $record->save($result); @@ -312,6 +339,21 @@ class calendar_import_csv extends importexport_basic_import_csv { } } + + /** + * Add a warning message about conflicting events + * + * @param int $record_num Current record index + * @param Array $conflicts List of found conflicting events + */ + protected function conflict_warning($record_num, &$conflicts) + { + $this->warnings[$record_num] = lang('Conflicts') . ':'; + foreach($conflicts as $conflict) + { + $this->warnings[$record_num] .= "
\n" . Api\DateTime::to($conflict['start']) . "\t" . $conflict['title']; + } + } /** * returns translated name of plugin diff --git a/calendar/inc/class.calendar_import_ical.inc.php b/calendar/inc/class.calendar_import_ical.inc.php index 1d78a1b16f..dc20624726 100644 --- a/calendar/inc/class.calendar_import_ical.inc.php +++ b/calendar/inc/class.calendar_import_ical.inc.php @@ -94,9 +94,9 @@ class calendar_import_ical implements importexport_iface_import_plugin { protected $errors = array(); /** - * List of actions, and how many times that action was taken - */ - protected $results = array(); + * List of actions, and how many times that action was taken + */ + protected $results = array(); /** * imports entries according to given definition object. @@ -136,18 +136,44 @@ class calendar_import_ical implements importexport_iface_import_plugin { { $_definition->plugin_options['no_notification'] = true; } + // User wants conflicting events to not be imported + if($_definition->plugin_options['skip_conflicts']) + { + $calendar_ical->conflict_callback = array($this, 'conflict_warning'); + } if (!$calendar_ical->importVCal($_stream, -1,null,false,0,'',null,null,null,$_definition->plugin_options['no_notification'])) { $this->errors[] = lang('Error: importing the iCal'); } else { - $this->results['imported'] = $calendar_ical->events_imported; + $this->results['imported'] += $calendar_ical->events_imported; } return $calendar_ical->events_imported; } + + /** + * Add a warning message about conflicting events + * + * @param int $record_num Current record index + * @param Array $conflicts List of found conflicting events + */ + public function conflict_warning(&$event, &$conflicts) + { + $warning = EGroupware\Api\DateTime::to($event['start']) . ' ' . $event['title'] . ' ' . lang('Conflicts') . ':'; + foreach($conflicts as $conflict) + { + $warning .= "
\n" . EGroupware\Api\DateTime::to($conflict['start']) . "\t" . $conflict['title']; + } + $this->warnings[] = $warning; + + // iCal will always count as imported, even if it wasn't + $this->results['imported'] -= 1; + + $this->results['skipped']++; + } /** * returns translated name of plugin diff --git a/calendar/inc/class.calendar_wizard_import_csv.inc.php b/calendar/inc/class.calendar_wizard_import_csv.inc.php index 18718d5983..ecac6c4f92 100644 --- a/calendar/inc/class.calendar_wizard_import_csv.inc.php +++ b/calendar/inc/class.calendar_wizard_import_csv.inc.php @@ -15,7 +15,7 @@ use EGroupware\Api; class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv { - /** + /** * constructor */ function __construct() @@ -26,6 +26,9 @@ class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv 'wizard_step50' => lang('Manage mapping'), ); + // Override conditions template to add conflict option + $this->step_templates['wizard_step55'] = 'calendar.import.conditions'; + // Field mapping $tracking = new calendar_tracking(); $this->mapping_fields = array('id' => 'Calendar ID') + $tracking->field2label; @@ -67,6 +70,11 @@ class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv $sel_options['string'] = array( 'id' => 'Calendar ID' ); + + if(!$content['skip_conflicts'] && $content['plugin_options']['skip_conflicts']) + { + $content['skip_conflicts'] = $content['plugin_options']['skip_conflicts']; + } return $result; } } diff --git a/calendar/inc/class.calendar_wizard_import_ical.inc.php b/calendar/inc/class.calendar_wizard_import_ical.inc.php new file mode 100644 index 0000000000..9d71d95838 --- /dev/null +++ b/calendar/inc/class.calendar_wizard_import_ical.inc.php @@ -0,0 +1,84 @@ + 'calendar.import.conditions' + ); + /** + * constructor + */ + function __construct() + { + $this->steps = array( + 'wizard_step55' => lang('Edit conditions'), + ); + } + + function wizard_step50(&$content, &$sel_options, &$readonlys, &$preserv) + { + $result = parent::wizard_step50($content, $sel_options, $readonlys, $preserv); + $content['msg'] .= "\n*" ; + + return $result; + } + + // Conditions + function wizard_step55(&$content, &$sel_options, &$readonlys, &$preserv) + { + // return from step55 + if ($content['step'] == 'wizard_step55') + { + switch (array_search('pressed', $content['button'])) + { + case 'next': + return $GLOBALS['egw']->importexport_definitions_ui->get_step($content['step'],1); + case 'previous' : + return $GLOBALS['egw']->importexport_definitions_ui->get_step($content['step'],-1); + case 'finish': + return 'wizard_finish'; + default : + return $this->wizard_step55($content,$sel_options,$readonlys,$preserv); + } + } + // init step30 + else + { + $content['text'] = $this->steps['wizard_step55']; + $content['step'] = 'wizard_step55'; + if(!$content['skip_conflicts'] && array_key_exists('skip_conflicts', $content['plugin_options'])) + { + $content['skip_conflicts'] = $content['plugin_options']['skip_conflicts']; + } + $preserv = $content; + unset ($preserv['button']); + + // No real conditions, but we share a template + $content['no_conditions'] = true; + + return $this->step_templates[$content['step']]; + } + + return $result; + } +} diff --git a/calendar/templates/default/import.conditions.xet b/calendar/templates/default/import.conditions.xet new file mode 100644 index 0000000000..e8b62b8496 --- /dev/null +++ b/calendar/templates/default/import.conditions.xet @@ -0,0 +1,12 @@ + + + + +