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); + } } }); }