| 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 |