forked from extern/egroupware
* Calendar: check recurrences for conflicts too (until configured search-time is exceeded, default 3s)
This commit is contained in:
parent
8a3dd67902
commit
b4017cb8c4
@ -196,6 +196,12 @@ class calendar_bo
|
||||
* @var Api\Categories
|
||||
*/
|
||||
var $categories;
|
||||
/**
|
||||
* Config values for "calendar", only used for horizont, regular calendar config is under phpgwapi
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
var $config;
|
||||
|
||||
/**
|
||||
* Does a user require an extra invite grant, to be able to invite an other user, default no
|
||||
@ -253,7 +259,7 @@ class calendar_bo
|
||||
}
|
||||
//error_log(__METHOD__ . " registered resources=". array2string($this->resources));
|
||||
|
||||
$this->config = Api\Config::read('calendar'); // only used for horizont, regular calendar Api\Config is under phpgwapi
|
||||
$this->config = Api\Config::read('calendar'); // only used for horizont, regular calendar config is under phpgwapi
|
||||
$this->require_acl_invite = $GLOBALS['egw_info']['server']['require_acl_invite'];
|
||||
|
||||
$this->categories = new Api\Categories($this->user,'calendar');
|
||||
@ -1343,6 +1349,7 @@ class calendar_bo
|
||||
$param = "'$param'";
|
||||
}
|
||||
break;
|
||||
case 'EGroupware\\Api\\DateTime':
|
||||
case 'egw_time':
|
||||
case 'datetime':
|
||||
$p = $param;
|
||||
@ -1363,6 +1370,7 @@ class calendar_bo
|
||||
}
|
||||
$msg = str_replace('%'.($i-1),$param,$msg);
|
||||
}
|
||||
error_log($msg);
|
||||
if ($backtrace) error_log(function_backtrace(1));
|
||||
}
|
||||
|
||||
|
@ -230,135 +230,18 @@ class calendar_boupdate extends calendar_bo
|
||||
}
|
||||
}
|
||||
// check for conflicts only happens !$ignore_conflicts AND if start + end date are given
|
||||
if (!$ignore_conflicts && !$event['non_blocking'] && isset($event['start']) && isset($event['end']))
|
||||
$checked_excluding = null;
|
||||
if (!$ignore_conflicts && !$event['non_blocking'] && isset($event['start']) && isset($event['end']) &&
|
||||
(($conflicts = $this->conflicts($event, $checked_excluding)) || $checked_excluding))
|
||||
{
|
||||
$types_with_quantity = array();
|
||||
foreach($this->resources as $type => $data)
|
||||
if ($checked_excluding) // warn user if not all recurrences have been checked
|
||||
{
|
||||
if ($data['max_quantity']) $types_with_quantity[] = $type;
|
||||
}
|
||||
// get all NOT rejected participants and evtl. their quantity
|
||||
$quantity = $users = array();
|
||||
foreach($event['participants'] as $uid => $status)
|
||||
{
|
||||
calendar_so::split_status($status,$q,$r);
|
||||
if ($status[0] == 'R') continue; // ignore rejected participants
|
||||
|
||||
if ($uid < 0) // group, check it's members too
|
||||
{
|
||||
$users += (array)$GLOBALS['egw']->accounts->members($uid,true);
|
||||
$users = array_unique($users);
|
||||
}
|
||||
$users[] = $uid;
|
||||
if (in_array($uid[0],$types_with_quantity))
|
||||
{
|
||||
$quantity[$uid] = $q;
|
||||
}
|
||||
}
|
||||
//$start = microtime(true);
|
||||
$overlapping_events =& $this->search(array(
|
||||
'start' => $event['start'],
|
||||
'end' => $event['end'],
|
||||
'users' => $users,
|
||||
'ignore_acl' => true, // otherwise we get only events readable by the user
|
||||
'enum_groups' => true, // otherwise group-events would not block time
|
||||
'query' => array(
|
||||
'cal_non_blocking' => 0,
|
||||
),
|
||||
'no_integration' => true, // do NOT use integration of other apps
|
||||
));
|
||||
//error_log(__METHOD__."() conflict check took ".number_format(microtime(true)-$start, 3).'s');
|
||||
if ($this->debug > 2 || $this->debug == 'update')
|
||||
{
|
||||
$this->debug_message('calendar_boupdate::update() checking for potential overlapping events for users %1 from %2 to %3',false,$users,$event['start'],$event['end']);
|
||||
}
|
||||
$max_quantity = $possible_quantity_conflicts = $conflicts = array();
|
||||
foreach((array) $overlapping_events as $k => $overlap)
|
||||
{
|
||||
if ($overlap['id'] == $event['id'] || // that's the event itself
|
||||
$overlap['id'] == $event['reference'] || // event is an exception of overlap
|
||||
$overlap['non_blocking']) // that's a non_blocking event
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($this->debug > 3 || $this->debug == 'update')
|
||||
{
|
||||
$this->debug_message('calendar_boupdate::update() checking overlapping event %1',false,$overlap);
|
||||
}
|
||||
// check if the overlap is with a rejected participant or within the allowed quantity
|
||||
$common_parts = array_intersect($users,array_keys($overlap['participants']));
|
||||
foreach($common_parts as $n => $uid)
|
||||
{
|
||||
$status = $overlap['participants'][$uid];
|
||||
calendar_so::split_status($status, $q, $r);
|
||||
if ($status == 'R')
|
||||
{
|
||||
unset($common_parts[$n]);
|
||||
continue;
|
||||
}
|
||||
if (is_numeric($uid) || !in_array($uid[0],$types_with_quantity))
|
||||
{
|
||||
continue; // no quantity check: quantity allways 1 ==> conflict
|
||||
}
|
||||
if (!isset($max_quantity[$uid]))
|
||||
{
|
||||
$res_info = $this->resource_info($uid);
|
||||
$max_quantity[$uid] = $res_info[$this->resources[$uid[0]]['max_quantity']];
|
||||
}
|
||||
$quantity[$uid] += $q;
|
||||
if ($quantity[$uid] <= $max_quantity[$uid])
|
||||
{
|
||||
$possible_quantity_conflicts[$uid][] =& $overlapping_events[$k]; // an other event can give the conflict
|
||||
unset($common_parts[$n]);
|
||||
continue;
|
||||
}
|
||||
// now we have a quantity conflict for $uid
|
||||
}
|
||||
if (count($common_parts))
|
||||
{
|
||||
if ($this->debug > 3 || $this->debug == 'update')
|
||||
{
|
||||
$this->debug_message('calendar_boupdate::update() conflicts with the following participants found %1',false,$common_parts);
|
||||
}
|
||||
$conflicts[$overlap['id'].'-'.$this->date2ts($overlap['start'])] =& $overlapping_events[$k];
|
||||
}
|
||||
}
|
||||
// check if we are withing the allowed quantity and if not add all events using that resource
|
||||
// seems this function is doing very strange things, it gives empty conflicts
|
||||
foreach($max_quantity as $uid => $max)
|
||||
{
|
||||
if ($quantity[$uid] > $max)
|
||||
{
|
||||
foreach((array)$possible_quantity_conflicts[$uid] as $conflict)
|
||||
{
|
||||
$conflicts[$conflict['id'].'-'.$this->date2ts($conflict['start'])] =& $possible_quantity_conflicts[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($possible_quantity_conflicts);
|
||||
|
||||
if (count($conflicts))
|
||||
{
|
||||
foreach($conflicts as $key => $conflict)
|
||||
{
|
||||
$conflict['participants'] = array_intersect_key((array)$conflict['participants'],$event['participants']);
|
||||
if (!$this->check_perms(Acl::READ,$conflict))
|
||||
{
|
||||
$conflicts[$key] = array(
|
||||
'id' => $conflict['id'],
|
||||
'title' => lang('busy'),
|
||||
'participants' => $conflict['participants'],
|
||||
'start' => $conflict['start'],
|
||||
'end' => $conflict['end'],
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($this->debug > 2 || $this->debug == 'update')
|
||||
{
|
||||
$this->debug_message('calendar_boupdate::update() %1 conflicts found %2',false,count($conflicts),$conflicts);
|
||||
}
|
||||
return $conflicts;
|
||||
$conflicts['warning'] = array(
|
||||
'start' => $checked_excluding,
|
||||
'title' => lang('Only recurrences until %1 (excluding) have been checked!', $checked_excluding->format(true)),
|
||||
);
|
||||
}
|
||||
return $conflicts;
|
||||
}
|
||||
|
||||
//echo "saving $event[id]="; _debug_array($event);
|
||||
@ -400,6 +283,182 @@ class calendar_boupdate extends calendar_bo
|
||||
return $cal_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check given event for conflicts and return them
|
||||
*
|
||||
* For recurring events we check a configurable fixed number of recurrences
|
||||
* or we try for a fixed maximum time.
|
||||
*
|
||||
* @param array $event
|
||||
* @param Api\DateTime& $checked_excluding =null time until which (excluding) recurrences have been checked
|
||||
* @return array or events
|
||||
*/
|
||||
function conflicts(array $event, &$checked_excluding=null)
|
||||
{
|
||||
$types_with_quantity = array();
|
||||
foreach($this->resources as $type => $data)
|
||||
{
|
||||
if ($data['max_quantity']) $types_with_quantity[] = $type;
|
||||
}
|
||||
// get all NOT rejected participants and evtl. their quantity
|
||||
$quantity = $users = array();
|
||||
foreach($event['participants'] as $uid => $status)
|
||||
{
|
||||
$q = $r = null;
|
||||
calendar_so::split_status($status,$q,$r);
|
||||
if ($status[0] == 'R') continue; // ignore rejected participants
|
||||
|
||||
if ($uid < 0) // group, check it's members too
|
||||
{
|
||||
$users = array_unique(array_merge($users, (array)$GLOBALS['egw']->accounts->members($uid,true)));
|
||||
}
|
||||
$users[] = $uid;
|
||||
if (in_array($uid[0],$types_with_quantity))
|
||||
{
|
||||
$quantity[$uid] = $q;
|
||||
}
|
||||
}
|
||||
$max_quantity = $possible_quantity_conflicts = $conflicts = array();
|
||||
|
||||
if ($event['recur_type'])
|
||||
{
|
||||
$recurences = calendar_rrule::event2rrule($event);
|
||||
}
|
||||
else
|
||||
{
|
||||
$recurences = array(new Api\DateTime((int)$event['start']));
|
||||
}
|
||||
$checked_excluding = null;
|
||||
$max_checked = $GLOBALS['egw_info']['server']['conflict_max_checked'];
|
||||
if (($max_check_time = (float)$GLOBALS['egw_info']['server']['conflict_max_check_time']) < 1.0)
|
||||
{
|
||||
$max_check_time = 3.0;
|
||||
}
|
||||
$checked = 0;
|
||||
$start = microtime(true);
|
||||
$duration = $event['end']-$event['start'];
|
||||
foreach($recurences as $date)
|
||||
{
|
||||
$startts = $date->format('ts');
|
||||
|
||||
// abort check if configured limits are exceeded
|
||||
if ($event['recur_type'] &&
|
||||
($checked++ > $max_checked && $max_checked > 0 || // maximum number of checked recurrences exceeded
|
||||
microtime(true) > $start+$max_check_time || // max check time exceeded
|
||||
$startts > $this->config['horizont'])) // we are behind horizont for which recurring events are rendered
|
||||
{
|
||||
if ($this->debug > 2 || $this->debug == 'conflicts')
|
||||
{
|
||||
$this->debug_message(__METHOD__.'() conflict check limited to %1 recurrences, %2 seconds, until (excluding) %3',
|
||||
$checked, microtime(true)-$start, $date);
|
||||
}
|
||||
$checked_excluding = $date;
|
||||
break;
|
||||
}
|
||||
$overlapping_events =& $this->search(array(
|
||||
'start' => $startts,
|
||||
'end' => $startts+$duration,
|
||||
'users' => $users,
|
||||
'ignore_acl' => true, // otherwise we get only events readable by the user
|
||||
'enum_groups' => true, // otherwise group-events would not block time
|
||||
'query' => array(
|
||||
'cal_non_blocking' => 0,
|
||||
),
|
||||
'no_integration' => true, // do NOT use integration of other apps
|
||||
));
|
||||
if ($this->debug > 2 || $this->debug == 'conflicts')
|
||||
{
|
||||
$this->debug_message(__METHOD__.'() checking for potential overlapping events for users %1 from %2 to %3',false,$users,$startts,$startts+$duration);
|
||||
}
|
||||
foreach((array) $overlapping_events as $k => $overlap)
|
||||
{
|
||||
if ($overlap['id'] == $event['id'] || // that's the event itself
|
||||
$overlap['id'] == $event['reference'] || // event is an exception of overlap
|
||||
$overlap['non_blocking']) // that's a non_blocking event
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($this->debug > 3 || $this->debug == 'conflicts')
|
||||
{
|
||||
$this->debug_message(__METHOD__.'() checking overlapping event %1',false,$overlap);
|
||||
}
|
||||
// check if the overlap is with a rejected participant or within the allowed quantity
|
||||
$common_parts = array_intersect($users,array_keys($overlap['participants']));
|
||||
foreach($common_parts as $n => $uid)
|
||||
{
|
||||
$status = $overlap['participants'][$uid];
|
||||
calendar_so::split_status($status, $q, $r);
|
||||
if ($status == 'R')
|
||||
{
|
||||
unset($common_parts[$n]);
|
||||
continue;
|
||||
}
|
||||
if (is_numeric($uid) || !in_array($uid[0],$types_with_quantity))
|
||||
{
|
||||
continue; // no quantity check: quantity allways 1 ==> conflict
|
||||
}
|
||||
if (!isset($max_quantity[$uid]))
|
||||
{
|
||||
$res_info = $this->resource_info($uid);
|
||||
$max_quantity[$uid] = $res_info[$this->resources[$uid[0]]['max_quantity']];
|
||||
}
|
||||
$quantity[$uid] += $q;
|
||||
if ($quantity[$uid] <= $max_quantity[$uid])
|
||||
{
|
||||
$possible_quantity_conflicts[$uid][] =& $overlapping_events[$k]; // an other event can give the conflict
|
||||
unset($common_parts[$n]);
|
||||
continue;
|
||||
}
|
||||
// now we have a quantity conflict for $uid
|
||||
}
|
||||
if (count($common_parts))
|
||||
{
|
||||
if ($this->debug > 3 || $this->debug == 'conflicts')
|
||||
{
|
||||
$this->debug_message(__METHOD__.'() conflicts with the following participants found %1',false,$common_parts);
|
||||
}
|
||||
$conflicts[$overlap['id'].'-'.$this->date2ts($overlap['start'])] =& $overlapping_events[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."() conflict check took ".number_format(microtime(true)-$start, 3).'s');
|
||||
// check if we are withing the allowed quantity and if not add all events using that resource
|
||||
// seems this function is doing very strange things, it gives empty conflicts
|
||||
foreach($max_quantity as $uid => $max)
|
||||
{
|
||||
if ($quantity[$uid] > $max)
|
||||
{
|
||||
foreach((array)$possible_quantity_conflicts[$uid] as $conflict)
|
||||
{
|
||||
$conflicts[$conflict['id'].'-'.$this->date2ts($conflict['start'])] =& $possible_quantity_conflicts[$k];
|
||||
}
|
||||
}
|
||||
}
|
||||
unset($possible_quantity_conflicts);
|
||||
|
||||
if (count($conflicts))
|
||||
{
|
||||
foreach($conflicts as $key => $conflict)
|
||||
{
|
||||
$conflict['participants'] = array_intersect_key((array)$conflict['participants'],$event['participants']);
|
||||
if (!$this->check_perms(Acl::READ,$conflict))
|
||||
{
|
||||
$conflicts[$key] = array(
|
||||
'id' => $conflict['id'],
|
||||
'title' => lang('busy'),
|
||||
'participants' => $conflict['participants'],
|
||||
'start' => $conflict['start'],
|
||||
'end' => $conflict['end'],
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($this->debug > 2 || $this->debug == 'conflicts')
|
||||
{
|
||||
$this->debug_message(__METHOD__.'() %1 conflicts found %2',false,count($conflicts),$conflicts);
|
||||
}
|
||||
}
|
||||
return $conflicts;
|
||||
}
|
||||
/**
|
||||
* Remove participants current user has no right to invite
|
||||
*
|
||||
|
@ -304,6 +304,8 @@ last changed calendar de letzte Änderung
|
||||
lastname of person to notify calendar de Nachname der zu benachrichtigenden Person
|
||||
length of the time interval calendar de Länge des Zeitintervalls
|
||||
limit number of description lines in list view (default 5, 0 for no limit) calendar de Anzahl Zeilen der Beschreibung in der Listenansicht (voreingestellt 5, 0 für alle)
|
||||
limit search for conflicts in recurrences to given number of recurrences calendar de Begrenze Suche nach Terminkonflikten auf die angegebene Anzahl Wiederholungen
|
||||
limit search for conflicts in recurrences to given time in seconds (default 3) calendar de Begrenze Suche nach Terminkonflikten auf die angegebene Zeit in Sekunden (Vorgabe 3)
|
||||
link title for events to show calendar de Erweiterung des Link-Titels für Kalender-Einträge
|
||||
link to view the event calendar de Verweis (Weblink) um den Termin anzuzeigen
|
||||
links calendar de Verknüpfungen
|
||||
@ -379,6 +381,7 @@ one month calendar de ein Monat
|
||||
one week calendar de eine Woche
|
||||
one year calendar de ein Jahr
|
||||
only group-events calendar de nur Gruppentermine
|
||||
only recurrences until %1 (excluding) have been checked! calendar de Wiederholungen wurden nur bis %1 (ausschließlich) überprüft!
|
||||
only the initial date of that recuring event is checked! calendar de Nur das Startdatum diese wiederholenden Termins wird geprüft!
|
||||
only used for first viewing of calendar, afterwards last selected view is used. calendar de Wird nur bei der Erstanzeige des Kalenders benutzt, danach immer die zuletzt ausgewählte Anzeige.
|
||||
open todo's: calendar de unerledigte Aufgaben:
|
||||
|
@ -304,6 +304,8 @@ last changed calendar en Last changed
|
||||
lastname of person to notify calendar en Last name of a person to notify
|
||||
length of the time interval calendar en Length of the time interval
|
||||
limit number of description lines in list view (default 5, 0 for no limit) calendar en Limit number of description lines in list view. Default is 5, 0 for no limit.
|
||||
limit search for conflicts in recurrences to given number of recurrences calendar en Limit search for conflicts in recurrences to given number of recurrences
|
||||
limit search for conflicts in recurrences to given time in seconds (default 3) calendar en Limit search for conflicts in recurrences to given time in seconds (default 3)
|
||||
link title for events to show calendar en Link title for events to show
|
||||
link to view the event calendar en Link to view the event
|
||||
links calendar en Links
|
||||
@ -379,6 +381,7 @@ one month calendar en One month
|
||||
one week calendar en One week
|
||||
one year calendar en One year
|
||||
only group-events calendar en Only group events
|
||||
only recurrences until %1 (excluding) have been checked! calendar en Only recurrences until %1 (excluding) have been checked!
|
||||
only the initial date of that recuring event is checked! calendar en Only the initial date of that recurring event is checked!
|
||||
only used for first viewing of calendar, afterwards last selected view is used. calendar en Only used for first viewing of calendar, afterwards last selected view is used.
|
||||
open todo's: calendar en Open ToDo's:
|
||||
|
@ -105,6 +105,17 @@
|
||||
<option value="yes">Yes</option>
|
||||
</select>
|
||||
</row>
|
||||
<row>
|
||||
<description value="Scheduling conflict" span="all" class="subHeader"/>
|
||||
</row>
|
||||
<row>
|
||||
<description value="Limit search for conflicts in recurrences to given time in seconds (default 3)"/>
|
||||
<float id="newsettings[conflict_max_check_time]" min="1" max="30"/>
|
||||
</row>
|
||||
<row>
|
||||
<description value="Limit search for conflicts in recurrences to given number of recurrences"/>
|
||||
<float id="newsettings[conflict_max_checked]"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</template>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<!-- $Id$ -->
|
||||
<overlay>
|
||||
<template id="calendar.conflicts" template="" lang="" group="0" version="1.0.1.001">
|
||||
<description value=" Scheduling conflict" class="calendar_size120b"/>
|
||||
<description value="Scheduling conflict" class="calendar_size120b"/>
|
||||
<grid>
|
||||
<columns>
|
||||
<column/>
|
||||
|
Loading…
Reference in New Issue
Block a user