diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php
index b55e3cb302..1e07b3c92c 100644
--- a/calendar/inc/class.calendar_bo.inc.php
+++ b/calendar/inc/class.calendar_bo.inc.php
@@ -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));
}
diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php
index 339f58b1c9..1e14ac4a48 100644
--- a/calendar/inc/class.calendar_boupdate.inc.php
+++ b/calendar/inc/class.calendar_boupdate.inc.php
@@ -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
*
diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang
index 5b840f2ff9..a8a64fabf0 100644
--- a/calendar/lang/egw_de.lang
+++ b/calendar/lang/egw_de.lang
@@ -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:
diff --git a/calendar/lang/egw_en.lang b/calendar/lang/egw_en.lang
index 13d7f77f8a..f8a453145b 100644
--- a/calendar/lang/egw_en.lang
+++ b/calendar/lang/egw_en.lang
@@ -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:
diff --git a/calendar/templates/default/config.xet b/calendar/templates/default/config.xet
index cf3eadb5cd..ea1328a306 100644
--- a/calendar/templates/default/config.xet
+++ b/calendar/templates/default/config.xet
@@ -105,6 +105,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/calendar/templates/default/conflicts.xet b/calendar/templates/default/conflicts.xet
index baef13f017..db5ba8c245 100644
--- a/calendar/templates/default/conflicts.xet
+++ b/calendar/templates/default/conflicts.xet
@@ -3,7 +3,7 @@
-
+