diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php
index e8dd1b9cd3..c604fc07d2 100644
--- a/calendar/inc/class.calendar_bo.inc.php
+++ b/calendar/inc/class.calendar_bo.inc.php
@@ -800,7 +800,6 @@ class calendar_bo
{
$exceptions[] = egw_time::to($exception, true); // true = date
}
- error_log(__METHOD__."(".array2string($event).", $start) exceptions=".array2string($exceptions));
foreach($events as $event)
{
$is_exception = in_array(egw_time::to($event['start'], true), $exceptions);
@@ -1020,7 +1019,6 @@ class calendar_bo
foreach($rrule as $time)
{
$time->setUser(); // $time is in timezone of event, convert it to usertime used here
- error_log($time);
if (($ts = $this->date2ts($time)) < $start-$event_length)
{
//echo "
".$time." --> ignored as $ts < $start-$event_length
\n";
@@ -1377,7 +1375,6 @@ class calendar_bo
}
$msg = str_replace('%'.($i-1),$param,$msg);
}
- error_log($msg);
if ($backtrace) error_log(function_backtrace(1));
}
diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php
index 7c5af1e4a4..423ea6c824 100644
--- a/calendar/inc/class.calendar_uiforms.inc.php
+++ b/calendar/inc/class.calendar_uiforms.inc.php
@@ -740,15 +740,6 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
if ($event['start'] != $old_event['start'] ||
$event['whole_day'] != $old_event['whole_day'])
{
- if(!($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + 1, true)))
- {
- $msg = lang("Error: You can't shift a series from the past!");
- $noerror = false;
- break;
- }
- // splitting of series confirmed or first event clicked (no confirmation necessary)
- $orig_event = $event;
-
// calculate offset against old series start or clicked recurrance,
// depending on which is smaller
$offset = $event['start'] - $old_event['start'];
@@ -756,67 +747,10 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
{
$offset = $off2;
}
- // base start-date of new series on actual / clicked date
- $actual_date = $event['actual_date'];
- $event['start'] = $actual_date + $offset;
- if ($content['duration'])
+ $msg = $this->_break_recurring($event, $old_event, $event['actual_date'] + $offset,$content['no_notifications']);
+ if($msg)
{
- $event['end'] = $event['start'] + $content['duration'];
- }
- elseif($event['end'] < $event['start'])
- {
- $event['end'] = $event['start'] + $event['end'] - $actual_date;
- }
- //error_log(__LINE__.": event[start]=$event[start]=".egw_time::to($event['start']).", duration=$content[duration], event[end]=$event[end]=".egw_time::to($event['end']).", offset=$offset\n");
- $event['participants'] = $old_event['participants'];
- foreach ($old_event['recur_exception'] as $key => $exdate)
- {
- if ($exdate > $actual_date)
- {
- unset($old_event['recur_exception'][$key]);
- $event['recur_exception'][$key] += $offset;
- }
- else
- {
- unset($event['recur_exception'][$key]);
- }
- }
- $old_alarms = $old_event['alarm'];
- if ($old_event['start'] < $actual_date)
- {
- unset($orig_event);
- // copy event by unsetting the id(s)
- unset($event['id']);
- unset($event['uid']);
- unset($event['caldav_name']);
-
- // set enddate of existing event
- $rriter = calendar_rrule::event2rrule($old_event, true);
- $rriter->rewind();
- $last = $rriter->current();
- do
- {
- $rriter->next_no_exception();
- $occurrence = $rriter->current();
- }
- while ($rriter->valid() &&
- egw_time::to($occurrence, 'ts') < $actual_date &&
- ($last = $occurrence));
- $last->setTime(0, 0, 0);
- $old_event['recur_enddate'] = egw_time::to($last, 'ts');
- if (!$this->bo->update($old_event,true,true,false,true,$dummy=null,$content['no_notifications']))
- {
- $msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'
'.
- lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','','');
- $noerror = false;
- $event = $orig_event;
- break;
- }
- $event['alarm'] = array();
+ $noerror = false;
}
}
}
@@ -1131,6 +1065,124 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
return lang('Edit status or alarms for this particular day');
}
+ /**
+ * Since we cannot change recurrences in the past, break a recurring
+ * event (that starts in the past), and create a new event.
+ *
+ * $old_event will be ended (if needed) and $event will be modified with the
+ * new start date and time. It is not allowed to edit events in the past,
+ * so if $as_of_date is in the past, it will be adjusted to today.
+ *
+ * @param array &$event Event to be modified
+ * @param array $old_event Unmodified (original) event, as read from the database
+ * @param date $as_of_date If provided, the break will be done as of this
+ * date instead of today
+ * @param boolean $no_notifications Toggle notifications to participants
+ *
+ * @return false or error message
+ */
+ function _break_recurring(&$event, $old_event, $as_of_date = null, $no_notifications = true)
+ {
+ $msg = false;
+
+ if(!$as_of_date )
+ {
+ $as_of_date = time();
+ }
+
+ //error_log(__METHOD__ . egw_time::to($old_event['start']) . ' -> '. egw_time::to($event['start']) . ' as of ' . egw_time::to($as_of_date));
+
+ if(!($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + 1, true)))
+ {
+ $msg = lang("Error: You can't shift a series from the past!");
+ $noerror = false;
+ return $msg;
+ }
+
+ // Hold on to this in case something goes wrong
+ $orig_event = $event;
+
+ $offset = $event['start'] - $old_event['start'];
+
+ // base start-date of new series on actual / clicked date
+ $event['start'] = $as_of_date ;
+
+ if (egw_time::to($old_event['start'],'Ymd') < egw_time::to($as_of_date,'Ymd') ||
+ // Adjust for requested date in the past
+ egw_time::to($as_of_date,'ts') < time()
+ )
+ {
+
+ unset($orig_event);
+ // copy event by unsetting the id(s)
+ unset($event['id']);
+ unset($event['uid']);
+ unset($event['caldav_name']);
+ $event['alarm'] = array();
+
+ // set enddate of existing event
+ $rriter = calendar_rrule::event2rrule($old_event, true);
+ $rriter->rewind();
+ $last = $rriter->current();
+ do
+ {
+ $rriter->next_no_exception();
+ $occurrence = $rriter->current();
+ }
+ while ($rriter->valid() && (
+ egw_time::to($occurrence, 'ts') <= time() ||
+ egw_time::to($occurrence, 'Ymd') < egw_time::to($as_of_date,'Ymd')
+ ) && ($last = $occurrence));
+
+
+ // Make sure as_of_date is still valid, may have to move forward
+ if(egw_time::to($as_of_date,'ts') < egw_time::to($last,'ts') ||
+ egw_time::to($as_of_date, 'Ymd') == egw_time::to($last, 'Ymd')) {
+ //$rriter->next_no_exception();
+ $event['start'] = egw_time::to($rriter->current(),'ts') + $offset;
+ }
+
+ //error_log(__METHOD__ ." Series should end at " . egw_time::to($last));
+ //error_log(__METHOD__ ." New series starts at " . egw_time::to($event['start']));
+ if ($content['duration'])
+ {
+ $event['end'] = $event['start'] + $content['duration'];
+ }
+ elseif($event['end'] < $event['start'])
+ {
+ $event['end'] = $old_event['end'] - $old_event['start'] + $event['start'];
+ }
+ //error_log(__LINE__.": event[start]=$event[start]=".egw_time::to($event['start']).", duration={$content['duration']}, event[end]=$event[end]=".egw_time::to($event['end']).", offset=$offset\n");
+
+ $last->setTime(0, 0, 0);
+ $event['participants'] = $old_event['participants'];
+ foreach ($old_event['recur_exception'] as $key => $exdate)
+ {
+ if ($exdate > $last)
+ {
+ unset($old_event['recur_exception'][$key]);
+ $event['recur_exception'][$key] += $offset;
+ }
+ else
+ {
+ unset($event['recur_exception'][$key]);
+ }
+ }
+ $old_event['recur_enddate'] = egw_time::to($last, 'ts');
+ if (!$this->bo->update($old_event,true,true,false,true,$dummy=null,$no_notifications))
+ {
+ $msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'
'.
+ lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','','');
+ $event = $orig_event;
+ }
+ }
+ return $msg;
+ }
+
/**
* return javascript to open mail compose window with preset content to mail all participants
*
@@ -2621,9 +2673,11 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
* @param string $targetDateTime the datetime where the event should be moved to, format: YYYYMMDD
* @param string|string[] $targetOwner the owner of the target calendar
* @param string $durationT the duration to support resizable calendar event
+ * @param string $seriesInstance If moving a whole series, not an exception, this is
+ * which particular instance was dragged
* @return string XML response if no error occurs
*/
- function ajax_moveEvent($_eventId,$calendarOwner,$targetDateTime,$targetOwner,$durationT=null)
+ function ajax_moveEvent($_eventId,$calendarOwner,$targetDateTime,$targetOwner,$durationT=null,$seriesInstance=null)
{
// we do not allow dragging into another users calendar ATM
if($targetOwner < 0)
@@ -2710,7 +2764,7 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
unset($preserv['edit_single']);
}
}
-
+
// Drag a whole day to a time
if($durationT && $durationT != 'whole_day')
{
@@ -2731,6 +2785,19 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
}
$event['start'] = $this->bo->date2ts($targetDateTime);
+
+ if ($event['recur_type'] != MCAL_RECUR_NONE && !$date && $seriesInstance)
+ {
+ // calculate offset against clicked recurrance,
+ // depending on which is smaller
+ $offset = egw_time::to($targetDateTime,'ts') - egw_time::to($seriesInstance,'ts');
+ $event['start'] = $old_event['start'] + $offset;
+
+ // We have a recurring event starting in the past -
+ // stop it & create a new one.
+ $this->_break_recurring($event, $old_event, $this->bo->date2ts($targetDateTime));
+ }
+
$event['end'] = $event['start']+$duration;
$status_reset_to_unknown = false;
$sameday = (date('Ymd', $old_event['start']) == date('Ymd', $event['start']));
@@ -2762,6 +2829,10 @@ foreach($recur_event as $_k => $_v) error_log($_k . ': ' . array2string($_v));
$response = egw_json_response::get();
if(!is_array($conflicts) && $conflicts)
{
+ if(is_int($conflicts))
+ {
+ $event['id'] = $conflicts;
+ }
if(!$event['recur_type'] && !$old_event['recur_type'])
{
// Directly update stored data. If event is still visible, it will
diff --git a/calendar/js/app.js b/calendar/js/app.js
index 20edfecc6c..3a2e0e0c1a 100644
--- a/calendar/js/app.js
+++ b/calendar/js/app.js
@@ -1419,17 +1419,20 @@ app.classes.calendar = AppJS.extend(
*/
move_edit_series: function(_DOM,_button)
{
- var content = this.et2.getArrayMgr('content').data;
- var start_date = this.et2.getWidgetById('start').get_value();
- var whole_day = this.et2.getWidgetById('whole_day');
+ var content = _button.getRoot().getArrayMgr('content').data;
+ var start_date = _button.getRoot().getWidgetById('start').get_value();
+ var whole_day = _button.getRoot().getWidgetById('whole_day');
var is_whole_day = whole_day && whole_day.get_value() == whole_day.options.selected_value;
var button = _button;
var that = this;
+
+ var tempDate = new Date();
+ var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
if (typeof content != 'undefined' && content.id != null &&
typeof content.recur_type != 'undefined' && content.recur_type != null && content.recur_type != 0
)
{
- if (content.start != start_date || content.whole_day != is_whole_day)
+ if (content.start != start_date && today >= new Date(content.start) || content.whole_day != is_whole_day)
{
et2_dialog.show_dialog(function(_button_id)
{
diff --git a/calendar/js/et2_widget_event.js b/calendar/js/et2_widget_event.js
index 927c2f9262..4ac6836734 100644
--- a/calendar/js/et2_widget_event.js
+++ b/calendar/js/et2_widget_event.js
@@ -180,7 +180,7 @@ var et2_calendar_event = et2_valueWidget.extend([et2_IDetachedDOM],
this._parent.date_helper.set_value(event.start.valueOf ? new Date(event.start) : event.start);
var formatted_start = this._parent.date_helper.getValue();
- this.set_id('event_' + (eventId || event.id));
+ this.set_id('event_' + event.app_id);
// Make sure category stuff is there
// Fake it to use the cache / call - if already there, these will return
diff --git a/calendar/js/et2_widget_timegrid.js b/calendar/js/et2_widget_timegrid.js
index 1ae7e20d36..e64a4154a4 100644
--- a/calendar/js/et2_widget_timegrid.js
+++ b/calendar/js/et2_widget_timegrid.js
@@ -372,7 +372,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
var drop_date = dropEnd.date||false;
var event_data = timegrid._get_event_info(ui.draggable);
- var event_widget = timegrid.getWidgetById('event_'+event_data.id);
+ var event_widget = timegrid.getWidgetById('event_'+event_data.app_id);
if(!event_widget)
{
// Widget was moved across weeks / owners
@@ -395,7 +395,6 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
event_widget._parent.date_helper.set_hours(dropEnd.whole_day ? 0 : dropEnd.hour||0);
event_widget._parent.date_helper.set_minutes(dropEnd.whole_day ? 0 : dropEnd.minute||0);
}
- event_widget.options.value.start = new Date(event_widget._parent.date_helper.getValue());
// Leave the helper there until the update is done
var loading = ui.helper.clone(true).appendTo($j('body'));
@@ -427,7 +426,7 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
// If it is an integrated infolog event we need to edit infolog entry
egw().json('stylite_infolog_calendar_integration::ajax_moveInfologEvent',
- [event_data.id, event_widget.options.value.start||false,duration],
+ [event_data.id, event_widget._parent.date_helper.getValue()||false,duration],
function() {loading.remove();}
).sendRequest(true);
}
@@ -439,15 +438,47 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
var duration = event_widget.options.value.whole_day && dropEnd.hour ? 86400-1 : false;
// Event (whole day or not) dropped on whole day section, change to whole day non blocking
if(dropEnd.whole_day) duration = 'whole_day';
+
+ // Send the update
+ var _send = function(series_instance)
+ {
+ var start = new Date(event_widget._parent.date_helper.getValue());
- egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
- button_id==='series' ? event_data.id : event_data.app_id,event_data.owner,
- event_widget.options.value.start,
- timegrid.options.owner||egw.user('account_id'),
- duration
- ],
- function() { loading.remove();}
- ).sendRequest(true);
+ egw().json('calendar.calendar_uiforms.ajax_moveEvent', [
+ button_id==='series' ? event_data.id : event_data.app_id,event_data.owner,
+ start,
+ timegrid.options.owner||egw.user('account_id'),
+ duration,
+ series_instance
+ ],
+ function() { loading.remove();}
+ ).sendRequest(true);
+ };
+
+ // Check for modifying a series that started before today
+ var tempDate = new Date();
+ var today = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(),0,-tempDate.getTimezoneOffset(),0);
+ if (today >= new Date(event_widget.options.value.start))
+ {
+ et2_dialog.show_dialog(function(_button_id)
+ {
+ if (_button_id == et2_dialog.OK_BUTTON)
+ {
+ _send(event_widget.options.value.recur_date);
+
+ }
+ else
+ {
+ return false;
+ }
+ },
+ egw.lang("Do you really want to change the start of this series? If you do, the original series will be terminated as of today and a new series for the future reflecting your changes will be created."),
+ egw.lang("This event is part of a series"), {}, et2_dialog.BUTTONS_OK_CANCEL , et2_dialog.WARNING_MESSAGE);
+ }
+ else
+ {
+ _send(event_widget.options.value.recur_date);
+ }
}
});
}