forked from extern/egroupware
Changes to how recurring events are split when you change them
- Events before today can (still) not be changed - Events after today can (still) be changed - If you change a series by editing (or dnd) an event after today, the change is effective as of that day
This commit is contained in:
parent
fbef1f10a6
commit
ede4956797
@ -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 "<p>".$time." --> ignored as $ts < $start-$event_length</p>\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));
|
||||
}
|
||||
|
||||
|
@ -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!').'<br />'.
|
||||
lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
|
||||
htmlspecialchars(egw::link('/index.php',array(
|
||||
'menuaction' => 'calendar.calendar_uiforms.edit',
|
||||
'cal_id' => $content['id'],
|
||||
))).'">','</a>');
|
||||
$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!').'<br />'.
|
||||
lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
|
||||
htmlspecialchars(egw::link('/index.php',array(
|
||||
'menuaction' => 'calendar.calendar_uiforms.edit',
|
||||
'cal_id' => $content['id'],
|
||||
))).'">','</a>');
|
||||
$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)
|
||||
@ -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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
@ -440,14 +439,46 @@ var et2_calendar_timegrid = et2_valueWidget.extend([et2_IDetachedDOM, et2_IResiz
|
||||
// Event (whole day or not) dropped on whole day section, change to whole day non blocking
|
||||
if(dropEnd.whole_day) duration = 'whole_day';
|
||||
|
||||
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);
|
||||
// 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,
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user