Add flag in calendar import definitions (CSV & iCal) to not import conflicting events

This commit is contained in:
nathangray 2016-07-11 13:08:41 -06:00
parent 980b967071
commit 4bb16b2bec
6 changed files with 225 additions and 12 deletions

View File

@ -168,7 +168,13 @@ class calendar_ical extends calendar_boupdate
var $log = false; var $log = false;
var $logfile="/tmp/log-vcal"; 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 * Constructor
* *
@ -1386,7 +1392,7 @@ class calendar_ical extends calendar_boupdate
// to not loose him, as EGroupware knows events without owner/ORGANIZER as participant // 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']])) 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 else // common adjustments for new events
@ -1765,6 +1771,41 @@ class calendar_ical extends calendar_boupdate
return $updated_id === 0 ? 0 : $return_id; 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 * Sync alarms of current user: add alarms added on client and remove the ones removed
* *

View File

@ -97,7 +97,7 @@ class calendar_import_csv extends importexport_basic_import_csv {
{ {
$record->owner = $options['owner']; $record->owner = $options['owner'];
} }
// Handle errors in length or start/end date // Handle errors in length or start/end date
if($record->start > $record->end) if($record->start > $record->end)
{ {
@ -148,7 +148,8 @@ class calendar_import_csv extends importexport_basic_import_csv {
else else
{ {
// Search app via link query // 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) if($result)
{ {
@ -294,13 +295,39 @@ class calendar_import_csv extends importexport_basic_import_csv {
} }
if ( $this->dry_run ) { if ( $this->dry_run ) {
//print_r($_data); //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]++; $this->results[$_action]++;
return true; return true;
} else { } else {
$result = $this->bo->save( $_data, $this->is_admin); $messages = null;
if(!$result) { $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'); $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->results[$_action]++;
// This does nothing (yet?) but update the identifier // This does nothing (yet?) but update the identifier
$record->save($result); $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] .= "<br />\n" . Api\DateTime::to($conflict['start']) . "\t" . $conflict['title'];
}
}
/** /**
* returns translated name of plugin * returns translated name of plugin

View File

@ -94,9 +94,9 @@ class calendar_import_ical implements importexport_iface_import_plugin {
protected $errors = array(); protected $errors = array();
/** /**
* List of actions, and how many times that action was taken * List of actions, and how many times that action was taken
*/ */
protected $results = array(); protected $results = array();
/** /**
* imports entries according to given definition object. * 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; $_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'])) if (!$calendar_ical->importVCal($_stream, -1,null,false,0,'',null,null,null,$_definition->plugin_options['no_notification']))
{ {
$this->errors[] = lang('Error: importing the iCal'); $this->errors[] = lang('Error: importing the iCal');
} }
else else
{ {
$this->results['imported'] = $calendar_ical->events_imported; $this->results['imported'] += $calendar_ical->events_imported;
} }
return $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 .= "<br />\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 * returns translated name of plugin

View File

@ -15,7 +15,7 @@ use EGroupware\Api;
class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv
{ {
/** /**
* constructor * constructor
*/ */
function __construct() function __construct()
@ -26,6 +26,9 @@ class calendar_wizard_import_csv extends importexport_wizard_basic_import_csv
'wizard_step50' => lang('Manage mapping'), 'wizard_step50' => lang('Manage mapping'),
); );
// Override conditions template to add conflict option
$this->step_templates['wizard_step55'] = 'calendar.import.conditions';
// Field mapping // Field mapping
$tracking = new calendar_tracking(); $tracking = new calendar_tracking();
$this->mapping_fields = array('id' => 'Calendar ID') + $tracking->field2label; $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( $sel_options['string'] = array(
'id' => 'Calendar ID' 'id' => 'Calendar ID'
); );
if(!$content['skip_conflicts'] && $content['plugin_options']['skip_conflicts'])
{
$content['skip_conflicts'] = $content['plugin_options']['skip_conflicts'];
}
return $result; return $result;
} }
} }

View File

@ -0,0 +1,84 @@
<?php
/**
* EGroupware - Wizard for user CSV import
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar
* @subpackage importexport
* @link http://www.egroupware.org
* @author Nathan Gray
* @version $Id$
*/
use EGroupware\Api;
class calendar_wizard_import_ical
{
/**
* List of steps. Key is the function, value is the translated title.
*/
public $steps;
/**
* List of eTemplates to use for each step. You can override this with your own etemplates steps.
*/
protected $step_templates = array(
'wizard_step55' => '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;
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE overlay PUBLIC "-//Stylite AG//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
<!-- $Id$ -->
<overlay>
<template id="calendar.import.conditions" template="" lang="" group="0" version="16.1">
<template id="importexport.wizard_basic_import_csv.conditions" disabled="@no_conditions"/>
<hbox>
<description value="Do not import conflicting events"/>
<checkbox id="skip_conflicts" />
</hbox>
</template>
</overlay>