Total Complexity | 701 |
Total Lines | 2957 |
Duplicated Lines | 0 % |
Changes | 4 | ||
Bugs | 1 | Features | 0 |
Complex classes like calendar_boupdate often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use calendar_boupdate, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
46 | class calendar_boupdate extends calendar_bo |
||
47 | { |
||
48 | /** |
||
49 | * Category ACL allowing to add a given category |
||
50 | */ |
||
51 | const CAT_ACL_ADD = 512; |
||
52 | /** |
||
53 | * Category ACL allowing to change status of a participant |
||
54 | */ |
||
55 | const CAT_ACL_STATUS = 1024; |
||
56 | |||
57 | /** |
||
58 | * name of method to debug or level of debug-messages: |
||
59 | * False=Off as higher as more messages you get ;-) |
||
60 | * 1 = function-calls incl. parameters to general functions like search, read, write, delete |
||
61 | * 2 = function-calls to exported helper-functions like check_perms |
||
62 | * 4 = function-calls to exported conversation-functions like date2ts, date2array, ... |
||
63 | * 5 = function-calls to private functions |
||
64 | * @var mixed |
||
65 | */ |
||
66 | var $debug; |
||
67 | |||
68 | /** |
||
69 | * Set Logging |
||
70 | * |
||
71 | * @var boolean |
||
72 | */ |
||
73 | var $log = false; |
||
74 | var $logfile = '/tmp/log-calendar-boupdate'; |
||
75 | |||
76 | /** |
||
77 | * Cached timezone data |
||
78 | * |
||
79 | * @var array id => data |
||
80 | */ |
||
81 | protected static $tz_cache = array(); |
||
82 | |||
83 | /** |
||
84 | * Constructor |
||
85 | */ |
||
86 | function __construct() |
||
93 | } |
||
94 | |||
95 | /** |
||
96 | * updates or creates an event, it (optionaly) checks for conflicts and sends the necessary notifications |
||
97 | * |
||
98 | * @param array &$event event-array, on return some values might be changed due to set defaults |
||
99 | * @param boolean $ignore_conflicts =false just ignore conflicts or do a conflict check and return the conflicting events |
||
100 | * @param boolean $touch_modified =true NOT USED ANYMORE (was only used in old csv-import), modified&modifier is always updated! |
||
101 | * @param boolean $ignore_acl =false should we ignore the acl |
||
102 | * @param boolean $updateTS =true update the content history of the event (ignored, as updating timestamps is required for sync!) |
||
103 | * @param array &$messages=null messages about because of missing ACL removed participants or categories |
||
104 | * @param boolean $skip_notification =false true: send NO notifications, default false = send them |
||
105 | * @return mixed on success: int $cal_id > 0, on error false or array with conflicting events (only if $check_conflicts) |
||
106 | * Please note: the events are not garantied to be readable by the user (no read grant or private)! |
||
107 | * |
||
108 | * @ToDo current conflict checking code does NOT cope quantity-wise correct with multiple non-overlapping |
||
109 | * events overlapping the event to store: the quantity sum is used, even as the events dont overlap! |
||
110 | * |
||
111 | * ++++++++ ++++++++ |
||
112 | * + + + B + If A get checked for conflicts, the check used for resource quantity is |
||
113 | * + + ++++++++ |
||
114 | * + A + quantity(A,resource)+quantity(B,resource)+quantity(C,resource) > maxBookable(resource) |
||
115 | * + + ++++++++ |
||
116 | * + + + C + which is clearly wrong for everything with a maximum quantity > 1 |
||
117 | * ++++++++ ++++++++ |
||
118 | */ |
||
119 | function update(&$event,$ignore_conflicts=false,$touch_modified=true,$ignore_acl=false,$updateTS=true,&$messages=null, $skip_notification=false) |
||
120 | { |
||
121 | unset($updateTS); // ignored, as updating timestamps is required for sync! |
||
122 | //error_log(__METHOD__."(".array2string($event).",$ignore_conflicts,$touch_modified,$ignore_acl)"); |
||
123 | if (!is_array($messages)) $messages = $messages ? (array)$messages : array(); |
||
124 | |||
125 | if ($this->debug > 1 || $this->debug == 'update') |
||
126 | { |
||
127 | $this->debug_message('calendar_boupdate::update(%1,ignore_conflict=%2,touch_modified=%3,ignore_acl=%4)', |
||
128 | false,$event,$ignore_conflicts,$touch_modified,$ignore_acl); |
||
129 | } |
||
130 | // check some minimum requirements: |
||
131 | // - new events need start, end and title |
||
132 | // - updated events cant set start, end or title to empty |
||
133 | if (!$event['id'] && (!$event['start'] || !$event['end'] || !$event['title']) || |
||
|
|||
134 | $event['id'] && (isset($event['start']) && !$event['start'] || isset($event['end']) && !$event['end'] || |
||
135 | isset($event['title']) && !$event['title'])) |
||
136 | { |
||
137 | $messages[] = lang('Required information (start, end, title, ...) missing!'); |
||
138 | return false; |
||
139 | } |
||
140 | |||
141 | $status_reset_to_unknown = false; |
||
142 | |||
143 | if (($new_event = !$event['id'])) // some defaults for new entries |
||
144 | { |
||
145 | // if no owner given, set user to owner |
||
146 | if (!$event['owner']) $event['owner'] = $this->user; |
||
147 | // set owner as participant if none is given |
||
148 | if (!is_array($event['participants']) || !count($event['participants'])) |
||
149 | { |
||
150 | $status = calendar_so::combine_status($event['owner'] == $this->user ? 'A' : 'U', 1, 'CHAIR'); |
||
151 | $event['participants'] = array($event['owner'] => $status); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | // check if user has the permission to update / create the event |
||
156 | if (!$ignore_acl && (!$new_event && !$this->check_perms(Acl::EDIT,$event['id']) || |
||
157 | $new_event && !$this->check_perms(Acl::EDIT,0,$event['owner'])) && |
||
158 | !$this->check_perms(Acl::ADD,0,$event['owner'])) |
||
159 | { |
||
160 | $messages[] = lang('Access to calendar of %1 denied!',Api\Accounts::username($event['owner'])); |
||
161 | return false; |
||
162 | } |
||
163 | if ($new_event) |
||
164 | { |
||
165 | $old_event = array(); |
||
166 | } |
||
167 | else |
||
168 | { |
||
169 | $old_event = $this->read((int)$event['id'],null,$ignore_acl); |
||
170 | } |
||
171 | |||
172 | // do we need to check, if user is allowed to invite the invited participants |
||
173 | if ($this->require_acl_invite && ($removed = $this->remove_no_acl_invite($event,$old_event))) |
||
174 | { |
||
175 | // report removed participants back to user |
||
176 | foreach($removed as $key => $account_id) |
||
177 | { |
||
178 | $removed[$key] = $this->participant_name($account_id); |
||
179 | } |
||
180 | $messages[] = lang('%1 participants removed because of missing invite grants',count($removed)). |
||
181 | ': '.implode(', ',$removed); |
||
182 | } |
||
183 | // check category based ACL |
||
184 | if ($event['category']) |
||
185 | { |
||
186 | if (!is_array($event['category'])) $event['category'] = explode(',',$event['category']); |
||
187 | if (!$old_event || !isset($old_event['category'])) |
||
188 | { |
||
189 | $old_event['category'] = array(); |
||
190 | } |
||
191 | elseif (!is_array($old_event['category'])) |
||
192 | { |
||
193 | $old_event['category'] = explode(',', $old_event['category']); |
||
194 | } |
||
195 | foreach($event['category'] as $key => $cat_id) |
||
196 | { |
||
197 | // check if user is allowed to update event categories |
||
198 | if ((!$old_event || !in_array($cat_id,$old_event['category'])) && |
||
199 | self::has_cat_right(self::CAT_ACL_ADD,$cat_id,$this->user) === false) |
||
200 | { |
||
201 | unset($event['category'][$key]); |
||
202 | // report removed category to user |
||
203 | $removed_cats[$cat_id] = $this->categories->id2name($cat_id); |
||
204 | continue; // no further check, as cat was removed |
||
205 | } |
||
206 | // for new or moved events check status of participants, if no category status right --> set all status to 'U' = unknown |
||
207 | if (!$status_reset_to_unknown && |
||
208 | self::has_cat_right(self::CAT_ACL_STATUS,$cat_id,$this->user) === false && |
||
209 | (!$old_event || $old_event['start'] != $event['start'] || $old_event['end'] != $event['end'])) |
||
210 | { |
||
211 | foreach((array)$event['participants'] as $uid => $status) |
||
212 | { |
||
213 | $q = $r = null; |
||
214 | calendar_so::split_status($status,$q,$r); |
||
215 | if ($status != 'U') |
||
216 | { |
||
217 | $event['participants'][$uid] = calendar_so::combine_status('U',$q,$r); |
||
218 | // todo: report reset status to user |
||
219 | } |
||
220 | } |
||
221 | $status_reset_to_unknown = true; // once is enough |
||
222 | } |
||
223 | } |
||
224 | if ($removed_cats) |
||
225 | { |
||
226 | $messages[] = lang('Category %1 removed because of missing rights',implode(', ',$removed_cats)); |
||
227 | } |
||
228 | if ($status_reset_to_unknown) |
||
229 | { |
||
230 | $messages[] = lang('Status of participants set to unknown because of missing category rights'); |
||
231 | } |
||
232 | } |
||
233 | // check for conflicts only happens !$ignore_conflicts AND if start + end date are given |
||
234 | $checked_excluding = null; |
||
235 | if (!$ignore_conflicts && !$event['non_blocking'] && isset($event['start']) && isset($event['end']) && |
||
236 | (($conflicts = $this->conflicts($event, $checked_excluding)) || $checked_excluding)) |
||
237 | { |
||
238 | if ($checked_excluding) // warn user if not all recurrences have been checked |
||
239 | { |
||
240 | $conflicts['warning'] = array( |
||
241 | 'start' => $checked_excluding, |
||
242 | 'title' => lang('Only recurrences until %1 (excluding) have been checked!', $checked_excluding->format(true)), |
||
243 | ); |
||
244 | } |
||
245 | return $conflicts; |
||
246 | } |
||
247 | |||
248 | // See if we need to reset any participant statuses |
||
249 | $this->check_reset_stati($event, $old_event); |
||
250 | |||
251 | //echo "saving $event[id]="; _debug_array($event); |
||
252 | $event2save = $event; |
||
253 | |||
254 | if (!($cal_id = $this->save($event, $ignore_acl))) |
||
255 | { |
||
256 | return $cal_id; |
||
257 | } |
||
258 | |||
259 | // we re-read the event, in case only partial information was update and we need the full info for the notifies |
||
260 | // The check for new private events is to at least show the private version, |
||
261 | // otherwise we get FALSE |
||
262 | $event = $this->read($cal_id, null, $ignore_acl, 'ts', $new_event && !$event['public'] ? $this->user : null); |
||
263 | //error_log("new $cal_id=". array2string($event)); |
||
264 | |||
265 | if($old_event['deleted'] && $event['deleted'] == null) |
||
266 | { |
||
267 | // Restored, bring back links |
||
268 | Link::restore('calendar', $cal_id); |
||
269 | } |
||
270 | if ($this->log_file) |
||
271 | { |
||
272 | $this->log2file($event2save,$event,$old_event); |
||
273 | } |
||
274 | // send notifications if the event is in the future |
||
275 | if(!$skip_notification && $event['end'] > $this->now_su) |
||
276 | { |
||
277 | if ($new_event) |
||
278 | { |
||
279 | $this->send_update(MSG_ADDED,$event['participants'],'',$event); |
||
280 | } |
||
281 | else // update existing event |
||
282 | { |
||
283 | $this->check4update($event,$old_event); |
||
284 | } |
||
285 | } |
||
286 | |||
287 | // notify the link-class about the update, as other apps may be subscribt to it |
||
288 | Link::notify_update('calendar',$cal_id,$event); |
||
289 | |||
290 | return $cal_id; |
||
291 | } |
||
292 | |||
293 | /** |
||
294 | * Check given event for conflicts and return them |
||
295 | * |
||
296 | * For recurring events we check a configurable fixed number of recurrences |
||
297 | * and for a fixed maximum time (default 3s). |
||
298 | * |
||
299 | * Conflict check skips past events/recurrences and is always limited by recurrence horizont, |
||
300 | * as it would only report non-recurring events after. |
||
301 | * |
||
302 | * @param array $event |
||
303 | * @param Api\DateTime& $checked_excluding =null time until which (excluding) recurrences have been checked |
||
304 | * @return array or events |
||
305 | */ |
||
306 | function conflicts(array $event, &$checked_excluding=null) |
||
307 | { |
||
308 | $types_with_quantity = array(); |
||
309 | foreach($this->resources as $type => $data) |
||
310 | { |
||
311 | if ($data['max_quantity']) $types_with_quantity[] = $type; |
||
312 | } |
||
313 | // get all NOT rejected participants and evtl. their quantity |
||
314 | $quantity = $users = array(); |
||
315 | foreach($event['participants'] as $uid => $status) |
||
316 | { |
||
317 | $q = $role = null; |
||
318 | calendar_so::split_status($status, $q, $role); |
||
319 | if ($status == 'R' || $role == 'NON-PARTICIPANT') continue; // ignore rejected or non-participants |
||
320 | |||
321 | if ($uid < 0) // group, check it's members too |
||
322 | { |
||
323 | $users = array_unique(array_merge($users, (array)$GLOBALS['egw']->accounts->members($uid,true))); |
||
324 | } |
||
325 | $users[] = $uid; |
||
326 | if (in_array($uid[0],$types_with_quantity)) |
||
327 | { |
||
328 | $quantity[$uid] = $q; |
||
329 | } |
||
330 | } |
||
331 | $max_quantity = $possible_quantity_conflicts = $conflicts = array(); |
||
332 | |||
333 | if ($event['recur_type']) |
||
334 | { |
||
335 | $recurences = calendar_rrule::event2rrule($event); |
||
336 | } |
||
337 | else |
||
338 | { |
||
339 | $recurences = array(new Api\DateTime((int)$event['start'])); |
||
340 | } |
||
341 | $checked_excluding = null; |
||
342 | $max_checked = $GLOBALS['egw_info']['server']['conflict_max_checked']; |
||
343 | if (($max_check_time = (float)$GLOBALS['egw_info']['server']['conflict_max_check_time']) < 1.0) |
||
344 | { |
||
345 | $max_check_time = 3.0; |
||
346 | } |
||
347 | $checked = 0; |
||
348 | $start = microtime(true); |
||
349 | $duration = $event['end']-$event['start']; |
||
350 | foreach($recurences as $date) |
||
351 | { |
||
352 | $startts = $date->format('ts'); |
||
353 | |||
354 | // skip past events or recurrences |
||
355 | if ($startts+$duration < $this->now_su) continue; |
||
356 | |||
357 | // abort check if configured limits are exceeded |
||
358 | if ($event['recur_type'] && |
||
359 | (++$checked > $max_checked && $max_checked > 0 || // maximum number of checked recurrences exceeded |
||
360 | microtime(true) > $start+$max_check_time || // max check time exceeded |
||
361 | $startts > $this->config['horizont'])) // we are behind horizon for which recurrences are rendered |
||
362 | { |
||
363 | if ($this->debug > 2 || $this->debug == 'conflicts') |
||
364 | { |
||
365 | $this->debug_message(__METHOD__.'() conflict check limited to %1 recurrences, %2 seconds, until (excluding) %3', |
||
366 | $checked, microtime(true)-$start, $date); |
||
367 | } |
||
368 | $checked_excluding = $date; |
||
369 | break; |
||
370 | } |
||
371 | $overlapping_events =& $this->search(array( |
||
372 | 'start' => $startts, |
||
373 | 'end' => $startts+$duration, |
||
374 | 'users' => $users, |
||
375 | 'ignore_acl' => true, // otherwise we get only events readable by the user |
||
376 | 'enum_groups' => true, // otherwise group-events would not block time |
||
377 | 'query' => array( |
||
378 | 'cal_non_blocking' => 0, |
||
379 | ), |
||
380 | 'no_integration' => true, // do NOT use integration of other apps |
||
381 | )); |
||
382 | if ($this->debug > 2 || $this->debug == 'conflicts') |
||
383 | { |
||
384 | $this->debug_message(__METHOD__.'() checking for potential overlapping events for users %1 from %2 to %3',false,$users,$startts,$startts+$duration); |
||
385 | } |
||
386 | foreach((array) $overlapping_events as $k => $overlap) |
||
387 | { |
||
388 | if ($overlap['id'] == $event['id'] || // that's the event itself |
||
389 | $overlap['id'] == $event['reference'] || // event is an exception of overlap |
||
390 | $overlap['non_blocking']) // that's a non_blocking event |
||
391 | { |
||
392 | continue; |
||
393 | } |
||
394 | if ($this->debug > 3 || $this->debug == 'conflicts') |
||
395 | { |
||
396 | $this->debug_message(__METHOD__.'() checking overlapping event %1',false,$overlap); |
||
397 | } |
||
398 | // check if the overlap is with a rejected participant or within the allowed quantity |
||
399 | $common_parts = array_intersect($users,array_keys($overlap['participants'])); |
||
400 | foreach($common_parts as $n => $uid) |
||
401 | { |
||
402 | $status = $overlap['participants'][$uid]; |
||
403 | calendar_so::split_status($status, $q, $role); |
||
404 | if ($status == 'R' || $role == 'NON-PARTICIPANT') |
||
405 | { |
||
406 | unset($common_parts[$n]); |
||
407 | continue; |
||
408 | } |
||
409 | if (is_numeric($uid) || !in_array($uid[0],$types_with_quantity)) |
||
410 | { |
||
411 | continue; // no quantity check: quantity allways 1 ==> conflict |
||
412 | } |
||
413 | if (!isset($max_quantity[$uid])) |
||
414 | { |
||
415 | $res_info = $this->resource_info($uid); |
||
416 | $max_quantity[$uid] = $res_info[$this->resources[$uid[0]]['max_quantity']]; |
||
417 | } |
||
418 | $quantity[$uid] += $q; |
||
419 | if ($quantity[$uid] <= $max_quantity[$uid]) |
||
420 | { |
||
421 | $possible_quantity_conflicts[$uid][] =& $overlapping_events[$k]; // an other event can give the conflict |
||
422 | unset($common_parts[$n]); |
||
423 | continue; |
||
424 | } |
||
425 | // now we have a quantity conflict for $uid |
||
426 | } |
||
427 | if (count($common_parts)) |
||
428 | { |
||
429 | if ($this->debug > 3 || $this->debug == 'conflicts') |
||
430 | { |
||
431 | $this->debug_message(__METHOD__.'() conflicts with the following participants found %1',false,$common_parts); |
||
432 | } |
||
433 | $conflicts[$overlap['id'].'-'.$this->date2ts($overlap['start'])] =& $overlapping_events[$k]; |
||
434 | } |
||
435 | } |
||
436 | } |
||
437 | //error_log(__METHOD__."() conflict check took ".number_format(microtime(true)-$start, 3).'s'); |
||
438 | // check if we are withing the allowed quantity and if not add all events using that resource |
||
439 | // seems this function is doing very strange things, it gives empty conflicts |
||
440 | foreach($max_quantity as $uid => $max) |
||
441 | { |
||
442 | if ($quantity[$uid] > $max) |
||
443 | { |
||
444 | foreach((array)$possible_quantity_conflicts[$uid] as $conflict) |
||
445 | { |
||
446 | $conflicts[$conflict['id'].'-'.$this->date2ts($conflict['start'])] =& $possible_quantity_conflicts[$k]; |
||
447 | } |
||
448 | } |
||
449 | } |
||
450 | unset($possible_quantity_conflicts); |
||
451 | |||
452 | if (count($conflicts)) |
||
453 | { |
||
454 | foreach($conflicts as $key => $conflict) |
||
455 | { |
||
456 | $conflict['participants'] = array_intersect_key((array)$conflict['participants'],$event['participants']); |
||
457 | if (!$this->check_perms(Acl::READ,$conflict)) |
||
458 | { |
||
459 | $conflicts[$key] = array( |
||
460 | 'id' => $conflict['id'], |
||
461 | 'title' => lang('busy'), |
||
462 | 'participants' => $conflict['participants'], |
||
463 | 'start' => $conflict['start'], |
||
464 | 'end' => $conflict['end'], |
||
465 | ); |
||
466 | } |
||
467 | } |
||
468 | if ($this->debug > 2 || $this->debug == 'conflicts') |
||
469 | { |
||
470 | $this->debug_message(__METHOD__.'() %1 conflicts found %2',false,count($conflicts),$conflicts); |
||
471 | } |
||
472 | } |
||
473 | return $conflicts; |
||
474 | } |
||
475 | /** |
||
476 | * Remove participants current user has no right to invite |
||
477 | * |
||
478 | * @param array &$event new event |
||
479 | * @param array $old_event =null old event with already invited participants |
||
480 | * @return array removed participants because of missing invite grants |
||
481 | */ |
||
482 | public function remove_no_acl_invite(array &$event,array $old_event=null) |
||
483 | { |
||
484 | if (!$this->require_acl_invite) |
||
485 | { |
||
486 | return array(); // nothing to check, everyone can invite everyone else |
||
487 | } |
||
488 | if ($event['id'] && is_null($old_event)) |
||
489 | { |
||
490 | $old_event = $this->read($event['id']); |
||
491 | } |
||
492 | $removed = array(); |
||
493 | foreach(array_keys((array)$event['participants']) as $uid) |
||
494 | { |
||
495 | if ((is_null($old_event) || !isset($old_event['participants'][$uid])) && !$this->check_acl_invite($uid)) |
||
496 | { |
||
497 | unset($event['participants'][$uid]); // remove participant |
||
498 | $removed[] = $uid; |
||
499 | } |
||
500 | } |
||
501 | //echo "<p>".__METHOD__."($event[title],".($old_event?'$old_event':'NULL').") returning ".array2string($removed)."</p>"; |
||
502 | return $removed; |
||
503 | } |
||
504 | |||
505 | /** |
||
506 | * Check if current user is allowed to invite a given participant |
||
507 | * |
||
508 | * @param int|string $uid |
||
509 | * @return boolean |
||
510 | */ |
||
511 | public function check_acl_invite($uid) |
||
512 | { |
||
513 | if (!is_numeric($uid) && $this->resources[$uid[0]]['check_invite']) |
||
514 | { |
||
515 | // Resource specific ACL check |
||
516 | return call_user_func($this->resources[$uid[0]]['check_invite'], $uid); |
||
517 | } |
||
518 | elseif (!$this->require_acl_invite) |
||
519 | { |
||
520 | $ret = true; // no grant required |
||
521 | } |
||
522 | elseif ($this->require_acl_invite == 'groups' && $GLOBALS['egw']->accounts->get_type($uid) != 'g') |
||
523 | { |
||
524 | $ret = true; // grant only required for groups |
||
525 | } |
||
526 | else |
||
527 | { |
||
528 | $ret = $this->check_perms(self::ACL_INVITE,0,$uid); |
||
529 | } |
||
530 | //error_log(__METHOD__."($uid) = ".array2string($ret)); |
||
531 | //echo "<p>".__METHOD__."($uid) require_acl_invite=$this->require_acl_invite returning ".array2string($ret)."</p>\n"; |
||
532 | return $ret; |
||
533 | } |
||
534 | |||
535 | /** |
||
536 | * Check for added, modified or deleted participants AND notify them |
||
537 | * |
||
538 | * @param array $new_event the updated event |
||
539 | * @param array $old_event the event before the update |
||
540 | * @todo check if there is a real change, not assume every save is a change |
||
541 | */ |
||
542 | function check4update($new_event,$old_event) |
||
543 | { |
||
544 | //error_log(__METHOD__."($new_event[title])"); |
||
545 | $modified = $added = $deleted = array(); |
||
546 | |||
547 | //echo "<p>calendar_boupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n"; |
||
548 | |||
549 | // Find modified and deleted participants ... |
||
550 | foreach($old_event['participants'] as $old_userid => $old_status) |
||
551 | { |
||
552 | if(isset($new_event['participants'][$old_userid])) |
||
553 | { |
||
554 | $modified[$old_userid] = $new_event['participants'][$old_userid]; |
||
555 | } |
||
556 | else |
||
557 | { |
||
558 | $deleted[$old_userid] = $old_status; |
||
559 | } |
||
560 | } |
||
561 | // Find new participants ... |
||
562 | foreach(array_keys((array)$new_event['participants']) as $new_userid) |
||
563 | { |
||
564 | if(!isset($old_event['participants'][$new_userid])) |
||
565 | { |
||
566 | $added[$new_userid] = 'U'; |
||
567 | } |
||
568 | } |
||
569 | //echo "<p>calendar_boupdate::check4update() added=".print_r($added,true).", modified=".print_r($modified,true).", deleted=".print_r($deleted,true)."</p>\n"; |
||
570 | if(count($added) || count($modified) || count($deleted)) |
||
571 | { |
||
572 | if(count($added)) |
||
573 | { |
||
574 | $this->send_update(MSG_ADDED,$added,$old_event,$new_event); |
||
575 | } |
||
576 | if(count($modified)) |
||
577 | { |
||
578 | $this->send_update(MSG_MODIFIED,$modified,$old_event,$new_event); |
||
579 | } |
||
580 | if(count($deleted)) |
||
581 | { |
||
582 | $this->send_update(MSG_DISINVITE,$deleted,$new_event); |
||
583 | } |
||
584 | } |
||
585 | } |
||
586 | |||
587 | /** |
||
588 | * checks if $userid has requested (in $part_prefs) updates for $msg_type |
||
589 | * |
||
590 | * @param int $userid numerical user-id |
||
591 | * @param array $part_prefs preferces of the user $userid |
||
592 | * @param int &$msg_type type of the notification: MSG_ADDED, MSG_MODIFIED, MSG_ACCEPTED, ... |
||
593 | * @param array $old_event Event before the change |
||
594 | * @param array $new_event Event after the change |
||
595 | * @param string $role we treat CHAIR like event owners |
||
596 | * @param string $status of current user |
||
597 | * @return boolean true = update requested, false otherwise |
||
598 | */ |
||
599 | public static function update_requested($userid, $part_prefs, &$msg_type, $old_event ,$new_event, $role, $status=null) |
||
600 | { |
||
601 | if ($msg_type == MSG_ALARM) |
||
602 | { |
||
603 | return True; // always True for now |
||
604 | } |
||
605 | $want_update = 0; |
||
606 | |||
607 | // the following switch falls through all cases, as each included the following too |
||
608 | // |
||
609 | $msg_is_response = $msg_type == MSG_REJECTED || $msg_type == MSG_ACCEPTED || $msg_type == MSG_TENTATIVE || $msg_type == MSG_DELEGATED; |
||
610 | |||
611 | // Check if user is not participating, and does not want notifications |
||
612 | if($msg_is_response && !$part_prefs['calendar']['receive_not_participating'] && !array_key_exists($userid, $old_event['participants'])) |
||
613 | { |
||
614 | return false; |
||
615 | } |
||
616 | |||
617 | // always notify externals chairs |
||
618 | // EGroupware owner only get notified about responses, if pref is NOT "no" |
||
619 | if (!is_numeric($userid) && $role == 'CHAIR' && |
||
620 | ($msg_is_response || in_array($msg_type, array(MSG_ADDED, MSG_DELETED)))) |
||
621 | { |
||
622 | switch($msg_type) |
||
623 | { |
||
624 | case MSG_DELETED: // treat deleting event as rejection to organizer |
||
625 | $msg_type = MSG_REJECTED; |
||
626 | break; |
||
627 | case MSG_ADDED: // new events use added, but organizer needs status |
||
628 | switch($status[0]) |
||
629 | { |
||
630 | case 'A': $msg_type = MSG_ACCEPTED; break; |
||
631 | case 'R': $msg_type = MSG_REJECTED; break; |
||
632 | case 'T': $msg_type = MSG_TENTATIVE; break; |
||
633 | case 'D': $msg_type = MSG_DELEGATED; break; |
||
634 | } |
||
635 | break; |
||
636 | } |
||
637 | ++$want_update; |
||
638 | } |
||
639 | else |
||
640 | { |
||
641 | switch($ru = $part_prefs['calendar']['receive_updates']) |
||
642 | { |
||
643 | case 'responses': |
||
644 | ++$want_update; |
||
645 | case 'modifications': |
||
646 | if (!$msg_is_response) |
||
647 | { |
||
648 | ++$want_update; |
||
649 | } |
||
650 | case 'time_change_4h': |
||
651 | case 'time_change': |
||
652 | default: |
||
653 | if (is_array($new_event) && is_array($old_event)) |
||
654 | { |
||
655 | $diff = max(abs(self::date2ts($old_event['start'])-self::date2ts($new_event['start'])), |
||
656 | abs(self::date2ts($old_event['end'])-self::date2ts($new_event['end']))); |
||
657 | $check = $ru == 'time_change_4h' ? 4 * 60 * 60 - 1 : 0; |
||
658 | if ($msg_type == MSG_MODIFIED && $diff > $check) |
||
659 | { |
||
660 | ++$want_update; |
||
661 | } |
||
662 | } |
||
663 | case 'add_cancel': |
||
664 | if ($msg_is_response && ($old_event['owner'] == $userid || $role == 'CHAIR') || |
||
665 | $msg_type == MSG_DELETED || $msg_type == MSG_ADDED || $msg_type == MSG_DISINVITE) |
||
666 | { |
||
667 | ++$want_update; |
||
668 | } |
||
669 | break; |
||
670 | case 'no': |
||
671 | break; |
||
672 | } |
||
673 | } |
||
674 | //error_log(__METHOD__."(userid=$userid, receive_updates='$ru', msg_type=$msg_type, ..., role='$role') msg_is_response=$msg_is_response --> want_update=$want_update"); |
||
675 | return $want_update > 0; |
||
676 | } |
||
677 | |||
678 | /** |
||
679 | * Check calendar prefs, if a given user (integer account_id) or email (user or externals) should get notified |
||
680 | * |
||
681 | * @param int|string $user_or_email |
||
682 | * @param string $ical_method ='REQUEST' |
||
683 | * @param string $role ='REQ-PARTICIPANT' |
||
684 | * @return boolean true if user requested to be notified, false if not |
||
685 | */ |
||
686 | static public function email_update_requested($user_or_email, $ical_method='REQUEST', $role='REQ-PARTICIPANT') |
||
687 | { |
||
688 | // check if email is from a user |
||
689 | if (is_numeric($user_or_email)) |
||
690 | { |
||
691 | $account_id = $user_or_email; |
||
692 | } |
||
693 | else |
||
694 | { |
||
695 | $account_id = $GLOBALS['egw']->accounts->name2id($user_or_email, 'account_email'); |
||
696 | } |
||
697 | if ($account_id) |
||
698 | { |
||
699 | $pref_obj = new Api\Preferences($account_id); |
||
700 | $prefs = $pref_obj->read_repository(); |
||
701 | } |
||
702 | else |
||
703 | { |
||
704 | $prefs = array( |
||
705 | 'calendar' => array( |
||
706 | 'receive_updates' => $GLOBALS['egw_info']['user']['preferences']['calendar']['notify_externals'], |
||
707 | ) |
||
708 | ); |
||
709 | } |
||
710 | switch($ical_method) |
||
711 | { |
||
712 | default: |
||
713 | case 'REQUEST': |
||
714 | $msg_type = MSG_ADDED; |
||
715 | break; |
||
716 | case 'REPLY': |
||
717 | $msg_type = MSG_ACCEPTED; |
||
718 | break; |
||
719 | case 'CANCEL': |
||
720 | $msg_type = MSG_DELETED; |
||
721 | break; |
||
722 | } |
||
723 | $ret = self::update_requested($account_id, $prefs, $msg_type, array(), array(), $role); |
||
724 | //error_log(__METHOD__."('$user_or_email', '$ical_method', '$role') account_id=$account_id --> updated_requested returned ".array2string($ret)); |
||
725 | return $ret; |
||
726 | } |
||
727 | |||
728 | /** |
||
729 | * Get iCal/iMip method from internal nummeric msg-type plus optional notification message and verbose name |
||
730 | * |
||
731 | * @param int $msg_type see MSG_* defines |
||
732 | * @param string& $action=null on return verbose name |
||
733 | * @param string& $msg=null on return notification message |
||
734 | */ |
||
735 | function msg_type2ical_method($msg_type, &$action=null, &$msg=null, $prefs=null) |
||
736 | { |
||
737 | switch($msg_type) |
||
738 | { |
||
739 | case MSG_DELETED: |
||
740 | $action = 'Canceled'; |
||
741 | $pref = 'Canceled'; |
||
742 | $method = 'CANCEL'; |
||
743 | break; |
||
744 | case MSG_MODIFIED: |
||
745 | $action = 'Modified'; |
||
746 | $pref = 'Modified'; |
||
747 | $method = 'REQUEST'; |
||
748 | break; |
||
749 | case MSG_DISINVITE: |
||
750 | $action = 'Disinvited'; |
||
751 | $pref = 'Disinvited'; |
||
752 | $method = 'CANCEL'; |
||
753 | break; |
||
754 | case MSG_ADDED: |
||
755 | $action = 'Added'; |
||
756 | $pref = 'Added'; |
||
757 | $method = 'REQUEST'; |
||
758 | break; |
||
759 | case MSG_REJECTED: |
||
760 | $action = 'Rejected'; |
||
761 | $pref = 'Response'; |
||
762 | $method = 'REPLY'; |
||
763 | break; |
||
764 | case MSG_TENTATIVE: |
||
765 | $action = 'Tentative'; |
||
766 | $pref = 'Response'; |
||
767 | $method = 'REPLY'; |
||
768 | break; |
||
769 | case MSG_ACCEPTED: |
||
770 | $action = 'Accepted'; |
||
771 | $pref = 'Response'; |
||
772 | $method = 'REPLY'; |
||
773 | break; |
||
774 | case MSG_DELEGATED: |
||
775 | $action = 'Delegated'; |
||
776 | $pref = 'Response'; |
||
777 | $method = 'REPLY'; |
||
778 | break; |
||
779 | case MSG_ALARM: |
||
780 | $action = 'Alarm'; |
||
781 | $pref = 'Alarm'; |
||
782 | break; |
||
783 | default: |
||
784 | $method = 'PUBLISH'; |
||
785 | } |
||
786 | if(is_null($prefs)) |
||
787 | { |
||
788 | $prefs = $this->cal_prefs; |
||
789 | } |
||
790 | $msg = $prefs['notify'.$pref]; |
||
791 | if (empty($msg)) |
||
792 | { |
||
793 | $msg = $prefs['notifyAdded']; // use a default |
||
794 | } |
||
795 | //error_log(__METHOD__."($msg_type) action='$action', $msg='$msg' returning '$method'"); |
||
796 | return $method; |
||
797 | } |
||
798 | |||
799 | /** |
||
800 | * sends update-messages to certain participants of an event |
||
801 | * |
||
802 | * @param int $msg_type type of the notification: MSG_ADDED, MSG_MODIFIED, MSG_ACCEPTED, ... |
||
803 | * @param array $to_notify numerical user-ids as keys (!) (value is not used) |
||
804 | * @param array $old_event Event before the change |
||
805 | * @param array $new_event =null Event after the change |
||
806 | * @param int/string $user =0 User/participant who started the notify, default current user |
||
807 | * @return bool true/false |
||
808 | */ |
||
809 | function send_update($msg_type,$to_notify,$old_event,$new_event=null,$user=0) |
||
1162 | } |
||
1163 | |||
1164 | function get_update_message($event,$added) |
||
1165 | { |
||
1166 | $nul = null; |
||
1167 | $details = $this->_get_event_details($event,$added ? lang('Added') : lang('Modified'),$nul); |
||
1168 | |||
1169 | $notify_msg = $this->cal_prefs[$added || empty($this->cal_prefs['notifyModified']) ? 'notifyAdded' : 'notifyModified']; |
||
1170 | |||
1171 | return explode("\n",$GLOBALS['egw']->preferences->parse_notify($notify_msg,$details),2); |
||
1172 | } |
||
1173 | |||
1174 | /** |
||
1175 | * Function called via async service, when an alarm is to be send |
||
1176 | * |
||
1177 | * @param array $alarm array with keys owner, cal_id, all |
||
1178 | * @return boolean |
||
1179 | */ |
||
1180 | function send_alarm($alarm) |
||
1181 | { |
||
1182 | //echo "<p>bocalendar::send_alarm("; print_r($alarm); echo ")</p>\n"; |
||
1183 | $GLOBALS['egw_info']['user']['account_id'] = $this->owner = $alarm['owner']; |
||
1184 | |||
1185 | $event_time_user = Api\DateTime::server2user($alarm['time'] + $alarm['offset']); // alarm[time] is in server-time, read requires user-time |
||
1186 | if (!$alarm['owner'] || !$alarm['cal_id'] || !($event = $this->read($alarm['cal_id'],$event_time_user))) |
||
1187 | { |
||
1188 | return False; // event not found |
||
1189 | } |
||
1190 | if ($alarm['all']) |
||
1191 | { |
||
1192 | $to_notify = $event['participants']; |
||
1193 | } |
||
1194 | elseif ($this->check_perms(Acl::READ,$event)) // checks agains $this->owner set to $alarm[owner] |
||
1195 | { |
||
1196 | $to_notify[$alarm['owner']] = 'A'; |
||
1197 | } |
||
1198 | else |
||
1199 | { |
||
1200 | return False; // no rights |
||
1201 | } |
||
1202 | // need to load calendar translations and set currentapp, so calendar can reload a different lang |
||
1203 | Api\Translation::add_app('calendar'); |
||
1204 | $GLOBALS['egw_info']['flags']['currentapp'] = 'calendar'; |
||
1205 | |||
1206 | $ret = $this->send_update(MSG_ALARM,$to_notify,$event,False,$alarm['owner']); |
||
1207 | |||
1208 | // create a new alarm for recuring events for the next event, if one exists |
||
1209 | if ($event['recur_type'] != MCAL_RECUR_NONE && ($event = $this->read($alarm['cal_id'],$event_time_user+1))) |
||
1210 | { |
||
1211 | $alarm['time'] = $this->date2ts($event['start']) - $alarm['offset']; |
||
1212 | unset($alarm['times']); |
||
1213 | unset($alarm['next']); |
||
1214 | unset($alarm['keep_time']); // need to remove the keep_time, as otherwise the alarm would be deleted automatically |
||
1215 | //error_log(__METHOD__."() moving alarm to next recurrence ".array2string($alarm)); |
||
1216 | $this->save_alarm($alarm['cal_id'], $alarm, false); // false = do NOT update timestamp, as nothing changed for iCal clients |
||
1217 | } |
||
1218 | return $ret; |
||
1219 | } |
||
1220 | |||
1221 | /** |
||
1222 | * saves an event to the database, does NOT do any notifications, see calendar_boupdate::update for that |
||
1223 | * |
||
1224 | * This methode converts from user to server time and handles the insertion of users and dates of repeating events |
||
1225 | * |
||
1226 | * @param array $event |
||
1227 | * @param boolean $ignore_acl =false should we ignore the acl |
||
1228 | * @param boolean $updateTS =true update the content history of the event |
||
1229 | * Please note: you should ALLWAYS update timestamps, as they are required for sync! |
||
1230 | * @return int|boolean $cal_id > 0 or false on error (eg. permission denied) |
||
1231 | */ |
||
1232 | function save($event,$ignore_acl=false,$updateTS=true) |
||
1233 | { |
||
1234 | //error_log(__METHOD__.'('.array2string($event).", $ignore_acl, $updateTS)"); |
||
1235 | |||
1236 | // check if user has the permission to update / create the event |
||
1237 | if (!$ignore_acl && ($event['id'] && !$this->check_perms(Acl::EDIT,$event['id']) || |
||
1238 | !$event['id'] && !$this->check_perms(Acl::EDIT,0,$event['owner']) && |
||
1239 | !$this->check_perms(Acl::ADD,0,$event['owner']))) |
||
1240 | { |
||
1241 | return false; |
||
1242 | } |
||
1243 | |||
1244 | if ($event['id']) |
||
1245 | { |
||
1246 | // invalidate the read-cache if it contains the event we store now |
||
1247 | if ($event['id'] == self::$cached_event['id']) self::$cached_event = array(); |
||
1248 | $old_event = $this->read($event['id'], $event['recurrence'], $ignore_acl, 'server'); |
||
1249 | } |
||
1250 | else |
||
1251 | { |
||
1252 | $old_event = null; |
||
1253 | } |
||
1254 | if (!isset($event['whole_day'])) $event['whole_day'] = $this->isWholeDay($event); |
||
1255 | |||
1256 | // set recur-enddate/range-end to real end-date of last recurrence |
||
1257 | if ($event['recur_type'] != MCAL_RECUR_NONE && $event['recur_enddate'] && $event['start']) |
||
1258 | { |
||
1259 | $event['recur_enddate'] = new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); |
||
1260 | $event['recur_enddate']->setTime(23,59,59); |
||
1261 | $rrule = calendar_rrule::event2rrule($event, true, Api\DateTime::$user_timezone->getName()); |
||
1262 | $rrule->rewind(); |
||
1263 | $enddate = $rrule->current(); |
||
1264 | do |
||
1265 | { |
||
1266 | $rrule->next_no_exception(); |
||
1267 | $occurrence = $rrule->current(); |
||
1268 | } |
||
1269 | while ($rrule->valid($event['whole_day']) && ($enddate = $occurrence)); |
||
1270 | $enddate->modify(($event['end'] - $event['start']).' second'); |
||
1271 | $event['recur_enddate'] = $save_event['recur_enddate'] = $enddate->format('ts'); |
||
1272 | //error_log(__METHOD__."($event[title]) start=".Api\DateTime::to($event['start'],'string').', end='.Api\DateTime::to($event['end'],'string').', range_end='.Api\DateTime::to($event['recur_enddate'],'string')); |
||
1273 | } |
||
1274 | |||
1275 | $save_event = $event; |
||
1276 | if ($event['whole_day']) |
||
1277 | { |
||
1278 | if (!empty($event['start'])) |
||
1279 | { |
||
1280 | $time = $this->so->startOfDay(new Api\DateTime($event['start'], Api\DateTime::$user_timezone)); |
||
1281 | $event['start'] = Api\DateTime::to($time, 'ts'); |
||
1282 | $save_event['start'] = $time; |
||
1283 | } |
||
1284 | if (!empty($event['end'])) |
||
1285 | { |
||
1286 | $time = new Api\DateTime($event['end'], Api\DateTime::$user_timezone); |
||
1287 | |||
1288 | // Check to see if switching timezones changes the date, we'll need to adjust for that |
||
1289 | $end_event_timezone = clone $time; |
||
1290 | $time->setServer(); |
||
1291 | $delta = (int)$end_event_timezone->format('z') - (int)$time->format('z'); |
||
1292 | $time->add("$delta days"); |
||
1293 | |||
1294 | $time->setTime(23, 59, 59); |
||
1295 | $event['end'] = Api\DateTime::to($time, 'ts'); |
||
1296 | $save_event['end'] = $time; |
||
1297 | } |
||
1298 | if (!empty($event['recurrence'])) |
||
1299 | { |
||
1300 | $time = $this->so->startOfDay(new Api\DateTime($event['recurrence'], Api\DateTime::$user_timezone)); |
||
1301 | $event['recurrence'] = Api\DateTime::to($time, 'ts'); |
||
1302 | } |
||
1303 | if (!empty($event['recur_enddate'])) |
||
1304 | { |
||
1305 | // all-day events are handled in server time, but here (BO) it's in user time |
||
1306 | $time = new Api\DateTime($event['recur_enddate'], Api\DateTime::$user_timezone); |
||
1307 | $time->setTime(23, 59, 59); |
||
1308 | // Check to see if switching timezones changes the date, we'll need to adjust for that |
||
1309 | $enddate_event_timezone = clone $time; |
||
1310 | $time->setServer(); |
||
1311 | $delta = (int)$enddate_event_timezone->format('z') - (int)$time->format('z'); |
||
1312 | $time->add("$delta days"); |
||
1313 | |||
1314 | //$time->setServer(); |
||
1315 | $time->setTime(23, 59, 59); |
||
1316 | |||
1317 | $event['recur_enddate'] = $save_event['recur_enddate'] = $time; |
||
1318 | } |
||
1319 | $timestamps = array('modified','created'); |
||
1320 | // all-day events are handled in server time |
||
1321 | // $event['tzid'] = $save_event['tzid'] = Api\DateTime::$server_timezone->getName(); |
||
1322 | } |
||
1323 | else |
||
1324 | { |
||
1325 | $timestamps = array('start','end','modified','created','recur_enddate','recurrence'); |
||
1326 | } |
||
1327 | // we run all dates through date2ts, to adjust to server-time and the possible date-formats |
||
1328 | foreach($timestamps as $ts) |
||
1329 | { |
||
1330 | // we convert here from user-time to timestamps in server-time! |
||
1331 | if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2ts($event[$ts],true) : 0; |
||
1332 | } |
||
1333 | // convert tzid name to integer tz_id, of set user default |
||
1334 | if (empty($event['tzid']) || !($event['tz_id'] = calendar_timezones::tz2id($event['tzid']))) |
||
1335 | { |
||
1336 | $event['tz_id'] = calendar_timezones::tz2id($event['tzid'] = Api\DateTime::$user_timezone->getName()); |
||
1337 | } |
||
1338 | // same with the recur exceptions |
||
1339 | if (isset($event['recur_exception']) && is_array($event['recur_exception'])) |
||
1340 | { |
||
1341 | foreach($event['recur_exception'] as &$date) |
||
1342 | { |
||
1343 | if ($event['whole_day']) |
||
1344 | { |
||
1345 | $time = $this->so->startOfDay(new Api\DateTime($date, Api\DateTime::$user_timezone)); |
||
1346 | $date = Api\DateTime::to($time, 'ts'); |
||
1347 | } |
||
1348 | else |
||
1349 | { |
||
1350 | $date = $this->date2ts($date,true); |
||
1351 | } |
||
1352 | } |
||
1353 | unset($date); |
||
1354 | } |
||
1355 | // same with the alarms |
||
1356 | if (isset($event['alarm']) && is_array($event['alarm']) && isset($event['start'])) |
||
1357 | { |
||
1358 | // Expand group invitations so we don't lose individual alarms |
||
1359 | $expanded = $event; |
||
1360 | $this->enum_groups($expanded); |
||
1361 | foreach($event['alarm'] as $id => &$alarm) |
||
1362 | { |
||
1363 | // remove alarms belonging to not longer existing or rejected participants |
||
1364 | if ($alarm['owner'] && isset($expanded['participants'])) |
||
1365 | { |
||
1366 | // Don't auto-delete alarm if for all users |
||
1367 | if($alarm['all']) continue; |
||
1368 | |||
1369 | $status = $expanded['participants'][$alarm['owner']]; |
||
1370 | if (!isset($status) || calendar_so::split_status($status) === 'R') |
||
1371 | { |
||
1372 | unset($event['alarm'][$id]); |
||
1373 | $this->so->delete_alarm($id); |
||
1374 | //error_log(__LINE__.': '.__METHOD__."(".array2string($event).") deleting alarm=".array2string($alarm).", $status=".array2string($alarm)); |
||
1375 | } |
||
1376 | } |
||
1377 | $alarm['time'] = $this->date2ts($alarm['time'],true); // user to server-time |
||
1378 | } |
||
1379 | } |
||
1380 | // update all existing alarm times, in case alarm got moved and alarms are not include in $event |
||
1381 | if ($old_event && is_array($old_event['alarm']) && isset($event['start'])) |
||
1382 | { |
||
1383 | foreach($old_event['alarm'] as $id => &$alarm) |
||
1384 | { |
||
1385 | if (!isset($event['alarm'][$id])) |
||
1386 | { |
||
1387 | $alarm['time'] = $event['start'] - $alarm['offset']; |
||
1388 | if ($alarm['time'] < time()) calendar_so::shift_alarm($event, $alarm); |
||
1389 | // remove (not store) alarms belonging to not longer existing or rejected participants |
||
1390 | $status = isset($event['participants']) ? $event['participants'][$alarm['owner']] : |
||
1391 | $old_event['participants'][$alarm['owner']]; |
||
1392 | if (!$alarm['owner'] || isset($status) && calendar_so::split_status($status) !== 'R') |
||
1393 | { |
||
1394 | $this->so->save_alarm($event['id'], $alarm); |
||
1395 | //error_log(__LINE__.': '.__METHOD__."() so->save_alarm($event[id], ".array2string($alarm).")"); |
||
1396 | } |
||
1397 | else |
||
1398 | { |
||
1399 | $this->so->delete_alarm($id); |
||
1400 | //error_log(__LINE__.': '.__METHOD__."(".array2string($event).") deleting alarm=".array2string($alarm).", $status=".array2string($alarm)); |
||
1401 | } |
||
1402 | } |
||
1403 | } |
||
1404 | } |
||
1405 | |||
1406 | // you should always update modification time (ctag depends on it!) |
||
1407 | if ($updateTS) |
||
1408 | { |
||
1409 | $event['modified'] = $save_event['modified'] = $this->now; |
||
1410 | $event['modifier'] = $save_event['modifier'] = $this->user; |
||
1411 | } |
||
1412 | |||
1413 | if (empty($event['id']) && (!isset($event['created']) || $event['created'] > $this->now)) |
||
1414 | { |
||
1415 | $event['created'] = $save_event['created'] = $this->now; |
||
1416 | $event['creator'] = $save_event['creator'] = $this->user; |
||
1417 | } |
||
1418 | $set_recurrences = $old_event ? abs($event['recur_enddate'] - $old_event['recur_enddate']) > 1 : false; |
||
1419 | $set_recurrences_start = 0; |
||
1420 | if (($cal_id = $this->so->save($event,$set_recurrences,$set_recurrences_start,0,$event['etag'])) && $set_recurrences && $event['recur_type'] != MCAL_RECUR_NONE) |
||
1421 | { |
||
1422 | $save_event['id'] = $cal_id; |
||
1423 | // unset participants to enforce the default stati for all added recurrences |
||
1424 | unset($save_event['participants']); |
||
1425 | $this->set_recurrences($save_event, $set_recurrences_start); |
||
1426 | } |
||
1427 | |||
1428 | // create links for new participants from addressbook, if configured |
||
1429 | if ($cal_id && $GLOBALS['egw_info']['server']['link_contacts'] && $event['participants']) |
||
1430 | { |
||
1431 | foreach($event['participants'] as $uid => $status) |
||
1432 | { |
||
1433 | $user_type = $user_id = null; |
||
1434 | calendar_so::split_user($uid, $user_type, $user_id); |
||
1435 | if ($user_type == 'c' && (!$old_event || !isset($old_event['participants'][$uid]))) |
||
1436 | { |
||
1437 | Link::link('calendar', $cal_id, 'addressbook', $user_id); |
||
1438 | } |
||
1439 | } |
||
1440 | } |
||
1441 | |||
1442 | // Update history |
||
1443 | $tracking = new calendar_tracking($this); |
||
1444 | if (empty($event['id']) && !empty($cal_id)) $event['id']=$cal_id; |
||
1445 | $tracking->track($save_event, $old_event); |
||
1446 | |||
1447 | return $cal_id; |
||
1448 | } |
||
1449 | |||
1450 | /** |
||
1451 | * Check if the current user has the necessary ACL rights to change the status of $uid |
||
1452 | * |
||
1453 | * For contacts we use edit rights of the owner of the event (aka. edit rights of the event). |
||
1454 | * |
||
1455 | * @param int|string $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15) |
||
1456 | * @param array|int $event event array or id of the event |
||
1457 | * @return boolean |
||
1458 | */ |
||
1459 | function check_status_perms($uid,$event) |
||
1460 | { |
||
1461 | if ($uid[0] == 'c' || $uid[0] == 'e') // for contact we use the owner of the event |
||
1462 | { |
||
1463 | if (!is_array($event) && !($event = $this->read($event))) return false; |
||
1464 | |||
1465 | return $this->check_perms(Acl::EDIT,0,$event['owner']); |
||
1466 | } |
||
1467 | // check if we have a category Acl for the event or not (null) |
||
1468 | $access = $this->check_cat_acl(self::CAT_ACL_STATUS,$event); |
||
1469 | if (!is_null($access)) |
||
1470 | { |
||
1471 | return $access; |
||
1472 | } |
||
1473 | // no access or denied access because of category acl --> regular check |
||
1474 | if (!is_numeric($uid)) // this is eg. for resources (r123) |
||
1475 | { |
||
1476 | $resource = $this->resource_info($uid); |
||
1477 | |||
1478 | return Acl::EDIT & $resource['rights']; |
||
1479 | } |
||
1480 | if (!is_array($event) && !($event = $this->read($event))) return false; |
||
1481 | |||
1482 | // regular user and groups (need to check memberships too) |
||
1483 | if (!isset($event['participants'][$uid])) |
||
1484 | { |
||
1485 | $memberships = $GLOBALS['egw']->accounts->memberships($uid,true); |
||
1486 | } |
||
1487 | $memberships[] = $uid; |
||
1488 | return array_intersect($memberships, array_keys($event['participants'])) && $this->check_perms(Acl::EDIT,0,$uid); |
||
1489 | } |
||
1490 | |||
1491 | /** |
||
1492 | * Check if current user has a certain right on the categories of an event |
||
1493 | * |
||
1494 | * Not having the given right for a single category, means not having it! |
||
1495 | * |
||
1496 | * @param int $right self::CAT_ACL_{ADD|STATUS} |
||
1497 | * @param int|array $event |
||
1498 | * @return boolean true if use has the right, false if not |
||
1499 | * @return boolean false=access denied because of cat acl, true access granted because of cat acl, |
||
1500 | * null = cat has no acl |
||
1501 | */ |
||
1502 | function check_cat_acl($right,$event) |
||
1503 | { |
||
1504 | if (!is_array($event)) $event = $this->read($event); |
||
1505 | |||
1506 | $ret = null; |
||
1507 | if ($event['category']) |
||
1508 | { |
||
1509 | foreach(is_array($event['category']) ? $event['category'] : explode(',',$event['category']) as $cat_id) |
||
1510 | { |
||
1511 | $access = self::has_cat_right($right,$cat_id,$this->user); |
||
1512 | if ($access === true) |
||
1513 | { |
||
1514 | $ret = true; |
||
1515 | break; |
||
1516 | } |
||
1517 | if ($access === false) |
||
1518 | { |
||
1519 | $ret = false; // cat denies access --> check further cats |
||
1520 | } |
||
1521 | } |
||
1522 | } |
||
1523 | //echo "<p>".__METHOD__."($event[id]: $event[title], $right) = ".array2string($ret)."</p>\n"; |
||
1524 | return $ret; |
||
1525 | } |
||
1526 | |||
1527 | /** |
||
1528 | * Array with $cat_id => $rights pairs for current user (no entry means, cat is not limited by ACL!) |
||
1529 | * |
||
1530 | * @var array |
||
1531 | */ |
||
1532 | private static $cat_rights_cache; |
||
1533 | |||
1534 | /** |
||
1535 | * Get rights for a given category id |
||
1536 | * |
||
1537 | * @param int $cat_id =null null to return array with all cats |
||
1538 | * @return array with account_id => right pairs |
||
1539 | */ |
||
1540 | public static function get_cat_rights($cat_id=null) |
||
1541 | { |
||
1542 | if (!isset(self::$cat_rights_cache)) |
||
1543 | { |
||
1544 | self::$cat_rights_cache = Api\Cache::getSession('calendar','cat_rights', |
||
1545 | array($GLOBALS['egw']->acl,'get_location_grants'),array('L%','calendar')); |
||
1546 | } |
||
1547 | //echo "<p>".__METHOD__."($cat_id) = ".array2string($cat_id ? self::$cat_rights_cache['L'.$cat_id] : self::$cat_rights_cache)."</p>\n"; |
||
1548 | return $cat_id ? self::$cat_rights_cache['L'.$cat_id] : self::$cat_rights_cache; |
||
1549 | } |
||
1550 | |||
1551 | /** |
||
1552 | * Set rights for a given single category and user |
||
1553 | * |
||
1554 | * @param int $cat_id |
||
1555 | * @param int $user |
||
1556 | * @param int $rights self::CAT_ACL_{ADD|STATUS} or'ed together |
||
1557 | */ |
||
1558 | public static function set_cat_rights($cat_id,$user,$rights) |
||
1559 | { |
||
1560 | //echo "<p>".__METHOD__."($cat_id,$user,$rights)</p>\n"; |
||
1561 | if (!isset(self::$cat_rights_cache)) self::get_cat_rights($cat_id); |
||
1562 | |||
1563 | if ((int)$rights != (int)self::$cat_rights_cache['L'.$cat_id][$user]) |
||
1564 | { |
||
1565 | if ($rights) |
||
1566 | { |
||
1567 | self::$cat_rights_cache['L'.$cat_id][$user] = $rights; |
||
1568 | $GLOBALS['egw']->acl->add_repository('calendar','L'.$cat_id,$user,$rights); |
||
1569 | } |
||
1570 | else |
||
1571 | { |
||
1572 | unset(self::$cat_rights_cache['L'.$cat_id][$user]); |
||
1573 | if (!self::$cat_rights_cache['L'.$cat_id]) unset(self::$cat_rights_cache['L'.$cat_id]); |
||
1574 | $GLOBALS['egw']->acl->delete_repository('calendar','L'.$cat_id,$user); |
||
1575 | } |
||
1576 | Api\Cache::setSession('calendar','cat_rights',self::$cat_rights_cache); |
||
1577 | } |
||
1578 | } |
||
1579 | |||
1580 | /** |
||
1581 | * Check if current user has a given right on a category (if it's restricted!) |
||
1582 | * |
||
1583 | * @param int $cat_id |
||
1584 | * @return boolean false=access denied because of cat acl, true access granted because of cat acl, |
||
1585 | * null = cat has no acl |
||
1586 | */ |
||
1587 | public static function has_cat_right($right,$cat_id,$user) |
||
1588 | { |
||
1589 | static $cache=null; |
||
1590 | |||
1591 | if (!isset($cache[$cat_id])) |
||
1592 | { |
||
1593 | $all = $own = 0; |
||
1594 | $cat_rights = self::get_cat_rights($cat_id); |
||
1595 | if (!is_null($cat_rights)) |
||
1596 | { |
||
1597 | static $memberships=null; |
||
1598 | if (is_null($memberships)) |
||
1599 | { |
||
1600 | $memberships = $GLOBALS['egw']->accounts->memberships($user,true); |
||
1601 | $memberships[] = $user; |
||
1602 | } |
||
1603 | foreach($cat_rights as $uid => $value) |
||
1604 | { |
||
1605 | $all |= $value; |
||
1606 | if (in_array($uid,$memberships)) $own |= $value; |
||
1607 | } |
||
1608 | } |
||
1609 | foreach(array(self::CAT_ACL_ADD,self::CAT_ACL_STATUS) as $mask) |
||
1610 | { |
||
1611 | $cache[$cat_id][$mask] = !($all & $mask) ? null : !!($own & $mask); |
||
1612 | } |
||
1613 | } |
||
1614 | //echo "<p>".__METHOD__."($right,$cat_id) all=$all, own=$own returning ".array2string($cache[$cat_id][$right])."</p>\n"; |
||
1615 | return $cache[$cat_id][$right]; |
||
1616 | } |
||
1617 | |||
1618 | /** |
||
1619 | * set the status of one participant for a given recurrence or for all recurrences since now (includes recur_date=0) |
||
1620 | * |
||
1621 | * @param int|array $event event-array or id of the event |
||
1622 | * @param string|int $uid account_id or 1-char type-identifer plus id (eg. c15 for addressbook entry #15) |
||
1623 | * @param int|char $status numeric status (defines) or 1-char code: 'R', 'U', 'T' or 'A' |
||
1624 | * @param int $recur_date =0 date to change, or 0 = all since now |
||
1625 | * @param boolean $ignore_acl =false do not check the permisions for the $uid, if true |
||
1626 | * @param boolean $updateTS =true update the content history of the event |
||
1627 | * DEPRECATED: we allways (have to) update timestamp, as they are required for sync! |
||
1628 | * @param boolean $skip_notification =false true: do not send notification messages |
||
1629 | * @return int number of changed recurrences |
||
1630 | */ |
||
1631 | function set_status($event,$uid,$status,$recur_date=0,$ignore_acl=false,$updateTS=true,$skip_notification=false) |
||
1632 | { |
||
1633 | unset($updateTS); |
||
1634 | |||
1635 | $cal_id = is_array($event) ? $event['id'] : $event; |
||
1636 | //echo "<p>calendar_boupdate::set_status($cal_id,$uid,$status,$recur_date)</p>\n"; |
||
1637 | if (!$cal_id || (!$ignore_acl && !$this->check_status_perms($uid,$event))) |
||
1638 | { |
||
1639 | return false; |
||
1640 | } |
||
1641 | $quantity = $role = null; |
||
1642 | calendar_so::split_status($status, $quantity, $role); |
||
1643 | if ($this->log) |
||
1644 | { |
||
1645 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
1646 | "($cal_id, $uid, $status, $recur_date)\n",3,$this->logfile); |
||
1647 | } |
||
1648 | $old_event = $this->read($cal_id, $recur_date, $ignore_acl, 'server'); |
||
1649 | if (($Ok = $this->so->set_status($cal_id,is_numeric($uid)?'u':$uid[0], |
||
1650 | is_numeric($uid)?$uid:substr($uid,1),$status, |
||
1651 | $recur_date?$this->date2ts($recur_date,true):0,$role))) |
||
1652 | { |
||
1653 | if ($status == 'R') // remove alarms belonging to rejected participants |
||
1654 | { |
||
1655 | foreach(is_array($event) && isset($event['alarm']) ? $event['alarm'] : $old_event['alarm'] as $id => $alarm) |
||
1656 | { |
||
1657 | if ((string)$alarm['owner'] === (string)$uid) |
||
1658 | { |
||
1659 | $this->so->delete_alarm($id); |
||
1660 | //error_log(__LINE__.': '.__METHOD__."(".array2string($event).", '$uid', '$status', ...) deleting alarm=".array2string($alarm).", $status=".array2string($alarm)); |
||
1661 | } |
||
1662 | } |
||
1663 | } |
||
1664 | |||
1665 | static $status2msg = array( |
||
1666 | 'R' => MSG_REJECTED, |
||
1667 | 'T' => MSG_TENTATIVE, |
||
1668 | 'A' => MSG_ACCEPTED, |
||
1669 | 'D' => MSG_DELEGATED, |
||
1670 | ); |
||
1671 | // Reset cached event |
||
1672 | static::$cached_event = array(); |
||
1673 | |||
1674 | if (isset($status2msg[$status]) && !$skip_notification) |
||
1675 | { |
||
1676 | if (!is_array($event)) $event = $this->read($cal_id); |
||
1677 | if (isset($recur_date)) $event = $this->read($event['id'],$recur_date); //re-read the actually edited recurring event |
||
1678 | $user_id = is_numeric($uid) ? (int)$uid : $uid; |
||
1679 | $this->send_update($status2msg[$status],$event['participants'],$event, null, $user_id); |
||
1680 | } |
||
1681 | |||
1682 | // Update history |
||
1683 | $event = $this->read($cal_id, $recur_date, $ignore_acl, 'server'); |
||
1684 | $tracking = new calendar_tracking($this); |
||
1685 | $tracking->track($event, $old_event); |
||
1686 | |||
1687 | } |
||
1688 | return $Ok; |
||
1689 | } |
||
1690 | |||
1691 | /** |
||
1692 | * update the status of all participant for a given recurrence or for all recurrences since now (includes recur_date=0) |
||
1693 | * |
||
1694 | * @param array $new_event event-array with the new stati |
||
1695 | * @param array $old_event event-array with the old stati |
||
1696 | * @param int $recur_date =0 date to change, or 0 = all since now |
||
1697 | * @param boolean $skip_notification Do not send notifications. Parameter passed on to set_status(). |
||
1698 | */ |
||
1699 | function update_status($new_event, $old_event , $recur_date=0, $skip_notification=false) |
||
1700 | { |
||
1701 | if (!isset($new_event['participants'])) return; |
||
1702 | |||
1703 | // check the old list against the new list |
||
1704 | foreach ($old_event['participants'] as $userid => $status) |
||
1705 | { |
||
1706 | if (!isset($new_event['participants'][$userid])){ |
||
1707 | // Attendee will be deleted this way |
||
1708 | $new_event['participants'][$userid] = 'G'; |
||
1709 | } |
||
1710 | elseif ($new_event['participants'][$userid] == $status) |
||
1711 | { |
||
1712 | // Same status -- nothing to do. |
||
1713 | unset($new_event['participants'][$userid]); |
||
1714 | } |
||
1715 | } |
||
1716 | // write the changes |
||
1717 | foreach ($new_event['participants'] as $userid => $status) |
||
1718 | { |
||
1719 | $this->set_status($old_event, $userid, $status, $recur_date, true, false,$skip_notification); |
||
1720 | } |
||
1721 | } |
||
1722 | |||
1723 | /** |
||
1724 | * deletes an event |
||
1725 | * |
||
1726 | * @param int $cal_id id of the event to delete |
||
1727 | * @param int $recur_date =0 if a single event from a series should be deleted, its date |
||
1728 | * @param boolean $ignore_acl =false true for no ACL check, default do ACL check |
||
1729 | * @param boolean $skip_notification =false |
||
1730 | * @param boolean $delete_exceptions =true true: delete, false: keep exceptions (with new UID) |
||
1731 | * @param int &$exceptions_kept=null on return number of kept exceptions |
||
1732 | * @return boolean true on success, false on error (usually permission denied) |
||
1733 | */ |
||
1734 | function delete($cal_id, $recur_date=0, $ignore_acl=false, $skip_notification=false, |
||
1735 | $delete_exceptions=true, &$exceptions_kept=null) |
||
1736 | { |
||
1737 | //error_log(__METHOD__."(cal_id=$cal_id, recur_date=$recur_date, ignore_acl=$ignore_acl, skip_notifications=$skip_notification)"); |
||
1738 | if (!($event = $this->read($cal_id,$recur_date)) || |
||
1739 | !$ignore_acl && !$this->check_perms(Acl::DELETE,$event)) |
||
1740 | { |
||
1741 | return false; |
||
1742 | } |
||
1743 | |||
1744 | // Don't send notification if the event has already been deleted |
||
1745 | if(!$event['deleted'] && !$skip_notification) |
||
1746 | { |
||
1747 | $this->send_update(MSG_DELETED,$event['participants'],$event); |
||
1748 | } |
||
1749 | |||
1750 | if (!$recur_date || $event['recur_type'] == MCAL_RECUR_NONE) |
||
1751 | { |
||
1752 | $config = Api\Config::read('phpgwapi'); |
||
1753 | if(!$config['calendar_delete_history'] || $event['deleted']) |
||
1754 | { |
||
1755 | $this->so->delete($cal_id); |
||
1756 | |||
1757 | // delete all links to the event |
||
1758 | Link::unlink(0,'calendar',$cal_id); |
||
1759 | } |
||
1760 | elseif ($config['calendar_delete_history']) |
||
1761 | { |
||
1762 | // mark all links to the event as deleted, but keep them |
||
1763 | Link::unlink(0,'calendar',$cal_id,'','','',true); |
||
1764 | |||
1765 | $event['deleted'] = $this->now; |
||
1766 | $this->save($event, $ignore_acl); |
||
1767 | // Actually delete alarms |
||
1768 | if (isset($event['alarm']) && is_array($event['alarm'])) |
||
1769 | { |
||
1770 | foreach($event['alarm'] as $id => $alarm) |
||
1771 | { |
||
1772 | $this->delete_alarm($id); |
||
1773 | } |
||
1774 | } |
||
1775 | } |
||
1776 | |||
1777 | // delete or keep (with new uid) exceptions of a recurring event |
||
1778 | if ($event['recur_type'] != MCAL_RECUR_NONE) |
||
1779 | { |
||
1780 | $exceptions_kept = 0; |
||
1781 | foreach ($this->so->get_related($event['uid']) as $id) |
||
1782 | { |
||
1783 | if ($delete_exceptions) |
||
1784 | { |
||
1785 | $this->delete($id, 0, $ignore_acl, true); |
||
1786 | } |
||
1787 | else |
||
1788 | { |
||
1789 | if (!($exception = $this->read($id))) continue; |
||
1790 | $exception['uid'] = Api\CalDAV::generate_uid('calendar', $id); |
||
1791 | $exception['reference'] = $exception['recurrence'] = 0; |
||
1792 | $this->update($exception, true, true, false, true, $msg=null, true); |
||
1793 | ++$exceptions_kept; |
||
1794 | } |
||
1795 | } |
||
1796 | } |
||
1797 | } |
||
1798 | else // delete an exception |
||
1799 | { |
||
1800 | // check if deleted recurrance has alarms (because it's the next recurrance) --> move it to next recurrance |
||
1801 | if ($event['alarm']) |
||
1802 | { |
||
1803 | $next_recurrance = null; |
||
1804 | foreach($event['alarm'] as &$alarm) |
||
1805 | { |
||
1806 | if (($alarm['time'] == $recur_date) || ($alarm['time']+$alarm['offset'] == $recur_date)) |
||
1807 | { |
||
1808 | //error_log(__METHOD__.__LINE__.'->'.array2string($recur_date)); |
||
1809 | //error_log(__METHOD__.__LINE__.array2string($event)); |
||
1810 | if (is_null($next_recurrance)) |
||
1811 | { |
||
1812 | $checkdate = $recur_date; |
||
1813 | //if ($alarm['time']+$alarm['offset'] == $recur_date) $checkdate = $recur_date + $alarm['offset']; |
||
1814 | if (($e = $this->read($cal_id,$checkdate+1))) |
||
1815 | { |
||
1816 | $next_recurrance = $this->date2ts($e['start']); |
||
1817 | } |
||
1818 | } |
||
1819 | $alarm['time'] = $this->date2ts($next_recurrance, true); // user to server-time |
||
1820 | $alarm['cal_id'] = $cal_id; |
||
1821 | unset($alarm['times']); |
||
1822 | unset($alarm['next']); |
||
1823 | $this->so->save_alarm($event['id'], $alarm); |
||
1824 | } |
||
1825 | } |
||
1826 | } |
||
1827 | // need to read series master, as otherwise existing exceptions will be lost! |
||
1828 | $recur_date = $this->date2ts($event['start']); |
||
1829 | //if ($event['alarm']) $alarmbuffer = $event['alarm']; |
||
1830 | $event = $this->read($cal_id); |
||
1831 | //if (isset($alarmbuffer)) $event['alarm'] = $alarmbuffer; |
||
1832 | $event['recur_exception'][] = $recur_date; |
||
1833 | $this->save($event);// updates the content-history |
||
1834 | } |
||
1835 | if ($event['reference']) |
||
1836 | { |
||
1837 | // evtl. delete recur_exception $event['recurrence'] from event with cal_id=$event['reference'] |
||
1838 | } |
||
1839 | return true; |
||
1840 | } |
||
1841 | |||
1842 | /** |
||
1843 | * helper for send_update and get_update_message |
||
1844 | * @internal |
||
1845 | * @param array $event |
||
1846 | * @param string $action |
||
1847 | * @param array $event_arr |
||
1848 | * @param array $disinvited |
||
1849 | * @return array |
||
1850 | */ |
||
1851 | function _get_event_details($event,$action,&$event_arr,$disinvited=array()) |
||
1852 | { |
||
1853 | $details = array( // event-details for the notify-msg |
||
1854 | 'id' => $event['id'], |
||
1855 | 'action' => lang($action), |
||
1856 | ); |
||
1857 | $event_arr = $this->event2array($event); |
||
1858 | foreach($event_arr as $key => $val) |
||
1859 | { |
||
1860 | if ($key == 'recur_type') $key = 'repetition'; |
||
1861 | $details[$key] = $val['data']; |
||
1862 | } |
||
1863 | $details['participants'] = $details['participants'] ? implode("\n",$details['participants']) : ''; |
||
1864 | |||
1865 | $event_arr['link']['field'] = lang('URL'); |
||
1866 | $eventStart_arr = $this->date2array($event['start']); // give this as 'date' to the link to pick the right recurrence for the participants state |
||
1867 | $link = $GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit&cal_id='.$event['id'].'&date='.$eventStart_arr['full'].'&no_popup=1&ajax=true'; |
||
1868 | // if url is only a path, try guessing the rest ;-) |
||
1869 | if ($link[0] == '/') $link = Api\Framework::getUrl($link); |
||
1870 | $event_arr['link']['data'] = $details['link'] = $link; |
||
1871 | |||
1872 | /* this is needed for notification-app |
||
1873 | * notification-app creates the link individual for |
||
1874 | * every user, so we must provide a neutral link-style |
||
1875 | * if calendar implements tracking in near future, this part can be deleted |
||
1876 | */ |
||
1877 | $link_arr = array(); |
||
1878 | $link_arr['text'] = $event['title']; |
||
1879 | $link_arr['view'] = array( 'menuaction' => 'calendar.calendar_uiforms.edit', |
||
1880 | 'cal_id' => $event['id'], |
||
1881 | 'date' => $eventStart_arr['full'], |
||
1882 | 'ajax' => true |
||
1883 | ); |
||
1884 | $link_arr['popup'] = '750x400'; |
||
1885 | $details['link_arr'] = $link_arr; |
||
1886 | |||
1887 | $dis = array(); |
||
1888 | foreach($disinvited as $uid) |
||
1889 | { |
||
1890 | $dis[] = $this->participant_name($uid); |
||
1891 | } |
||
1892 | $details['disinvited'] = implode(', ',$dis); |
||
1893 | return $details; |
||
1894 | } |
||
1895 | |||
1896 | /** |
||
1897 | * create array with name, translated name and readable content of each attributes of an event |
||
1898 | * |
||
1899 | * old function, so far only used by send_update (therefor it's in bocalupdate and not bocal) |
||
1900 | * |
||
1901 | * @param array $event event to use |
||
1902 | * @returns array of attributes with fieldname as key and array with the 'field'=translated name 'data' = readable content (for participants this is an array !) |
||
1903 | */ |
||
1904 | function event2array($event) |
||
1995 | } |
||
1996 | |||
1997 | /** |
||
1998 | * log all updates to a file |
||
1999 | * |
||
2000 | * @param array $event2save event-data before calling save |
||
2001 | * @param array $event_saved event-data read back from the DB |
||
2002 | * @param array $old_event =null event-data in the DB before calling save |
||
2003 | * @param string $type ='update' |
||
2004 | */ |
||
2005 | function log2file($event2save,$event_saved,$old_event=null,$type='update') |
||
2006 | { |
||
2007 | if (!($f = fopen($this->log_file,'a'))) |
||
2008 | { |
||
2009 | echo "<p>error opening '$this->log_file' !!!</p>\n"; |
||
2010 | return false; |
||
2011 | } |
||
2012 | fwrite($f,$type.': '.Api\Accounts::username($this->user).': '.date('r')."\n"); |
||
2013 | fwrite($f,"Time: time to save / saved time read back / old time before save\n"); |
||
2014 | foreach(array('start','end') as $name) |
||
2015 | { |
||
2016 | fwrite($f,$name.': '.(isset($event2save[$name]) ? $this->format_date($event2save[$name]) : 'not set').' / '. |
||
2017 | $this->format_date($event_saved[$name]) .' / '. |
||
2018 | (is_null($old_event) ? 'no old event' : $this->format_date($old_event[$name]))."\n"); |
||
2019 | } |
||
2020 | foreach(array('event2save','event_saved','old_event') as $name) |
||
2021 | { |
||
2022 | fwrite($f,$name.' = '.print_r($$name,true)); |
||
2023 | } |
||
2024 | fwrite($f,"\n"); |
||
2025 | fclose($f); |
||
2026 | |||
2027 | return true; |
||
2028 | } |
||
2029 | |||
2030 | /** |
||
2031 | * Check alarms and move them if needed |
||
2032 | * |
||
2033 | * Used when the start time has changed, and alarms need to be updated |
||
2034 | * |
||
2035 | * @param array $event |
||
2036 | * @param array $old_event |
||
2037 | * @param Api\DateTime|int|null $instance_date For recurring events, this is the date we |
||
2038 | * are dealing with |
||
2039 | */ |
||
2040 | function check_move_alarms(Array &$event, Array $old_event = null, $instance_date = null) |
||
2041 | { |
||
2042 | if ($old_event !== null && $event['start'] == $old_event['start']) return; |
||
2043 | |||
2044 | $time = new Api\DateTime($event['start']); |
||
2045 | if(!is_array($event['alarm'])) |
||
2046 | { |
||
2047 | $event['alarm'] = $this->so->read_alarms($event['id']); |
||
2048 | } |
||
2049 | |||
2050 | if (is_object($instance_date)) |
||
2051 | { |
||
2052 | if (!is_a($instance_date, 'EGroupware\\Api\\DateTime')) |
||
2053 | { |
||
2054 | throw new Api\Exception\WrongParameter('$instance_date must be integer or Api\DateTime!'); |
||
2055 | } |
||
2056 | $instance_date = $instance_date->format('ts'); |
||
2057 | } |
||
2058 | |||
2059 | foreach($event['alarm'] as &$alarm) |
||
2060 | { |
||
2061 | if($event['recur_type'] != MCAL_RECUR_NONE && $instance_date) |
||
2062 | { |
||
2063 | calendar_so::shift_alarm($event, $alarm, $instance_date); |
||
2064 | } |
||
2065 | else if ($alarm['time'] !== $time->format('ts') - $alarm['offset']) |
||
2066 | { |
||
2067 | $alarm['time'] = $time->format('ts') - $alarm['offset']; |
||
2068 | $this->save_alarm($event['id'], $alarm); |
||
2069 | } |
||
2070 | } |
||
2071 | } |
||
2072 | |||
2073 | /** |
||
2074 | * saves a new or updated alarm |
||
2075 | * |
||
2076 | * @param int $cal_id Id of the calendar-entry |
||
2077 | * @param array $alarm array with fields: text, owner, enabled, .. |
||
2078 | * @param boolean $update_modified =true call update modified, default true |
||
2079 | * @return string id of the alarm, or false on error (eg. no perms) |
||
2080 | */ |
||
2081 | function save_alarm($cal_id, $alarm, $update_modified=true) |
||
2091 | } |
||
2092 | |||
2093 | /** |
||
2094 | * delete one alarms identified by its id |
||
2095 | * |
||
2096 | * @param string $id alarm-id is a string of 'cal:'.$cal_id.':'.$alarm_nr, it is used as the job-id too |
||
2097 | * @return int number of alarms deleted, false on error (eg. no perms) |
||
2098 | */ |
||
2099 | function delete_alarm($id) |
||
2100 | { |
||
2101 | list(,$cal_id) = explode(':',$id); |
||
2102 | |||
2103 | if (!($alarm = $this->so->read_alarm($id)) || !$cal_id || !$this->check_perms(Acl::EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0)) |
||
2104 | { |
||
2105 | return false; // no rights to delete the alarm |
||
2106 | } |
||
2107 | |||
2108 | return $this->so->delete_alarm($id); |
||
2109 | } |
||
2110 | |||
2111 | /** |
||
2112 | * Find existing categories in database by name or add categories that do not exist yet |
||
2113 | * currently used for ical/sif import |
||
2114 | * |
||
2115 | * @param array $catname_list names of the categories which should be found or added |
||
2116 | * @param int|array $old_event =null match against existing event and expand the returned category ids |
||
2117 | * by the ones the user normally does not see due to category permissions - used to preserve categories |
||
2118 | * @return array category ids (found, added and preserved categories) |
||
2119 | */ |
||
2120 | function find_or_add_categories($catname_list, $old_event=null) |
||
2121 | { |
||
2122 | if (is_array($old_event) || $old_event > 0) |
||
2123 | { |
||
2124 | // preserve categories without users read access |
||
2125 | if (!is_array($old_event)) $old_event = $this->read($old_event); |
||
2126 | $old_categories = explode(',',$old_event['category']); |
||
2127 | $old_cats_preserve = array(); |
||
2128 | if (is_array($old_categories) && count($old_categories) > 0) |
||
2129 | { |
||
2130 | foreach ($old_categories as $cat_id) |
||
2131 | { |
||
2132 | if (!$this->categories->check_perms(Acl::READ, $cat_id)) |
||
2133 | { |
||
2134 | $old_cats_preserve[] = $cat_id; |
||
2135 | } |
||
2136 | } |
||
2137 | } |
||
2138 | } |
||
2139 | |||
2140 | $cat_id_list = array(); |
||
2141 | foreach ((array)$catname_list as $cat_name) |
||
2142 | { |
||
2143 | $cat_name = trim($cat_name); |
||
2144 | $cat_id = $this->categories->name2id($cat_name, 'X-'); |
||
2145 | |||
2146 | if (!$cat_id) |
||
2147 | { |
||
2148 | // some SyncML clients (mostly phones) add an X- to the category names |
||
2149 | if (strncmp($cat_name, 'X-', 2) == 0) |
||
2150 | { |
||
2151 | $cat_name = substr($cat_name, 2); |
||
2152 | } |
||
2153 | $cat_id = $this->categories->add(array('name' => $cat_name, 'descr' => $cat_name, 'access' => 'private')); |
||
2154 | } |
||
2155 | |||
2156 | if ($cat_id) |
||
2157 | { |
||
2158 | $cat_id_list[] = $cat_id; |
||
2159 | } |
||
2160 | } |
||
2161 | |||
2162 | if (is_array($old_cats_preserve) && count($old_cats_preserve) > 0) |
||
2163 | { |
||
2164 | $cat_id_list = array_merge($cat_id_list, $old_cats_preserve); |
||
2165 | } |
||
2166 | |||
2167 | if (count($cat_id_list) > 1) |
||
2168 | { |
||
2169 | $cat_id_list = array_unique($cat_id_list); |
||
2170 | sort($cat_id_list, SORT_NUMERIC); |
||
2171 | } |
||
2172 | |||
2173 | return $cat_id_list; |
||
2174 | } |
||
2175 | |||
2176 | function get_categories($cat_id_list) |
||
2177 | { |
||
2178 | if (!is_array($cat_id_list)) |
||
2179 | { |
||
2180 | $cat_id_list = explode(',',$cat_id_list); |
||
2181 | } |
||
2182 | $cat_list = array(); |
||
2183 | foreach ($cat_id_list as $cat_id) |
||
2184 | { |
||
2185 | if ($cat_id && $this->categories->check_perms(Acl::READ, $cat_id) && |
||
2186 | ($cat_name = $this->categories->id2name($cat_id)) && $cat_name != '--') |
||
2187 | { |
||
2188 | $cat_list[] = $cat_name; |
||
2189 | } |
||
2190 | } |
||
2191 | |||
2192 | return $cat_list; |
||
2193 | } |
||
2194 | |||
2195 | /** |
||
2196 | * Try to find a matching db entry |
||
2197 | * |
||
2198 | * @param array $event the vCalendar data we try to find |
||
2199 | * @param string filter='exact' exact -> find the matching entry |
||
2200 | * check -> check (consitency) for identical matches |
||
2201 | * relax -> be more tolerant |
||
2202 | * master -> try to find a releated series master |
||
2203 | * @return array calendar_ids of matching entries |
||
2204 | */ |
||
2205 | function find_event($event, $filter='exact') |
||
2206 | { |
||
2207 | $matchingEvents = array(); |
||
2208 | $query = array(); |
||
2209 | |||
2210 | if ($this->log) |
||
2211 | { |
||
2212 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2213 | "($filter)[EVENT]:" . array2string($event)."\n",3,$this->logfile); |
||
2214 | } |
||
2215 | |||
2216 | if (!isset($event['recurrence'])) $event['recurrence'] = 0; |
||
2217 | |||
2218 | if ($filter == 'master') |
||
2219 | { |
||
2220 | $query[] = 'recur_type!='. MCAL_RECUR_NONE; |
||
2221 | $query['cal_recurrence'] = 0; |
||
2222 | } |
||
2223 | elseif ($filter == 'exact') |
||
2224 | { |
||
2225 | if ($event['recur_type'] != MCAL_RECUR_NONE) |
||
2226 | { |
||
2227 | $query[] = 'recur_type='.$event['recur_type']; |
||
2228 | } |
||
2229 | else |
||
2230 | { |
||
2231 | $query[] = 'recur_type IS NULL'; |
||
2232 | } |
||
2233 | $query['cal_recurrence'] = $event['recurrence']; |
||
2234 | } |
||
2235 | |||
2236 | if ($event['id']) |
||
2237 | { |
||
2238 | if ($this->log) |
||
2239 | { |
||
2240 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2241 | '(' . $event['id'] . ")[EventID]\n",3,$this->logfile); |
||
2242 | } |
||
2243 | if (($egwEvent = $this->read($event['id'], 0, false, 'server'))) |
||
2244 | { |
||
2245 | if ($this->log) |
||
2246 | { |
||
2247 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2248 | '()[FOUND]:' . array2string($egwEvent)."\n",3,$this->logfile); |
||
2249 | } |
||
2250 | if ($egwEvent['recur_type'] != MCAL_RECUR_NONE && |
||
2251 | (empty($event['uid']) || $event['uid'] == $egwEvent['uid'])) |
||
2252 | { |
||
2253 | if ($filter == 'master') |
||
2254 | { |
||
2255 | $matchingEvents[] = $egwEvent['id']; // we found the master |
||
2256 | } |
||
2257 | if ($event['recur_type'] == $egwEvent['recur_type']) |
||
2258 | { |
||
2259 | $matchingEvents[] = $egwEvent['id']; // we found the event |
||
2260 | } |
||
2261 | elseif ($event['recur_type'] == MCAL_RECUR_NONE && |
||
2262 | $event['recurrence'] != 0) |
||
2263 | { |
||
2264 | $exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']); |
||
2265 | if (in_array($event['recurrence'], $exceptions)) |
||
2266 | { |
||
2267 | $matchingEvents[] = $egwEvent['id'] . ':' . (int)$event['recurrence']; |
||
2268 | } |
||
2269 | } |
||
2270 | } elseif ($filter != 'master' && ($filter == 'exact' || |
||
2271 | $event['recur_type'] == $egwEvent['recur_type'] && |
||
2272 | strpos($egwEvent['title'], $event['title']) === 0)) |
||
2273 | { |
||
2274 | $matchingEvents[] = $egwEvent['id']; // we found the event |
||
2275 | } |
||
2276 | } |
||
2277 | if (!empty($matchingEvents) || $filter == 'exact') return $matchingEvents; |
||
2278 | } |
||
2279 | unset($event['id']); |
||
2280 | |||
2281 | // No chance to find a master without [U]ID |
||
2282 | if ($filter == 'master' && empty($event['uid'])) return $matchingEvents; |
||
2283 | |||
2284 | // only query calendars of users, we have READ-grants from |
||
2285 | $users = array(); |
||
2286 | foreach(array_keys($this->grants) as $user) |
||
2287 | { |
||
2288 | $user = trim($user); |
||
2289 | if ($this->check_perms(Acl::READ|self::ACL_READ_FOR_PARTICIPANTS|self::ACL_FREEBUSY,0,$user)) |
||
2290 | { |
||
2291 | if ($user && !in_array($user,$users)) // already added? |
||
2292 | { |
||
2293 | $users[] = $user; |
||
2294 | } |
||
2295 | } |
||
2296 | elseif ($GLOBALS['egw']->accounts->get_type($user) != 'g') |
||
2297 | { |
||
2298 | continue; // for non-groups (eg. users), we stop here if we have no read-rights |
||
2299 | } |
||
2300 | // the further code is only for real users |
||
2301 | if (!is_numeric($user)) continue; |
||
2302 | |||
2303 | // for groups we have to include the members |
||
2304 | if ($GLOBALS['egw']->accounts->get_type($user) == 'g') |
||
2305 | { |
||
2306 | $members = $GLOBALS['egw']->accounts->members($user, true); |
||
2307 | if (is_array($members)) |
||
2308 | { |
||
2309 | foreach($members as $member) |
||
2310 | { |
||
2311 | // use only members which gave the user a read-grant |
||
2312 | if (!in_array($member, $users) && |
||
2313 | $this->check_perms(Acl::READ|self::ACL_FREEBUSY, 0, $member)) |
||
2314 | { |
||
2315 | $users[] = $member; |
||
2316 | } |
||
2317 | } |
||
2318 | } |
||
2319 | } |
||
2320 | else // for users we have to include all the memberships, to get the group-events |
||
2321 | { |
||
2322 | $memberships = $GLOBALS['egw']->accounts->memberships($user, true); |
||
2323 | if (is_array($memberships)) |
||
2324 | { |
||
2325 | foreach($memberships as $group) |
||
2326 | { |
||
2327 | if (!in_array($group, $users)) |
||
2328 | { |
||
2329 | $users[] = $group; |
||
2330 | } |
||
2331 | } |
||
2332 | } |
||
2333 | } |
||
2334 | } |
||
2335 | |||
2336 | if ($filter != 'master' && ($filter != 'exact' || empty($event['uid']))) |
||
2337 | { |
||
2338 | if (!empty($event['whole_day'])) |
||
2339 | { |
||
2340 | if ($filter == 'relax') |
||
2341 | { |
||
2342 | $delta = 1800; |
||
2343 | } |
||
2344 | else |
||
2345 | { |
||
2346 | $delta = 60; |
||
2347 | } |
||
2348 | |||
2349 | // check length with some tolerance |
||
2350 | $length = $event['end'] - $event['start'] - $delta; |
||
2351 | $query[] = ('(cal_end-cal_start)>' . $length); |
||
2352 | $length += 2 * $delta; |
||
2353 | $query[] = ('(cal_end-cal_start)<' . $length); |
||
2354 | $query[] = ('cal_start>' . ($event['start'] - 86400)); |
||
2355 | $query[] = ('cal_start<' . ($event['start'] + 86400)); |
||
2356 | } |
||
2357 | elseif (isset($event['start'])) |
||
2358 | { |
||
2359 | if ($filter == 'relax') |
||
2360 | { |
||
2361 | $query[] = ('cal_start>' . ($event['start'] - 3600)); |
||
2362 | $query[] = ('cal_start<' . ($event['start'] + 3600)); |
||
2363 | } |
||
2364 | else |
||
2365 | { |
||
2366 | // we accept a tiny tolerance |
||
2367 | $query[] = ('cal_start>' . ($event['start'] - 2)); |
||
2368 | $query[] = ('cal_start<' . ($event['start'] + 2)); |
||
2369 | } |
||
2370 | } |
||
2371 | if ($filter == 'relax') |
||
2372 | { |
||
2373 | $matchFields = array(); |
||
2374 | } |
||
2375 | else |
||
2376 | { |
||
2377 | $matchFields = array('priority', 'public'); |
||
2378 | } |
||
2379 | foreach ($matchFields as $key) |
||
2380 | { |
||
2381 | if (isset($event[$key])) $query['cal_'.$key] = $event[$key]; |
||
2382 | } |
||
2383 | } |
||
2384 | |||
2385 | if (!empty($event['uid'])) |
||
2386 | { |
||
2387 | $query['cal_uid'] = $event['uid']; |
||
2388 | if ($this->log) |
||
2389 | { |
||
2390 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2391 | '(' . $event['uid'] . ")[EventUID]\n",3,$this->logfile); |
||
2392 | } |
||
2393 | } |
||
2394 | |||
2395 | if ($this->log) |
||
2396 | { |
||
2397 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2398 | '[QUERY]: ' . array2string($query)."\n",3,$this->logfile); |
||
2399 | } |
||
2400 | if (!count($users) || !($foundEvents = |
||
2401 | $this->so->search(null, null, $users, 0, 'owner', false, 0, array('query' => $query)))) |
||
2402 | { |
||
2403 | if ($this->log) |
||
2404 | { |
||
2405 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2406 | "[NO MATCH]\n",3,$this->logfile); |
||
2407 | } |
||
2408 | return $matchingEvents; |
||
2409 | } |
||
2410 | |||
2411 | $pseudos = array(); |
||
2412 | |||
2413 | foreach($foundEvents as $egwEvent) |
||
2414 | { |
||
2415 | if ($this->log) |
||
2416 | { |
||
2417 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2418 | '[FOUND]: ' . array2string($egwEvent)."\n",3,$this->logfile); |
||
2419 | } |
||
2420 | |||
2421 | if (in_array($egwEvent['id'], $matchingEvents)) continue; |
||
2422 | |||
2423 | // convert timezone id of event to tzid (iCal id like 'Europe/Berlin') |
||
2424 | if (!$egwEvent['tz_id'] || !($egwEvent['tzid'] = calendar_timezones::id2tz($egwEvent['tz_id']))) |
||
2425 | { |
||
2426 | $egwEvent['tzid'] = Api\DateTime::$server_timezone->getName(); |
||
2427 | } |
||
2428 | if (!isset(self::$tz_cache[$egwEvent['tzid']])) |
||
2429 | { |
||
2430 | self::$tz_cache[$egwEvent['tzid']] = calendar_timezones::DateTimeZone($egwEvent['tzid']); |
||
2431 | } |
||
2432 | if (!$event['tzid']) |
||
2433 | { |
||
2434 | $event['tzid'] = Api\DateTime::$server_timezone->getName(); |
||
2435 | } |
||
2436 | if (!isset(self::$tz_cache[$event['tzid']])) |
||
2437 | { |
||
2438 | self::$tz_cache[$event['tzid']] = calendar_timezones::DateTimeZone($event['tzid']); |
||
2439 | } |
||
2440 | |||
2441 | if (!empty($event['uid'])) |
||
2442 | { |
||
2443 | if ($filter == 'master') |
||
2444 | { |
||
2445 | // We found the master |
||
2446 | $matchingEvents = array($egwEvent['id']); |
||
2447 | break; |
||
2448 | } |
||
2449 | if ($filter == 'exact') |
||
2450 | { |
||
2451 | // UID found |
||
2452 | if (empty($event['recurrence'])) |
||
2453 | { |
||
2454 | $egwstart = new Api\DateTime($egwEvent['start'], Api\DateTime::$server_timezone); |
||
2455 | $egwstart->setTimezone(self::$tz_cache[$egwEvent['tzid']]); |
||
2456 | $dtstart = new Api\DateTime($event['start'], Api\DateTime::$server_timezone); |
||
2457 | $dtstart->setTimezone(self::$tz_cache[$event['tzid']]); |
||
2458 | if ($egwEvent['recur_type'] == MCAL_RECUR_NONE && |
||
2459 | $event['recur_type'] == MCAL_RECUR_NONE || |
||
2460 | $egwEvent['recur_type'] != MCAL_RECUR_NONE && |
||
2461 | $event['recur_type'] != MCAL_RECUR_NONE) |
||
2462 | { |
||
2463 | if ($egwEvent['recur_type'] == MCAL_RECUR_NONE && |
||
2464 | $egwstart->format('Ymd') == $dtstart->format('Ymd') || |
||
2465 | $egwEvent['recur_type'] != MCAL_RECUR_NONE) |
||
2466 | { |
||
2467 | // We found an exact match |
||
2468 | $matchingEvents = array($egwEvent['id']); |
||
2469 | break; |
||
2470 | } |
||
2471 | else |
||
2472 | { |
||
2473 | $matchingEvents[] = $egwEvent['id']; |
||
2474 | } |
||
2475 | } |
||
2476 | continue; |
||
2477 | } |
||
2478 | elseif ($egwEvent['recurrence'] == $event['recurrence']) |
||
2479 | { |
||
2480 | // We found an exact match |
||
2481 | $matchingEvents = array($egwEvent['id']); |
||
2482 | break; |
||
2483 | } |
||
2484 | if ($egwEvent['recur_type'] != MCAL_RECUR_NONE && |
||
2485 | $event['recur_type'] == MCAL_RECUR_NONE && |
||
2486 | !$egwEvent['recurrence'] && $event['recurrence']) |
||
2487 | { |
||
2488 | $exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['tzid']); |
||
2489 | if (in_array($event['recurrence'], $exceptions)) |
||
2490 | { |
||
2491 | // We found a pseudo exception |
||
2492 | $matchingEvents = array($egwEvent['id'] . ':' . (int)$event['recurrence']); |
||
2493 | break; |
||
2494 | } |
||
2495 | } |
||
2496 | continue; |
||
2497 | } |
||
2498 | } |
||
2499 | |||
2500 | // check times |
||
2501 | if ($filter != 'relax') |
||
2502 | { |
||
2503 | if (empty($event['whole_day'])) |
||
2504 | { |
||
2505 | if (abs($event['end'] - $egwEvent['end']) >= 120) |
||
2506 | { |
||
2507 | if ($this->log) |
||
2508 | { |
||
2509 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2510 | "() egwEvent length does not match!\n",3,$this->logfile); |
||
2511 | } |
||
2512 | continue; |
||
2513 | } |
||
2514 | } |
||
2515 | else |
||
2516 | { |
||
2517 | if (!$this->so->isWholeDay($egwEvent)) |
||
2518 | { |
||
2519 | if ($this->log) |
||
2520 | { |
||
2521 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2522 | "() egwEvent is not a whole-day event!\n",3,$this->logfile); |
||
2523 | } |
||
2524 | continue; |
||
2525 | } |
||
2526 | } |
||
2527 | } |
||
2528 | |||
2529 | // check for real match |
||
2530 | $matchFields = array('title', 'description'); |
||
2531 | if ($filter != 'relax') |
||
2532 | { |
||
2533 | $matchFields[] = 'location'; |
||
2534 | } |
||
2535 | foreach ($matchFields as $key) |
||
2536 | { |
||
2537 | if (!empty($event[$key]) && (empty($egwEvent[$key]) |
||
2538 | || strpos(str_replace("\r\n", "\n", $egwEvent[$key]), $event[$key]) !== 0)) |
||
2539 | { |
||
2540 | if ($this->log) |
||
2541 | { |
||
2542 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2543 | "() event[$key] differ: '" . $event[$key] . |
||
2544 | "' <> '" . $egwEvent[$key] . "'\n",3,$this->logfile); |
||
2545 | } |
||
2546 | continue 2; // next foundEvent |
||
2547 | } |
||
2548 | } |
||
2549 | |||
2550 | if (is_array($event['category'])) |
||
2551 | { |
||
2552 | // check categories |
||
2553 | $egwCategories = explode(',', $egwEvent['category']); |
||
2554 | foreach ($egwCategories as $cat_id) |
||
2555 | { |
||
2556 | if ($this->categories->check_perms(Acl::READ, $cat_id) && |
||
2557 | !in_array($cat_id, $event['category'])) |
||
2558 | { |
||
2559 | if ($this->log) |
||
2560 | { |
||
2561 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2562 | "() egwEvent category $cat_id is missing!\n",3,$this->logfile); |
||
2563 | } |
||
2564 | continue 2; |
||
2565 | } |
||
2566 | } |
||
2567 | $newCategories = array_diff($event['category'], $egwCategories); |
||
2568 | if (!empty($newCategories)) |
||
2569 | { |
||
2570 | if ($this->log) |
||
2571 | { |
||
2572 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2573 | '() event has additional categories:' |
||
2574 | . array2string($newCategories)."\n",3,$this->logfile); |
||
2575 | } |
||
2576 | continue; |
||
2577 | } |
||
2578 | } |
||
2579 | |||
2580 | if ($filter != 'relax') |
||
2581 | { |
||
2582 | // check participants |
||
2583 | if (is_array($event['participants'])) |
||
2584 | { |
||
2585 | foreach ($event['participants'] as $attendee => $status) |
||
2586 | { |
||
2587 | if (!isset($egwEvent['participants'][$attendee]) && |
||
2588 | $attendee != $egwEvent['owner']) // || |
||
2589 | //(!$relax && $egw_event['participants'][$attendee] != $status)) |
||
2590 | { |
||
2591 | if ($this->log) |
||
2592 | { |
||
2593 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2594 | "() additional event['participants']: $attendee\n",3,$this->logfile); |
||
2595 | } |
||
2596 | continue 2; |
||
2597 | } |
||
2598 | else |
||
2599 | { |
||
2600 | unset($egwEvent['participants'][$attendee]); |
||
2601 | } |
||
2602 | } |
||
2603 | // ORGANIZER and Groups may be missing |
||
2604 | unset($egwEvent['participants'][$egwEvent['owner']]); |
||
2605 | foreach ($egwEvent['participants'] as $attendee => $status) |
||
2606 | { |
||
2607 | if (is_numeric($attendee) && $attendee < 0) |
||
2608 | { |
||
2609 | unset($egwEvent['participants'][$attendee]); |
||
2610 | } |
||
2611 | } |
||
2612 | if (!empty($egwEvent['participants'])) |
||
2613 | { |
||
2614 | if ($this->log) |
||
2615 | { |
||
2616 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2617 | '() missing event[participants]: ' . |
||
2618 | array2string($egwEvent['participants'])."\n",3,$this->logfile); |
||
2619 | } |
||
2620 | continue; |
||
2621 | } |
||
2622 | } |
||
2623 | } |
||
2624 | |||
2625 | if ($event['recur_type'] == MCAL_RECUR_NONE) |
||
2626 | { |
||
2627 | if ($egwEvent['recur_type'] != MCAL_RECUR_NONE) |
||
2628 | { |
||
2629 | // We found a pseudo Exception |
||
2630 | $pseudos[] = $egwEvent['id'] . ':' . $event['start']; |
||
2631 | continue; |
||
2632 | } |
||
2633 | } |
||
2634 | elseif ($filter != 'relax') |
||
2635 | { |
||
2636 | // check exceptions |
||
2637 | // $exceptions[$remote_ts] = $egw_ts |
||
2638 | $exceptions = $this->so->get_recurrence_exceptions($egwEvent, $event['$tzid'], 0, 0, 'map'); |
||
2639 | if (is_array($event['recur_exception'])) |
||
2640 | { |
||
2641 | foreach ($event['recur_exception'] as $key => $day) |
||
2642 | { |
||
2643 | if (isset($exceptions[$day])) |
||
2644 | { |
||
2645 | unset($exceptions[$day]); |
||
2646 | } |
||
2647 | else |
||
2648 | { |
||
2649 | if ($this->log) |
||
2650 | { |
||
2651 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2652 | "() additional event['recur_exception']: $day\n",3,$this->logfile); |
||
2653 | } |
||
2654 | continue 2; |
||
2655 | } |
||
2656 | } |
||
2657 | if (!empty($exceptions)) |
||
2658 | { |
||
2659 | if ($this->log) |
||
2660 | { |
||
2661 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2662 | '() missing event[recur_exception]: ' . |
||
2663 | array2string($event['recur_exception'])."\n",3,$this->logfile); |
||
2664 | } |
||
2665 | continue; |
||
2666 | } |
||
2667 | } |
||
2668 | |||
2669 | // check recurrence information |
||
2670 | foreach (array('recur_type', 'recur_interval', 'recur_enddate') as $key) |
||
2671 | { |
||
2672 | if (isset($event[$key]) |
||
2673 | && $event[$key] != $egwEvent[$key]) |
||
2674 | { |
||
2675 | if ($this->log) |
||
2676 | { |
||
2677 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2678 | "() events[$key] differ: " . $event[$key] . |
||
2679 | ' <> ' . $egwEvent[$key]."\n",3,$this->logfile); |
||
2680 | } |
||
2681 | continue 2; |
||
2682 | } |
||
2683 | } |
||
2684 | } |
||
2685 | $matchingEvents[] = $egwEvent['id']; // exact match |
||
2686 | } |
||
2687 | |||
2688 | if ($filter == 'exact' && !empty($event['uid']) && count($matchingEvents) > 1 |
||
2689 | || $filter != 'master' && !empty($egwEvent['recur_type']) && empty($event['recur_type'])) |
||
2690 | { |
||
2691 | // Unknown exception for existing series |
||
2692 | if ($this->log) |
||
2693 | { |
||
2694 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2695 | "() new exception for series found.\n",3,$this->logfile); |
||
2696 | } |
||
2697 | $matchingEvents = array(); |
||
2698 | } |
||
2699 | |||
2700 | // append pseudos as last entries |
||
2701 | $matches = array_merge($matchingEvents, $pseudos); |
||
2702 | |||
2703 | if ($this->log) |
||
2704 | { |
||
2705 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2706 | '[MATCHES]:' . array2string($matches)."\n",3,$this->logfile); |
||
2707 | } |
||
2708 | return $matches; |
||
2709 | } |
||
2710 | |||
2711 | /** |
||
2712 | * classifies an incoming event from the eGW point-of-view |
||
2713 | * |
||
2714 | * exceptions: unlike other calendar apps eGW does not create an event exception |
||
2715 | * if just the participant state changes - therefore we have to distinguish between |
||
2716 | * real exceptions and status only exceptions |
||
2717 | * |
||
2718 | * @param array $event the event to check |
||
2719 | * |
||
2720 | * @return array |
||
2721 | * type => |
||
2722 | * SINGLE a single event |
||
2723 | * SERIES-MASTER the series master |
||
2724 | * SERIES-EXCEPTION event is a real exception |
||
2725 | * SERIES-PSEUDO-EXCEPTION event is a status only exception |
||
2726 | * SERIES-EXCEPTION-PROPAGATE event was a status only exception in the past and is now a real exception |
||
2727 | * stored_event => if event already exists in the database array with event data or false |
||
2728 | * master_event => for event type SERIES-EXCEPTION, SERIES-PSEUDO-EXCEPTION or SERIES-EXCEPTION-PROPAGATE |
||
2729 | * the corresponding series master event array |
||
2730 | * NOTE: this param is false if event is of type SERIES-MASTER |
||
2731 | */ |
||
2732 | function get_event_info($event) |
||
2733 | { |
||
2734 | $type = 'SINGLE'; // default |
||
2735 | $master_event = false; //default |
||
2736 | $stored_event = false; |
||
2737 | $recurrence_event = false; |
||
2738 | $wasPseudo = false; |
||
2739 | |||
2740 | if (($foundEvents = $this->find_event($event, 'exact'))) |
||
2741 | { |
||
2742 | // We found the exact match |
||
2743 | $eventID = array_shift($foundEvents); |
||
2744 | if (strstr($eventID, ':')) |
||
2745 | { |
||
2746 | $type = 'SERIES-PSEUDO-EXCEPTION'; |
||
2747 | $wasPseudo = true; |
||
2748 | list($eventID, $date) = explode(':', $eventID); |
||
2749 | $recur_date = $this->date2usertime($date); |
||
2750 | $stored_event = $this->read($eventID, $recur_date, false, 'server'); |
||
2751 | $master_event = $this->read($eventID, 0, false, 'server'); |
||
2752 | $recurrence_event = $stored_event; |
||
2753 | } |
||
2754 | else |
||
2755 | { |
||
2756 | $stored_event = $this->read($eventID, 0, false, 'server'); |
||
2757 | } |
||
2758 | if (!empty($stored_event['uid']) && empty($event['uid'])) |
||
2759 | { |
||
2760 | $event['uid'] = $stored_event['uid']; // restore the UID if it was not delivered |
||
2761 | } |
||
2762 | } |
||
2763 | |||
2764 | if ($event['recur_type'] != MCAL_RECUR_NONE) |
||
2765 | { |
||
2766 | $type = 'SERIES-MASTER'; |
||
2767 | } |
||
2768 | |||
2769 | if ($type == 'SINGLE' && |
||
2770 | ($foundEvents = $this->find_event($event, 'master'))) |
||
2771 | { |
||
2772 | // SINGLE, SERIES-EXCEPTION OR SERIES-EXCEPTON-STATUS |
||
2773 | foreach ($foundEvents as $eventID) |
||
2774 | { |
||
2775 | // Let's try to find a related series |
||
2776 | if ($this->log) |
||
2777 | { |
||
2778 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2779 | "()[MASTER]: $eventID\n",3,$this->logfile); |
||
2780 | } |
||
2781 | $type = 'SERIES-EXCEPTION'; |
||
2782 | if (($master_event = $this->read($eventID, 0, false, 'server'))) |
||
2783 | { |
||
2784 | if (isset($stored_event['id']) && |
||
2785 | $master_event['id'] != $stored_event['id']) |
||
2786 | { |
||
2787 | break; // this is an existing exception |
||
2788 | } |
||
2789 | elseif (isset($event['recurrence']) && |
||
2790 | in_array($event['recurrence'], $master_event['recur_exception'])) |
||
2791 | { |
||
2792 | $type = 'SERIES-PSEUDO-EXCEPTION'; // could also be a real one |
||
2793 | $recurrence_event = $master_event; |
||
2794 | $recurrence_event['start'] = $event['recurrence']; |
||
2795 | $recurrence_event['end'] -= $master_event['start'] - $event['recurrence']; |
||
2796 | break; |
||
2797 | } |
||
2798 | elseif (in_array($event['start'], $master_event['recur_exception'])) |
||
2799 | { |
||
2800 | $type='SERIES-PSEUDO-EXCEPTION'; // new pseudo exception? |
||
2801 | $recurrence_event = $master_event; |
||
2802 | $recurrence_event['start'] = $event['start']; |
||
2803 | $recurrence_event['end'] -= $master_event['start'] - $event['start']; |
||
2804 | break; |
||
2805 | } |
||
2806 | else |
||
2807 | { |
||
2808 | // try to find a suitable pseudo exception date |
||
2809 | $egw_rrule = calendar_rrule::event2rrule($master_event, false); |
||
2810 | $egw_rrule->current = clone $egw_rrule->time; |
||
2811 | while ($egw_rrule->valid()) |
||
2812 | { |
||
2813 | $occurrence = Api\DateTime::to($egw_rrule->current(), 'server'); |
||
2814 | if ($this->log) |
||
2815 | { |
||
2816 | error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. |
||
2817 | '() try occurrence ' . $egw_rrule->current() |
||
2818 | . " ($occurrence)\n",3,$this->logfile); |
||
2819 | } |
||
2820 | if ($event['start'] == $occurrence) |
||
2821 | { |
||
2822 | $type = 'SERIES-PSEUDO-EXCEPTION'; // let's try a pseudo exception |
||
2823 | $recurrence_event = $master_event; |
||
2824 | $recurrence_event['start'] = $occurrence; |
||
2825 | $recurrence_event['end'] -= $master_event['start'] - $occurrence; |
||
2826 | break 2; |
||
2827 | } |
||
2828 | if (isset($event['recurrence']) && $event['recurrence'] == $occurrence) |
||
2829 | { |
||
2830 | $type = 'SERIES-EXCEPTION-PROPAGATE'; |
||
2831 | if ($stored_event) |
||
2832 | { |
||
2833 | unset($stored_event['id']); // signal the true exception |
||
2834 | $stored_event['recur_type'] = MCAL_RECUR_NONE; |
||
2835 | } |
||
2836 | break 2; |
||
2837 | } |
||
2838 | $egw_rrule->next_no_exception(); |
||
2839 | } |
||
2840 | } |
||
2841 | } |
||
2842 | } |
||
2843 | } |
||
2844 | |||
2845 | // check pseudo exception propagation |
||
2846 | if ($recurrence_event) |
||
2847 | { |
||
2848 | // default if we cannot find a proof for a fundamental change |
||
2849 | // the recurrence_event is the master event with start and end adjusted to the recurrence |
||
2850 | // check for changed data |
||
2851 | foreach (array('start','end','uid','title','location','description', |
||
2852 | 'priority','public','special','non_blocking') as $key) |
||
2853 | { |
||
2854 | if (!empty($event[$key]) && $recurrence_event[$key] != $event[$key]) |
||
2855 | { |
||
2856 | if ($wasPseudo) |
||
2857 | { |
||
2858 | // We started with a pseudo exception |
||
2859 | $type = 'SERIES-EXCEPTION-PROPAGATE'; |
||
2860 | } |
||
2861 | else |
||
2862 | { |
||
2863 | $type = 'SERIES-EXCEPTION'; |
||
2864 | } |
||
2865 | |||
2866 | if ($stored_event) |
||
2867 | { |
||
2868 | unset($stored_event['id']); // signal the true exception |
||
2869 | $stored_event['recur_type'] = MCAL_RECUR_NONE; |
||
2870 | } |
||
2871 | break; |
||
2872 | } |
||
2873 | } |
||
2874 | // the event id here is always the id of the master event |
||
2875 | // unset it to prevent confusion of stored event and master event |
||
2876 | unset($event['id']); |
||
2877 | } |
||
2878 | |||
2879 | // check ACL |
||
2880 | if (is_array($master_event)) |
||
2881 | { |
||
2882 | $acl_edit = $this->check_perms(Acl::EDIT, $master_event['id']); |
||
2883 | } |
||
2884 | else |
||
2885 | { |
||
2886 | if (is_array($stored_event)) |
||
2887 | { |
||
2888 | $acl_edit = $this->check_perms(Acl::EDIT, $stored_event['id']); |
||
2889 | } |
||
2890 | else |
||
2891 | { |
||
2892 | $acl_edit = true; // new event |
||
2893 | } |
||
2894 | } |
||
2895 | |||
2896 | return array( |
||
2897 | 'type' => $type, |
||
2898 | 'acl_edit' => $acl_edit, |
||
2899 | 'stored_event' => $stored_event, |
||
2900 | 'master_event' => $master_event, |
||
2901 | ); |
||
2902 | } |
||
2903 | |||
2904 | /** |
||
2905 | * Translates all timestamps for a given event from server-time to user-time. |
||
2906 | * The update() and save() methods expect timestamps in user-time. |
||
2907 | * @param &$event the event we are working on |
||
2908 | * |
||
2909 | */ |
||
2910 | function server2usertime (&$event) |
||
2911 | { |
||
2912 | // we run all dates through date2usertime, to adjust to user-time |
||
2913 | foreach(array('start','end','recur_enddate','recurrence') as $ts) |
||
2914 | { |
||
2915 | // we convert here from server-time to timestamps in user-time! |
||
2916 | if (isset($event[$ts])) $event[$ts] = $event[$ts] ? $this->date2usertime($event[$ts]) : 0; |
||
2917 | } |
||
2918 | // same with the recur exceptions |
||
2919 | if (isset($event['recur_exception']) && is_array($event['recur_exception'])) |
||
2920 | { |
||
2921 | foreach($event['recur_exception'] as $n => $date) |
||
2922 | { |
||
2923 | $event['recur_exception'][$n] = $this->date2usertime($date); |
||
2924 | } |
||
2925 | } |
||
2926 | // same with the alarms |
||
2927 | if (isset($event['alarm']) && is_array($event['alarm'])) |
||
2928 | { |
||
2929 | foreach($event['alarm'] as $id => $alarm) |
||
2930 | { |
||
2931 | $event['alarm'][$id]['time'] = $this->date2usertime($alarm['time']); |
||
2932 | } |
||
2933 | } |
||
2934 | } |
||
2935 | /** |
||
2936 | * Delete events that are more than $age years old |
||
2937 | * |
||
2938 | * Purges old events from the database |
||
2939 | * |
||
2940 | * @param int|float $age How many years old the event must be before it is deleted |
||
2941 | */ |
||
2942 | function purge($age) |
||
2947 | } |
||
2948 | } |
||
2949 | |||
2950 | /** |
||
2951 | * Check to see if we need to reset the status of any of the participants |
||
2952 | * - Current user is never reset |
||
2953 | * - Other users we respect their preference |
||
2954 | * - Non-users status is always reset |
||
2955 | * |
||
2956 | * @param Array $event New event |
||
2957 | * @param Array $old_event Event before modification |
||
2958 | * |
||
2959 | * @return boolean true if any statuses were reset |
||
2960 | */ |
||
2961 | protected function check_reset_stati(&$event, $old_event) |
||
2962 | { |
||
2963 | if(!$old_event || !is_array($old_event) || $event['start'] == $old_event['start']) |
||
2964 | { |
||
2965 | return false; |
||
2966 | } |
||
2967 | |||
2968 | $status_reset = false; |
||
3006 |