18require_once(
'AwlCache.php');
19require_once(
'vComponent.php');
20require_once(
'vCalendar.php');
21require_once(
'WritableCollection.php');
22require_once(
'schedule-functions.php');
23include_once(
'iSchedule.php');
24include_once(
'RRule.php');
31$GLOBALS[
'tz_regex'] =
':^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Etc|Europe|Indian|Mexico|Mideast|Pacific|US)/[a-z_]+$:i';
41function rollback_on_error( $caldav_context, $user_no, $path, $message=
'', $error_no=500 ) {
42 global $c, $bad_events;
43 if ( !$message ) $message = translate(
'Database error');
44 $qry =
new AwlQuery();
45 if ( $qry->TransactionState() != 0 ) $qry->Rollback();
46 if ( $caldav_context ) {
47 if ( isset($bad_events) && isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) {
48 $bad_events[] = $message;
52 $request->DoResponse( $error_no, $message );
57 $c->messages[] = sprintf(translate(
'Status: %d, Message: %s, User: %d, Path: %s'), $error_no, $message, $user_no, $path);
72function controlRequestContainer( $username, $user_no, $path, $caldav_context, $public =
null ) {
73 global $c, $request, $bad_events;
76 if ( preg_match(
'#^(.*/)([^/]+)$#', $path, $matches ) ) {
77 $request_container = $matches[1];
81 $request_container = $path;
84 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) {
85 $bad_events = array();
91 if ( $request_container ==
"/$username/" ) {
95 dbg_error_log(
'WARN',
' Storing events directly in user\'s base folders is not recommended!');
98 $sql =
'SELECT * FROM collection WHERE dav_name = :dav_name';
99 $qry =
new AwlQuery( $sql, array(
':dav_name' => $request_container) );
100 if ( ! $qry->Exec(
'PUT',__LINE__,__FILE__) ) {
101 rollback_on_error( $caldav_context, $user_no, $path,
'Database error in: '.$sql );
103 if ( !isset($c->readonly_webdav_collections) || $c->readonly_webdav_collections ==
true ) {
104 if ( $qry->rows() == 0 ) {
105 $request->DoResponse( 405 );
109 if ( $qry->rows() == 0 ) {
110 if ( $public ==
true ) $public =
't';
else $public =
'f';
111 if ( preg_match(
'{^(.*/)([^/]+)/$}', $request_container, $matches ) ) {
112 $parent_container = $matches[1];
113 $displayname = $matches[2];
115 $sql =
'INSERT INTO collection ( user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, created, modified, publicly_readable, resourcetypes )
116VALUES( :user_no, :parent_container, :dav_name, :dav_etag, :dav_displayname, TRUE, current_timestamp, current_timestamp, :is_public::boolean, :resourcetypes )';
118 ':user_no' => $user_no,
119 ':parent_container' => $parent_container,
120 ':dav_name' => $request_container,
121 ':dav_etag' => md5($user_no. $request_container),
122 ':dav_displayname' => $displayname,
123 ':is_public' => $public,
124 ':resourcetypes' =>
'<DAV::collection/><urn:ietf:params:xml:ns:caldav:calendar/>'
126 $qry->QDo( $sql, $params );
128 else if ( isset($public) ) {
129 $collection = $qry->Fetch();
130 if ( empty($collection->is_public) ) $collection->is_public =
'f';
131 if ( $collection->is_public == ($public?
't':
'f') ) {
132 $sql =
'UPDATE collection SET publicly_readable = :is_public::boolean WHERE collection_id = :collection_id';
133 $params = array(
':is_public' => ($public?
't':
'f'),
':collection_id' => $collection->collection_id );
134 if ( ! $qry->QDo($sql,$params) ) {
135 rollback_on_error( $caldav_context, $user_no, $path,
'Database error in: '.$sql );
150function public_events_only( $user_no, $dav_name ) {
153 $sql =
'SELECT public_events_only FROM collection WHERE dav_name = :dav_name';
155 $qry =
new AwlQuery($sql, array(
':dav_name' => $dav_name) );
157 if( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() == 1 ) {
158 $collection = $qry->Fetch();
160 if ($collection->public_events_only ==
't') {
175function GetTZID( vComponent $comp ) {
176 $p = $comp->GetProperty(
'DTSTART');
177 if ( !isset($p) && $comp->GetType() ==
'VTODO' ) {
178 $p = $comp->GetProperty(
'DUE');
180 if ( !isset($p) )
return null;
181 return $p->GetParameterValue(
'TZID');
190function quote_dt_param($dt) {
191 if (
null === $dt ||
'' === $dt) {
195 return "'" . $dt .
"'";
202function handle_schedule_request( $ical ) {
203 global $c, $session, $request;
204 $resources = $ical->GetComponents(
'VTIMEZONE',
false);
206 $etag = md5 ( $request->raw_post );
207 $reply =
new XMLDocument( array(
"DAV:" =>
"",
"urn:ietf:params:xml:ns:caldav" =>
"C" ) );
208 $responses = array();
210 $attendees = $ic->GetProperties(
'ATTENDEE');
211 $wr_attendees = $ic->GetProperties(
'X-WR-ATTENDEE');
212 if ( count ( $wr_attendees ) > 0 ) {
213 dbg_error_log(
"PUT",
"Non-compliant iCal request. Using X-WR-ATTENDEE property" );
214 foreach( $wr_attendees AS $k => $v ) {
218 dbg_error_log(
"PUT",
"Attempting to deliver scheduling request for %d attendees", count($attendees) );
220 foreach( $attendees AS $k => $attendee ) {
221 $attendee_email = preg_replace(
'/^mailto:/',
'', $attendee->Value() );
222 if ( $attendee_email == $request->principal->email() ) {
223 dbg_error_log(
"PUT",
"not delivering to owner" );
226 $schedule_status = $attendee->GetParameterValue (
'SCHEDULE-STATUS' );
227 if ( $attendee->GetParameterValue (
'PARTSTAT' ) !=
'NEEDS-ACTION' || (isset($schedule_status) && preg_match (
'/^[35]\.[3-9]/', $schedule_status ) ) ) {
228 dbg_error_log(
"PUT",
"attendee %s does not need action", $attendee_email );
232 if ( isset($c->enable_auto_schedule) && !$c->enable_auto_schedule ) {
235 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'5.3;No scheduling support for user');
239 dbg_error_log(
"PUT",
"Delivering to %s", $attendee_email );
241 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$attendee_email,
'options'=> array (
'allow_by_email' =>
true ) ) );
242 if ( ! $attendee_principal->Exists() ){
243 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'5.3;No scheduling support for user');
246 $deliver_path = $attendee_principal->internal_url(
'schedule-inbox');
249 $priv = $ar->HavePrivilegeTo(
'schedule-deliver-invite' );
250 if ( ! $ar->HavePrivilegeTo(
'schedule-deliver-invite' ) ){
251 $reply =
new XMLDocument( array(
'DAV:' =>
'') );
252 $privnodes = array( $reply->href($attendee_principal->url(
'schedule-inbox')),
new XMLElement(
'privilege' ) );
254 $reply->NSElement( $privnodes[1],
'schedule-deliver-invite' );
255 $xml =
new XMLElement(
'need-privileges',
new XMLElement(
'resource', $privnodes) );
256 $xmldoc = $reply->Render(
'error',$xml);
257 $request->DoResponse( 403, $xmldoc,
'text/xml; charset="utf-8"');
261 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'1.2;Scheduling message has been delivered');
262 $ncal =
new vCalendar( array(
'METHOD' =>
'REQUEST') );
263 $ncal->AddComponent( array_merge( $ical->GetComponents(
'VEVENT',
false), array($ic) ));
264 $content = $ncal->Render();
265 $cid = $ar->GetProperty(
'collection_id');
266 dbg_error_log(
'DELIVER',
'to user: %s, to path: %s, collection: %s, from user: %s, caldata %s', $attendee_principal->user_no(), $deliver_path, $cid, $request->user_no, $content );
267 $item_etag = md5($content);
268 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->user_no, $item_etag,
269 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
270 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'1.2;Scheduling message has been delivered');
273 $ncal =
new vCalendar(array(
'METHOD' =>
'REQUEST'));
274 $ncal->AddComponent ( array_merge ( $ical->GetComponents(
'VEVENT',
false) , array ($ic) ));
275 $content = $ncal->Render();
276 $deliver_path = $request->principal->internal_url(
'schedule-inbox');
278 $item_etag = md5($content);
279 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->user_no, $item_etag,
280 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
282 header(
'ETag: "'. $etag .
'"' );
283 header(
'Schedule-Tag: "'.$etag .
'"' );
284 $request->DoResponse( 201,
'Created' );
293function handle_schedule_reply ( vCalendar $ical ) {
294 global $c, $session, $request;
295 $resources = $ical->GetComponents(
'VTIMEZONE',
false);
297 $etag = md5 ( $request->raw_post );
298 $organizer = $ical->GetOrganizer();
299 $arrayOrganizer = array($organizer);
301 if ( empty( $arrayOrganizer ) )
return false;
303 $attendees = array_merge($arrayOrganizer,$ical->GetAttendees());
304 dbg_error_log(
"PUT",
"Attempting to deliver scheduling request for %d attendees", count($attendees) );
306 foreach( $attendees AS $k => $attendee ) {
307 $attendee_email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
308 dbg_error_log(
"PUT",
"Delivering to %s", $attendee_email );
309 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$attendee_email,
'options'=> array (
'allow_by_email' =>
true ) ) );
310 $deliver_path = $attendee_principal->internal_url(
'schedule-inbox');
311 $attendee_email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
312 if ( $attendee_email == $request->principal->email ) {
313 dbg_error_log(
"PUT",
"not delivering to owner" );
317 if ( ! $ar->HavePrivilegeTo(
'schedule-deliver-reply' ) ){
318 $reply =
new XMLDocument( array(
'DAV:' =>
'') );
319 $privnodes = array( $reply->href($attendee_principal->url(
'schedule-inbox')),
new XMLElement(
'privilege' ) );
321 $reply->NSElement( $privnodes[1],
'schedule-deliver-reply' );
322 $xml =
new XMLElement(
'need-privileges',
new XMLElement(
'resource', $privnodes) );
323 $xmldoc = $reply->Render(
'error',$xml);
324 $request->DoResponse( 403, $xmldoc,
'text/xml; charset="utf-8"' );
328 $ncal =
new vCalendar( array(
'METHOD' =>
'REPLY') );
329 $ncal->AddComponent ( array_merge ( $ical->GetComponents(
'VEVENT',
false) , array ($ic) ));
330 $content = $ncal->Render();
331 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->user_no, md5($content),
332 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
334 $request->DoResponse( 201,
'Created' );
343function do_scheduling_reply( vCalendar $resource, vProperty $organizer ) {
345 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
346 $organizer_principal =
new Principal(
'email',$organizer_email );
347 if ( !$organizer_principal->Exists() ) {
348 dbg_error_log(
'PUT',
'Organizer "%s" not found - cannot perform scheduling reply.', $organizer );
351 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data FROM caldav_data JOIN calendar_item USING(dav_id) ';
352 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
353 $sql .=
'AND uid=? LIMIT 1';
354 $uids = $resource->GetPropertiesByPath(
'/VCALENDAR/*/UID');
355 if ( count($uids) == 0 ) {
356 dbg_error_log(
'PUT',
'No UID in VCALENDAR - giving up on REPLY.' );
359 $uid = $uids[0]->Value();
360 $qry =
new AwlQuery($sql,$organizer_principal->user_no(), $uid);
361 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
362 dbg_error_log(
'PUT',
'Could not find original event from organizer - giving up on REPLY.' );
365 $row = $qry->Fetch();
366 $attendees = $resource->GetAttendees();
367 foreach( $attendees AS $v ) {
368 $email = preg_replace(
'/^mailto:/i',
'', $v->Value() );
369 if ( $email == $request->principal->email() ) {
373 if ( empty($attendee) ) {
374 dbg_error_log(
'PUT',
'Could not find ATTENDEE in VEVENT - giving up on REPLY.' );
377 $schedule_original =
new vCalendar($row->caldav_data);
378 $attendee->SetParameterValue(
'SCHEDULE-STATUS',
'2.0');
379 $schedule_original->UpdateAttendeeStatus($request->principal->email(), clone($attendee) );
381 $collection_path = preg_replace(
'{/[^/]+$}',
'/', $row->dav_name );
382 $segment_name = str_replace($collection_path,
'', $row->dav_name );
384 $organizer_inbox =
new WritableCollection(array(
'path' => $organizer_principal->internal_url(
'schedule-inbox')));
386 $schedule_reply = GetItip(
new vCalendar($schedule_original->Render(
null,
true)),
'REPLY', $attendee->Value(), array(
'CUTYPE'=>
true,
'SCHEDULE-STATUS'=>
true));
388 dbg_error_log(
'PUT',
'Writing scheduling REPLY from %s to %s', $request->principal->email(), $organizer_principal->email() );
391 if ( !$organizer_calendar->Exists() ) {
392 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
393 $organizer_calendar->dav_name(), $organizer_principal->username());
397 if ( ! $organizer_inbox->HavePrivilegeTo(
'schedule-deliver-reply') ) {
400 else if ( $organizer_inbox->WriteCalendarMember($schedule_reply,
false,
false, $request->principal->username().$segment_name) !==
false ) {
402 if ( $organizer_calendar->WriteCalendarMember($schedule_original,
false,
false, $segment_name) ===
false ) {
403 dbg_error_log(
'ERROR',
'Could not write updated calendar member to %s',
404 $organizer_calendar->dav_name());
405 trace_bug(
'Failed to write scheduling resource.');
410 $schedule_request = clone($schedule_original);
411 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
413 dbg_error_log(
'PUT',
'Status for organizer <%s> set to "%s"', $organizer->Value(), $response );
414 $organizer->SetParameterValue(
'SCHEDULE-STATUS', $response );
415 $resource->UpdateOrganizerStatus($organizer);
416 $scheduling_actions =
true;
418 $calling_attendee = clone($attendee);
419 $attendees = $schedule_original->GetAttendees();
420 foreach( $attendees AS $attendee ) {
421 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
422 if ( $email == $request->principal->email() || $email == $organizer_principal->email() )
continue;
424 $agent = $attendee->GetParameterValue(
'SCHEDULE-AGENT');
425 if ( $agent && $agent !=
'SERVER' ) {
426 dbg_error_log(
"PUT",
"not delivering to %s, schedule agent set to value other than server", $email );
434 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$email,
'options'=> array (
'allow_by_email' =>
true ) ) );
435 if ( ! $attendee_principal->Exists() ){
436 dbg_error_log(
'PUT',
'Could not find attendee %s', $email);
439 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
440 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
441 $sql .=
'AND uid=? LIMIT 1';
442 $qry =
new AwlQuery($sql,$attendee_principal->user_no(), $uid);
443 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
444 dbg_error_log(
'PUT',
"Could not find attendee's event %s", $uid );
446 $row = $qry->Fetch();
447 $schedule_original =
new vCalendar($row->caldav_data);
448 $schedule_original->UpdateAttendeeStatus($request->principal->email(), clone($calling_attendee) );
449 $schedule_request = clone($schedule_original);
450 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
452 $schedule_target =
new Principal(
'email',$email);
454 if ( $schedule_target->Exists() ) {
459 if ($attendee_calendar->IsCalendar()) {
460 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
462 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
463 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
465 if ( !$attendee_calendar->Exists() ) {
466 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
467 $attendee_calendar->dav_name(), $schedule_target->username());
471 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
472 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
475 else if ( $attendee_inbox->WriteCalendarMember($schedule_request,
false) !==
false ) {
477 if ( $attendee_calendar->WriteCalendarMember($schedule_original,
false) ===
false ) {
478 dbg_error_log(
'ERROR',
'Could not write updated calendar member to %s',
479 $attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
480 trace_bug(
'Failed to write scheduling resource.');
485 dbg_error_log(
'PUT',
'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
486 $attendee->SetParameterValue(
'SCHEDULE-STATUS', $response );
487 $scheduling_actions =
true;
489 $resource->UpdateAttendeeStatus($email, clone($attendee));
493 return $scheduling_actions;
504function do_scheduling_requests( vCalendar $resource, $create, $old_data =
null ) {
506 if ( !isset($request) || (isset($c->enable_auto_schedule) && !$c->enable_auto_schedule) )
return false;
508 if ( ! is_object($resource) ) {
509 trace_bug(
'do_scheduling_requests called with non-object parameter (%s)', gettype($resource) );
513 $organizer = $resource->GetOrganizer();
514 if ( $organizer ===
false || empty($organizer) ) {
515 dbg_error_log(
'PUT',
'Event has no organizer - no scheduling required.' );
518 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
521 $resource->Render(
null,
true);
523 if ( $request->principal->email() != $organizer_email ) {
524 return do_scheduling_reply($resource,$organizer);
527 if (isset($c->enable_attendee_group_resolution) && $c->enable_attendee_group_resolution) {
528 $mail_domain = preg_replace(
'/^.*@/i',
'', $c->admin_email );
529 $attendees = $resource->GetAttendees();
530 $new_attendees = array();
531 foreach( $attendees AS $attendee ) {
532 $v = $attendee->Value();
534 if ($v ==
"invalid:nomail") {
535 $localname = $attendee->GetParameterValue(
"CN");
536 }
else if ((preg_match(
'/^@/', $v) == 1) || (preg_match(
'/mailto:@/',$v) == 1)) {
537 $localname = preg_replace(
'/^.*@/',
'', $v);
538 }
else if (preg_match(
'/@/', $v) != 1) {
540 }
else if (preg_match(
'/@'.$mail_domain.
'/', $v) == 1) {
541 $localname = preg_replace(
'/@.*$/',
'', $v);
542 $localname = preg_replace(
'/^mailto:/',
'', $localname);
545 dbg_error_log(
'PUT',
'try to resolve local attendee %s', $localname);
546 $qry =
new AwlQuery(
'SELECT fullname, email FROM usr WHERE user_no = (SELECT user_no FROM principal WHERE type_id = 1 AND user_no = (SELECT user_no FROM usr WHERE lower(username) = (text(:username)))) UNION SELECT fullname, email FROM usr WHERE user_no IN (SELECT user_no FROM principal WHERE principal_id IN (SELECT member_id FROM group_member WHERE group_id = (SELECT principal_id FROM principal WHERE type_id = 3 AND user_no = (SELECT user_no FROM usr WHERE lower(username) = (text(:username))))))', array(
':username' => strtolower($localname)));
547 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() >= 1 ) {
548 dbg_error_log(
'PUT',
'resolved local name %s to %d individual attendees', $localname, $qry->rows());
549 while ($row = $qry->Fetch()) {
550 if ($row->email == $request->principal->email())
continue;
551 dbg_error_log(
'PUT',
'adding individual attendee %s <%s>', $row->fullname, $row->email);
552 $a = clone($attendee);
553 $a->SetParameterValue(
"CN", $row->fullname);
554 $a->SetParameterValue(
"PARTSTAT",
"NEEDS-ACTION");
555 $a->Value(
"mailto:" . $row->email);
556 $new_attendees[] = $a;
559 $new_attendees[] = clone($attendee);
562 $new_attendees[] = clone($attendee);
565 $events = $resource->GetComponents(
"VEVENT");
567 $event->SetProperties($new_attendees,
'ATTENDEE');
569 $resource->UpdateAttendeeStatus(
"this-is-nonsense",
new vProperty(
"ATTENDEE:dummy"));
570 $attendees = $resource->GetAttendees();
574 $orig_resource =
new vCalendar($resource->Render(
null,
true));
576 $schedule_request =
new vCalendar($resource->Render(
null,
true));
577 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
579 $old_attendees = array();
580 if ( !empty($old_data) ) {
581 $old_resource =
new vCalendar($old_data);
582 $old_attendees = $old_resource->GetAttendees();
584 $attendees = $resource->GetAttendees();
585 if ( count($attendees) == 0 && count($old_attendees) == 0 ) {
586 dbg_error_log(
'PUT',
'Event has no attendees - no scheduling required.', count($attendees) );
589 $removed_attendees = array();
590 foreach( $old_attendees AS $attendee ) {
591 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
592 if ( $email == $request->principal->email() )
continue;
593 $removed_attendees[$email] = $attendee;
596 $uids = $resource->GetPropertiesByPath(
'/VCALENDAR/*/UID');
597 if ( count($uids) == 0 ) {
598 dbg_error_log(
'PUT',
'No UID in VCALENDAR - giving up on REPLY.' );
601 $uid = $uids[0]->Value();
603 dbg_error_log(
'PUT',
'Writing scheduling resources for %d attendees', count($attendees) );
604 $scheduling_actions =
false;
605 foreach( $attendees AS $attendee ) {
606 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
607 if ( $email == $request->principal->email() ) {
608 dbg_error_log(
"PUT",
"not delivering to owner '%s'", $request->principal->email() );
613 $attendee_is_new =
true;
616 $attendee_is_new = !isset($removed_attendees[$email]);
617 if ( !$attendee_is_new ) unset($removed_attendees[$email]);
620 $agent = $attendee->GetParameterValue(
'SCHEDULE-AGENT');
621 if ( $agent && $agent !=
'SERVER' ) {
622 dbg_error_log(
"PUT",
"not delivering to %s, schedule agent set to value other than server", $email );
625 $schedule_target =
new Principal(
'email',$email);
627 dbg_error_log(
'PUT',
'Handling scheduling resources for %s on %s which is %s', $email,
628 ($create?
'create':
'update'), ($attendee_is_new?
'new' :
'an update') );
629 if ( $schedule_target->Exists() ) {
632 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
633 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
634 $sql .=
'AND uid=? LIMIT 1';
635 $qry =
new AwlQuery($sql,$schedule_target->user_no(), $uid);
636 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
637 dbg_error_log(
'PUT',
"Could not find event in attendee's calendars" );
638 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
641 $row = $qry->Fetch();
644 if ($attendee_calendar->IsCalendar()) {
645 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
647 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
648 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
651 if ( !$attendee_calendar->Exists() ) {
652 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
653 $attendee_calendar->dav_name(), $schedule_target->username());
657 if ($attendee_is_new || !isset($row)) {
658 $this_schedule_request = clone($schedule_request);
659 $this_resource = clone($resource);
661 dbg_error_log(
'PUT',
"adjusting only some major properties in %s's instance of the event", $schedule_target->username());
662 $this_resource =
new vCalendar($row->caldav_data);
663 $these_events = $this_resource->GetComponents(
"VEVENT");
664 $this_event = $these_events[0];
665 $events = $resource->GetComponents(
"VEVENT");
668 $this_event->SetProperties( $event->GetProperties(
'DTSTAMP'),
'DTSTAMP' );
669 $this_event->SetProperties( $event->GetProperties(
'SEQUENCE'),
'SEQUENCE' );
670 $this_event->SetProperties( $event->GetProperties(
'DTSTART'),
'DTSTART' );
671 $this_event->SetProperties( $event->GetProperties(
'DTEND'),
'DTEND' );
672 $this_event->SetProperties( $event->GetProperties(
'DURATION'),
'DURATION' );
673 $this_event->SetProperties( $event->GetProperties(
'SUMMARY'),
'SUMMARY' );
674 $this_event->SetProperties( $event->GetProperties(
'LOCATION'),
'LOCATION' );
675 $this_event->SetProperties( $event->GetProperties(
'DESCRIPTION'),
'DESCRIPTION' );
676 $this_event->SetProperties( $event->GetProperties(
'GEO'),
'GEO' );
677 $this_event->SetProperties( $event->GetProperties(
'RESOURCES'),
'RESOURCES' );
678 $this_event->SetProperties( $event->GetProperties(
'STATUS'),
'STATUS' );
679 $this_event->SetProperties( $event->GetProperties(
'ATTENDEE'),
'ATTENDEE' );
680 $this_resource->SetComponents($these_events,
"VEVENT");
681 $this_schedule_request = clone($this_resource);
682 $this_schedule_request->AddProperty(
'METHOD',
'REQUEST');
684 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
685 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
688 else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, $attendee_is_new) !==
false ) {
690 if ( $attendee_calendar->WriteCalendarMember($this_resource, $attendee_is_new) ===
false ) {
691 dbg_error_log(
'ERROR',
'Could not write %s calendar member to %s', ($attendee_is_new?
'new':
'updated'),
692 $attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
693 trace_bug(
'Failed to write scheduling resource.');
700 $answer = $remote->sendRequest ( $email,
'VEVENT/REQUEST', $schedule_request->Render() );
701 if ( $answer ===
false ) {
705 foreach ( $answer as $a )
707 if ( $a ===
false ) {
710 elseif ( substr( $a, 0, 1 ) >= 1 ) {
719 dbg_error_log(
'PUT',
'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
720 $attendee->SetParameterValue(
'SCHEDULE-STATUS', $response );
721 $scheduling_actions =
true;
725 foreach( $removed_attendees AS $attendee ) {
726 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
727 $schedule_target =
new Principal(
'email',$email);
728 if ( $schedule_target->Exists() ) {
731 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
732 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
733 $sql .=
'AND uid=? LIMIT 1';
734 $qry =
new AwlQuery($sql,$schedule_target->user_no(), $uid);
735 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
736 dbg_error_log(
'PUT',
"Could not find event in attendee's calendars" );
737 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
739 $row = $qry->Fetch();
742 if ($attendee_calendar->IsCalendar()) {
743 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
745 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
746 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
748 dbg_error_log(
'PUT',
"marking %s's instance of the event to show that the user's invitation has been revoked", $schedule_target->username());
749 $this_resource =
new vCalendar($row->caldav_data);
750 $these_events = $this_resource->GetComponents(
"VEVENT");
751 $this_event = $these_events[0];
752 $properties[] =
new vProperty(
"DESCRIPTION:Your invitation to this event has been revoked." );
753 $this_event->SetProperties( $properties,
'DESCRIPTION' );
754 $properties[] =
new vProperty(
"STATUS:CANCELLED" );
755 $this_event->SetProperties( $properties,
'STATUS' );
756 $this_event->SetProperties(
null,
'ATTENDEE' );
757 $this_resource->SetComponents($these_events,
"VEVENT");
758 $this_schedule_request = clone($this_resource);
759 $this_schedule_request->AddProperty(
'METHOD',
'REQUEST');
760 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
761 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
764 else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request,
false) !==
false ) {
766 if ( $attendee_calendar->WriteCalendarMember($this_resource,
false) ===
false ) {
767 dbg_error_log(
'ERROR',
'Could not write updated calendar member');
768 trace_bug(
'Failed to write scheduling resource.');
775 return $scheduling_actions;
788function import_collection( $import_content, $user_no, $path, $caldav_context, $appending =
false ) {
791 if ( ! ini_get(
'open_basedir') && (isset($c->dbg[
'ALL']) || isset($c->dbg[
'put'])) ) {
792 $fh = fopen(
'/var/log/davical/PUT-2.debug',
'w');
794 fwrite($fh,$import_content);
799 if ( preg_match(
'{^begin:(vcard|vcalendar)}i', $import_content, $matches) ) {
800 if ( strtoupper($matches[1]) ==
'VCARD' )
801 import_addressbook_collection( $import_content, $user_no, $path, $caldav_context, $appending );
802 elseif ( strtoupper($matches[1]) ==
'VCALENDAR' )
803 import_calendar_collection( $import_content, $user_no, $path, $caldav_context, $appending );
806 $cache = getCacheInstance();
807 $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}
', '/
', $path);
808 $cache->delete( $cache_ns, null );
811 dbg_error_log('PUT
', 'Can only
import files which are VCARD or VCALENDAR
');
823function import_addressbook_collection( $vcard_content, $user_no, $path, $caldav_context, $appending = false ) {
825 // We hack this into an enclosing component because vComponent only expects a single root component
826 $addressbook = new vComponent("BEGIN:ADDRESSES\r\n".$vcard_content."\r\nEND:ADDRESSES\r\n");
828 require_once('vcard.php
');
830 $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name
';
831 $qry = new AwlQuery( $sql, array( ':dav_name
' => $path) );
832 if ( ! $qry->Exec('PUT
',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error in:
'.$sql );
833 if ( ! $qry->rows() == 1 ) {
834 dbg_error_log( 'ERROR
', ' PUT: Collection does not exist at
"%s" for user %d
', $path, $user_no );
835 rollback_on_error( $caldav_context, $user_no, $path, sprintf('Error: Collection does not exist at
"%s" for user %d
', $path, $user_no ));
837 $collection = $qry->Fetch();
838 $collection_id = $collection->collection_id;
840 // Fetch the current collection data
841 $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id
', array(
842 ':collection_id
' => $collection_id
844 $current_data = array();
845 while( $row = $qry->Fetch() )
846 $current_data[$row->dav_name] = $row->caldav_data;
848 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
849 $base_params = array(
850 ':collection_id
' => $collection_id,
851 ':session_user
' => $session->user_no,
852 ':caldav_type
' => 'VCARD
'
855 $dav_data_insert = <<<EOSQL
856INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
857 VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id )
860 $dav_data_update = <<<EOSQL
861UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
862 modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
866 $resources = $addressbook->GetComponents();
867 if ( count($resources) > 0 )
868 $qry->QDo('SELECT new_sync_token(0,
'.$collection_id.')
');
870 foreach( $resources AS $k => $resource ) {
871 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
873 $vcard = new vCard( $resource->Render() );
875 $uid = $vcard->GetPValue('UID
');
878 $vcard->AddProperty('UID
',$uid);
881 $last_modified = $vcard->GetPValue('REV
');
882 if ( empty($last_modified) ) {
883 $last_modified = gmdate( 'Ymd\THis\Z
' );
884 $vcard->AddProperty('REV
',$last_modified);
887 $created = $vcard->GetPValue('X-CREATED
');
888 if ( empty($last_modified) ) {
889 $created = gmdate( 'Ymd\THis\Z
' );
890 $vcard->AddProperty('X-CREATED
',$created);
893 $rendered_card = $vcard->Render();
895 // We don't allow any of &?\/@%+: in the UID to appear in the path, but anything
else is fair game.
896 $dav_name = sprintf(
'%s%s.vcf', $path, preg_replace(
'{[&?\\/@%+:]}',
'',$uid) );
898 $dav_data_params = $base_params;
899 $dav_data_params[
':user_no'] = $user_no;
900 $dav_data_params[
':dav_name'] = $dav_name;
901 $dav_data_params[
':etag'] = md5($rendered_card);
902 $dav_data_params[
':dav_data'] = $rendered_card;
903 $dav_data_params[
':modified'] = $last_modified;
904 $dav_data_params[
':created'] = $created;
908 if ( isset($current_data[$dav_name]) ) {
909 if ( $rendered_card == $current_data[$dav_name] ) {
910 unset($current_data[$dav_name]);
914 unset($current_data[$dav_name]);
920 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
923 if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
924 rollback_on_error( $caldav_context, $user_no, $path,
'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
927 $qry->QDo(
'SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $dav_name));
928 if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
929 $dav_id = $row->dav_id;
932 $vcard->Write( $row->dav_id, !$inserting );
934 $qry->QDo(
"SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(
':dav_name' => $dav_name ) );
936 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
939 if ( !$appending && count($current_data) > 0 ) {
940 $params = array(
':collection_id' => $collection_id );
941 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
942 foreach( $current_data AS $dav_name => $data ) {
943 $params[
':dav_name'] = $dav_name;
944 $qry->QDo(
'DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
945 $qry->QDo(
'SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
947 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
950 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
951 if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on COMMIT');
966function import_calendar_collection( $ics_content, $user_no, $path, $caldav_context, $appending =
false ) {
967 global $c, $session, $tz_regex;
968 $calendar =
new vComponent($ics_content);
969 $timezones = $calendar->GetComponents(
'VTIMEZONE',
true);
970 $components = $calendar->GetComponents(
'VTIMEZONE',
false);
975 if ( isset($_GET[
'after']) ) {
976 $after = $_GET[
'after'];
977 if ( strtoupper(substr($after, 0, 1)) ==
'P' || strtoupper(substr($after, 0, 1)) ==
'-P' ) {
979 $duration = $duration->asSeconds();
980 $after = time() - (abs($duration));
984 $after = $after->epoch();
988 $displayname = $calendar->GetPValue(
'X-WR-CALNAME');
989 if ( !$appending && isset($displayname) ) {
990 $sql =
'UPDATE collection SET dav_displayname = :displayname WHERE dav_name = :dav_name';
991 $qry =
new AwlQuery( $sql, array(
':displayname' => $displayname,
':dav_name' => $path) );
992 if ( ! $qry->Exec(
'PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on: '.$sql );
997 foreach( $timezones AS $k => $tz ) {
998 $tz_ids[$tz->GetPValue(
'TZID')] = $k;
1002 $resources = array();
1003 foreach( $components AS $k => $comp ) {
1004 $uid = $comp->GetPValue(
'UID');
1005 if ( $uid ==
null || $uid ==
'' ) {
1007 $comp->AddProperty(
'UID',$uid);
1008 dbg_error_log(
'LOG WARN',
' PUT: New collection resource does not have a UID - we assign one!' );
1010 if ( !isset($resources[$uid]) ) $resources[$uid] = array();
1011 $resources[$uid][] = $comp;
1014 $tzid = GetTZID($comp);
1015 if ( !empty($tzid) && !isset($resources[$uid][$tzid]) && isset($tz_ids[$tzid]) ) {
1016 $resources[$uid][$tzid] = $timezones[$tz_ids[$tzid]];
1021 $sql =
'SELECT * FROM collection WHERE dav_name = :dav_name';
1022 $qry =
new AwlQuery( $sql, array(
':dav_name' => $path) );
1023 if ( ! $qry->Exec(
'PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on: '.$sql );
1024 if ( ! $qry->rows() == 1 ) {
1025 dbg_error_log(
'ERROR',
' PUT: Collection does not exist at "%s" for user %d', $path, $user_no );
1026 rollback_on_error( $caldav_context, $user_no, $path, sprintf(
'Error: Collection does not exist at "%s" for user %d', $path, $user_no ));
1028 $collection = $qry->Fetch();
1029 $collection_id = $collection->collection_id;
1032 $qry->QDo(
'SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array(
1033 ':collection_id' => $collection_id
1035 $current_data = array();
1036 while( $row = $qry->Fetch() )
1037 $current_data[$row->dav_name] = $row->caldav_data;
1039 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
1040 $base_params = array(
':collection_id' => $collection_id );
1042 $dav_data_insert = <<<EOSQL
1043INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
1044 VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id )
1047 $dav_data_update = <<<EOSQL
1048UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
1049 modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
1052 $calitem_insert = <<<EOSQL
1053INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
1054 dtstart, dtstart_orig, dtend, dtend_orig, summary, location,
class, transp,
1055 description, rrule, tz_id, last_modified, url, priority, created, due,
1056 percent_complete, status, collection_id )
1057VALUES ( :user_no, :dav_name, currval(
'dav_id_seq'), :etag, :uid, :dtstamp,
1058 :dtstart, :dtstart_orig, ##dtend##, :dtend_orig, :summary, :location,
1059 :
class, :transp, :description, :rrule, :tzid, :modified, :url, :priority,
1060 :created, :due, :percent_complete, :status, :collection_id)
1063 $calitem_update = <<<EOSQL
1065SET user_no=:user_no, dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart,
1066 dtstart_orig=:dtstart_orig, dtend=##dtend##, summary=:summary,
1067 location=:location,
class=:
class, transp=:transp, description=:description,
1068 rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url,
1069 priority=:priority, due=:due, percent_complete=:percent_complete,
1071WHERE collection_id=:collection_id AND dav_name=:dav_name
1075 if ( count($resources) > 0 )
1076 $qry->QDo(
'SELECT new_sync_token(0,'.$collection_id.
')');
1078 foreach( $resources AS $uid => $resource ) {
1081 $vcal =
new vCalendar();
1082 $vcal->SetComponents($resource);
1083 $icalendar = $vcal->Render();
1084 $dav_name = sprintf(
'%s%s.ics', $path, preg_replace(
'{[&?\\/@%+:]}',
'',$uid) );
1086 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
1089 $first = $resource[0];
1091 $dav_data_params = $base_params;
1092 $dav_data_params[
':user_no'] = $user_no;
1094 $dav_data_params[
':dav_name'] = $dav_name;
1095 $dav_data_params[
':etag'] = md5($icalendar);
1096 $calitem_params = $dav_data_params;
1097 $dav_data_params[
':dav_data'] = $icalendar;
1098 $dav_data_params[
':caldav_type'] = $first->GetType();
1099 $dav_data_params[
':session_user'] = $session->user_no;
1101 $dtstart = $first->GetPValue(
'DTSTART');
1102 $calitem_params[
':dtstart_orig'] = quote_dt_param($dtstart);
1103 $calitem_params[
':dtstart'] = quote_dt_param($dtstart);
1105 if ( (!isset($dtstart) || $dtstart ==
'') && $first->GetPValue(
'DUE') !=
'' ) {
1106 $dtstart = $first->GetPValue(
'DUE');
1107 if ( isset($after) ) $dtstart_date =
new RepeatRuleDateTime($first->GetProperty(
'DUE'));
1109 else if ( isset($after) ) {
1113 $calitem_params[
':rrule'] = $first->GetPValue(
'RRULE');
1116 if ( isset($after) && empty($calitem_params[
':rrule']) && $dtstart_date->epoch() < $after )
continue;
1120 if ( isset($current_data[$dav_name]) ) {
1121 if ( $icalendar == $current_data[$dav_name] ) {
1122 if ( $after ==
null ) {
1123 unset($current_data[$dav_name]);
1128 unset($current_data[$dav_name]);
1135 if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
1136 rollback_on_error( $caldav_context, $user_no, $path,
'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
1139 $qry->QDo(
'SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $dav_data_params[
':dav_name']));
1140 if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
1141 $dav_id = $row->dav_id;
1144 $dtend = $first->GetPValue(
'DTEND');
1145 $calitem_params[
':dtend_orig'] = quote_dt_param($dtend);
1147 if ( isset($dtend) && $dtend !=
'' ) {
1148 dbg_error_log(
'PUT',
' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue(
'DURATION') );
1149 $calitem_params[
':dtend'] = quote_dt_param($dtend);
1154 if ( $first->GetPValue(
'DURATION') !=
'' AND $dtstart !=
'' ) {
1155 $duration = trim(preg_replace(
'#[PT]#',
' ', $first->GetPValue(
'DURATION') ));
1156 if ( $duration ==
'' ) $duration =
'0 seconds';
1157 $dtend =
'(:dtstart::timestamp with time zone + :duration::interval)';
1158 $calitem_params[
':duration'] = $duration;
1160 elseif ( $first->GetType() ==
'VEVENT' ) {
1174 $dtstart_prop = $first->GetProperty(
'DTSTART');
1175 if ( empty($dtstart_prop) ) {
1176 dbg_error_log(
'PUT',
'Invalid VEVENT without DTSTART, UID="%s" in collection %d', $uid, $collection_id);
1179 $value_type = $dtstart_prop->GetParameterValue(
'VALUE');
1180 dbg_error_log(
'PUT',
'DTSTART without DTEND. DTSTART value type is %s', $value_type );
1181 if ( isset($value_type) && $value_type ==
'DATE' )
1182 $dtend =
'(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1184 $dtend =
':dtstart';
1188 $last_modified = $first->GetPValue(
'LAST-MODIFIED');
1189 if ( !isset($last_modified) || $last_modified ==
'' ) $last_modified = gmdate(
'Ymd\THis\Z' );
1190 $calitem_params[
':modified'] = $last_modified;
1192 $dtstamp = $first->GetPValue(
'DTSTAMP');
1193 if ( empty($dtstamp) ) $dtstamp = $last_modified;
1194 $calitem_params[
':dtstamp'] = $dtstamp;
1197 $class = ($collection->public_events_only ==
't' && isset($class) ?
'PUBLIC' : $first->GetPValue(
'CLASS') );
1198 $calitem_params[
':class'] = $class;
1202 $tzid = GetTZID($first);
1203 if ( !empty($tzid) && !empty($resource[$tzid]) ) {
1204 $tz = $resource[$tzid];
1205 $olson = $vcal->GetOlsonName($tz);
1206 dbg_error_log(
'PUT',
' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson :
'') );
1207 if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1208 dbg_error_log(
'PUT',
' Setting timezone to %s', $olson );
1209 $qry->QDo(
'SET TIMEZONE TO \''.$olson.
"'" );
1210 $last_olson = $olson;
1212 $params = array(
':tzid' => $tzid);
1213 $qry =
new AwlQuery(
'SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1214 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1215 $params[
':olson_name'] = $olson;
1216 $params[
':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1217 $params[
':last_modified'] = (isset($tz) ? $tz->GetPValue(
'LAST-MODIFIED') : null );
1218 if ( empty($params[
':last_modified']) ) {
1219 $params[
':last_modified'] = gmdate(
'Ymd\THis\Z');
1221 $qry->QDo(
'INSERT INTO timezones (tzid, olson_name, active, vtimezone, last_modified) VALUES(:tzid,:olson_name,false,:vtimezone,:last_modified)', $params );
1225 $tz = $olson = $tzid =
null;
1228 $sql = str_replace(
'##dtend##', $dtend, ($inserting ? $calitem_insert : $calitem_update) );
1229 $calitem_params[
':uid'] = $first->GetPValue(
'UID');
1230 $calitem_params[
':url'] = $first->GetPValue(
'URL');
1231 $calitem_params[
':due'] = $first->GetPValue(
'DUE');
1232 $calitem_params[
':tzid'] = $tzid;
1233 $calitem_params[
':transp'] = $first->GetPValue(
'TRANSP');
1234 $calitem_params[
':status'] = $first->GetPValue(
'STATUS');
1235 $calitem_params[
':summary'] = $first->GetPValue(
'SUMMARY');
1236 $calitem_params[
':location'] = $first->GetPValue(
'LOCATION');
1237 $calitem_params[
':priority'] = $first->GetPValue(
'PRIORITY');
1238 $calitem_params[
':description'] = $first->GetPValue(
'DESCRIPTION');
1239 $calitem_params[
':percent_complete'] = $first->GetPValue(
'PERCENT-COMPLETE');
1246 $created = $first->GetPValue(
'CREATED');
1247 if ( $created ==
'00001231T000000Z' ) $created =
'20001231T000000Z';
1248 $calitem_params[
':created'] = $created;
1252 if ( !$qry->QDo($sql,$calitem_params) ) rollback_on_error( $caldav_context, $user_no, $path);
1254 write_alarms($dav_id, $first);
1255 write_attendees($dav_id, $vcal);
1257 $qry->QDo(
"SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(
':dav_name' => $dav_name ) );
1259 do_scheduling_requests( $vcal,
true );
1260 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1263 if ( !$appending && count($current_data) > 0 ) {
1264 $params = array(
':collection_id' => $collection_id );
1265 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
1266 foreach( $current_data AS $dav_name => $data ) {
1267 $params[
':dav_name'] = $dav_name;
1268 $qry->QDo(
'DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
1269 $qry->QDo(
'SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
1271 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1274 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
1275 if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path);
1288function write_alarms( $dav_id, vComponent $ical ) {
1289 $qry =
new AwlQuery(
'DELETE FROM calendar_alarm WHERE dav_id = '.$dav_id );
1290 $qry->Exec(
'PUT',__LINE__,__FILE__);
1292 $alarms = $ical->GetComponents(
'VALARM');
1293 if ( count($alarms) < 1 )
return;
1295 $qry->SetSql(
'INSERT INTO calendar_alarm ( dav_id, count, action, trigger, summary, description, component, next_trigger )
1296 VALUES( '.$dav_id.
', :count, :action, :trigger, :summary, :description, :component,
1297 :related::timestamp with time zone + :related_trigger::interval )' );
1300 foreach( $alarms AS $v ) {
1301 $trigger = array_merge($v->GetProperties(
'TRIGGER'));
1302 if ( $trigger ==
null )
continue;
1303 $trigger = $trigger[0];
1305 $related_trigger =
'0M';
1306 $trigger_type = $trigger->GetParameterValue(
'VALUE');
1307 if ( !isset($trigger_type) || $trigger_type ==
'DURATION' ) {
1308 switch ( $trigger->GetParameterValue(
'RELATED') ) {
1309 case 'DTEND': $related = $ical->GetProperty(
'DTEND');
break;
1310 case 'DUE': $related = $ical->GetProperty(
'DUE');
break;
1311 default: $related = $ical->GetProperty(
'DTSTART');
1313 $duration = $trigger->Value();
1314 if ( !preg_match(
'{^-?P(:?\d+W)?(:?\d+D)?(:?T(:?\d+H)?(:?\d+M)?(:?\d+S)?)?$}', $duration ) )
continue;
1315 $minus = (substr($duration,0,1) ==
'-');
1316 $related_trigger = trim(preg_replace(
'#[PT-]#',
' ', $duration ));
1317 if ($related_trigger ==
'') $related_trigger =
'0 seconds';
1319 $related_trigger = preg_replace(
'{(\d+[WDHMS])}',
'-$1 ', $related_trigger );
1322 $related_trigger = preg_replace(
'{(\d+[WDHMS])}',
'$1 ', $related_trigger );
1325 else if ( $trigger_type ==
'DATE-TIME' ) {
1326 $related = $trigger;
1329 if (
false === strtotime($trigger->Value()) )
continue;
1330 $related = $trigger;
1333 $qry->Bind(
':action', $v->GetPValue(
'ACTION'));
1334 $qry->Bind(
':trigger', $trigger->Render());
1335 $qry->Bind(
':summary', $v->GetPValue(
'SUMMARY'));
1336 $qry->Bind(
':description', $v->GetPValue(
'DESCRIPTION'));
1337 $qry->Bind(
':component', $v->Render());
1338 $qry->Bind(
':related', $related_date->UTC() );
1339 $qry->Bind(
':related_trigger', $related_trigger );
1340 $qry->Bind(
':count', $count++ );
1341 $qry->Exec(
'PUT',__LINE__,__FILE__);
1353function write_attendees( $dav_id, vCalendar $ical ) {
1354 $qry =
new AwlQuery(
'DELETE FROM calendar_attendee WHERE dav_id = '.$dav_id );
1355 $qry->Exec(
'PUT',__LINE__,__FILE__);
1357 $attendees = $ical->GetAttendees();
1358 if ( count($attendees) < 1 )
return;
1360 $qry->SetSql(
'INSERT INTO calendar_attendee ( dav_id, status, partstat, cn, attendee, role, rsvp, property )
1361 VALUES( '.$dav_id.
', :status, :partstat, :cn, :attendee, :role, :rsvp, :property )' );
1363 $processed = array();
1364 foreach( $attendees AS $v ) {
1365 $attendee = $v->Value();
1366 if ( isset($processed[$attendee]) ) {
1367 dbg_error_log(
'LOG',
'Duplicate attendee "%s" in resource "%d"', $attendee, $dav_id );
1368 dbg_error_log(
'LOG',
'Original: "%s"', $processed[$attendee] );
1369 dbg_error_log(
'LOG',
'Duplicate: "%s"', $v->Render() );
1372 $qry->Bind(
':attendee', $attendee );
1373 $qry->Bind(
':status', $v->GetParameterValue(
'STATUS') );
1374 $qry->Bind(
':partstat', $v->GetParameterValue(
'PARTSTAT') );
1375 $qry->Bind(
':cn', $v->GetParameterValue(
'CN') );
1376 $qry->Bind(
':role', $v->GetParameterValue(
'ROLE') );
1377 $qry->Bind(
':rsvp', $v->GetParameterValue(
'RSVP') );
1378 $qry->Bind(
':property', $v->Render() );
1379 $qry->Exec(
'PUT',__LINE__,__FILE__);
1380 $processed[$attendee] = $v->Render();
1401function write_resource(
DAVResource $resource, $caldav_data,
DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action=
true, $weak_etag=
null ) {
1402 global $tz_regex, $session;
1404 $path = $resource->bound_from();
1405 $user_no = $collection->user_no();
1406 $vcal =
new vCalendar( $caldav_data );
1407 $resources = $vcal->GetComponents(
'VTIMEZONE',
false);
1408 if ( !isset($resources[0]) ) {
1409 $resource_type =
'Unknown';
1411 rollback_on_error( $caldav_context, $user_no, $path, translate(
'No calendar content'), 412 );
1415 $first = $resources[0];
1416 if ( !($first instanceof vComponent) ) {
1417 print $vcal->Render();
1418 fatal(
'This is not a vComponent!');
1420 $resource_type = $first->GetType();
1423 $collection_id = $collection->collection_id();
1425 $qry =
new AwlQuery();
1428 $calitem_params = array(
1432 if ( $put_action_type ==
'INSERT' ) {
1433 $qry->QDo(
'SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data');
1436 $qry->QDo(
'SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $path));
1438 if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
1440 trace_bug(
'No dav_id for "%s" on %s!!!', $path, ($put_action_type ==
'INSERT' ?
'create':
'update'));
1441 rollback_on_error( $caldav_context, $user_no, $path);
1444 $dav_id = $row->dav_id;
1445 $old_dav_data = $row->caldav_data;
1448 if ($old_dav_data) {
1450 $oldvcal =
new vCalendar( $old_dav_data );
1451 $oldr = $oldvcal->GetComponents(
'VTIMEZONE',
false);
1452 $oldfirst = $oldr[0];
1453 $dtstart = $first->GetPValue(
'DTSTART');
1454 $olddtstart = $oldfirst->GetPValue(
'DTSTART');
1455 if (strcmp($dtstart, $olddtstart)) $modified =
true;
1456 $dtend = $first->GetPValue(
'DTEND');
1457 $olddtend = $oldfirst->GetPValue(
'DTEND');
1458 if (isset($dtend) && isset($olddtend) && strcmp($dtend, $olddtend)) $modified =
true;
1459 $duration = $first->GetPValue(
'DURATION');
1460 $oldduration = $oldfirst->GetPValue(
'DURATION');
1461 $organizer = $vcal->GetOrganizer();
1462 if (isset($duration) && isset($oldduration) && strcmp($duration, $oldduration)) $modified =
true;
1463 if (($modified ==
true) && !($organizer ===
false || empty($organizer))) {
1464 dbg_error_log(
'PUT',
"event time attributes have been modified, reset all attendees' PARTSTAT to NEEDS-ACTION.");
1465 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
1466 $attendees = $first->GetProperties(
'ATTENDEE');
1467 foreach( $attendees AS $attendee ) {
1468 $attendee_email = preg_replace(
'/^mailto:/',
'', $attendee->Value() );
1469 if ( $attendee_email != $organizer_email ) {
1470 $attendee->SetParameterValue(
"PARTSTAT",
"NEEDS-ACTION");
1471 $attendee->SetParameterValue(
"SCHEDULE-STATUS",
"1.0");
1474 $first->SetProperties($attendees,
'ATTENDEE');
1475 $caldav_data = $vcal->Render(
null,
true);
1476 dbg_error_log(
'PUT',
"event time attributes have been modified, reset all attendees' PARTSTAT completed.");
1480 $dav_params = array(
1482 ':dav_data' => $caldav_data,
1483 ':caldav_type' => $resource_type,
1484 ':session_user' => $author,
1485 ':weak_etag' => $weak_etag
1488 $dav_params[
':dav_id'] = $dav_id;
1489 $calitem_params[
':dav_id'] = $dav_id;
1492 if ( $first->GetType() ==
'VTODO' ) $due = $first->GetPValue(
'DUE');
1493 $calitem_params[
':due'] = $due;
1494 $dtstart = $first->GetPValue(
'DTSTART');
1495 $calitem_params[
':dtstart_orig'] = quote_dt_param($dtstart);
1497 if ( empty($dtstart) ) $dtstart = $due;
1498 $calitem_params[
':dtstart'] = quote_dt_param($dtstart);
1500 $dtend = $first->GetPValue(
'DTEND');
1501 $calitem_params[
':dtend_orig'] = quote_dt_param($dtend);
1503 if ( isset($dtend) && $dtend !=
'' ) {
1504 dbg_error_log(
'PUT',
' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue(
'DURATION') );
1505 $calitem_params[
':dtend'] = quote_dt_param($dtend);
1511 if ( $first->GetPValue(
'DURATION') !=
'' AND $dtstart !=
'' ) {
1512 $duration = trim(preg_replace(
'#[PT]#',
' ', $first->GetPValue(
'DURATION') ));
1513 if ( $duration ==
'' ) $duration =
'0 seconds';
1514 $dtend =
'(:dtstart::timestamp with time zone + :duration::interval)';
1515 $calitem_params[
':duration'] = $duration;
1517 elseif ( $first->GetType() ==
'VEVENT' ) {
1531 $dtstart_prop = $first->GetProperty(
'DTSTART');
1532 $value_type = $dtstart_prop->GetParameterValue(
'VALUE');
1533 dbg_error_log(
'PUT',
'DTSTART without DTEND. DTSTART value type is %s', $value_type );
1534 if ( isset($value_type) && $value_type ==
'DATE' )
1535 $dtend =
'(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1537 $dtend =
':dtstart';
1541 $dtstamp = $first->GetPValue(
'DTSTAMP');
1542 if ( !isset($dtstamp) || $dtstamp ==
'' ) {
1544 $dtstamp = gmdate(
'Ymd\THis\Z' );
1546 $calitem_params[
':dtstamp'] = $dtstamp;
1548 $last_modified = $first->GetPValue(
'LAST-MODIFIED');
1549 if ( !isset($last_modified) || $last_modified ==
'' ) $last_modified = $dtstamp;
1550 $dav_params[
':modified'] = $last_modified;
1551 $calitem_params[
':modified'] = $last_modified;
1553 $created = $first->GetPValue(
'CREATED');
1554 if ( $created ==
'00001231T000000Z' ) $created =
'20001231T000000Z';
1556 $class = $first->GetPValue(
'CLASS');
1559 if ( public_events_only($user_no, $path) && isset($class) ) {
1563 $calitem_params[
':class'] = $class;
1566 $last_olson =
'Turkmenikikamukau';
1567 $tzid = GetTZID($first);
1568 if ( !empty($tzid) ) {
1569 $timezones = $vcal->GetComponents(
'VTIMEZONE');
1570 foreach( $timezones AS $k => $tz ) {
1571 if ( $tz->GetPValue(
'TZID') != $tzid ) {
1575 dbg_error_log(
'ERROR',
' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue(
'TZID'), $tzid );
1578 $olson = olson_from_tzstring($tzid);
1579 if ( empty($olson) ) {
1580 $olson = $tz->GetPValue(
'X-LIC-LOCATION');
1581 if ( !empty($olson) ) {
1582 $olson = olson_from_tzstring($olson);
1587 dbg_error_log(
'PUT',
' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson :
'') );
1588 if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1589 dbg_error_log(
'PUT',
' Setting timezone to %s', $olson );
1590 if ( $olson !=
'' ) {
1591 $qry->QDo(
'SET TIMEZONE TO \''.$olson.
"'" );
1593 $last_olson = $olson;
1595 $params = array(
':tzid' => $tzid);
1596 $qry =
new AwlQuery(
'SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1597 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1598 $params[
':olson_name'] = $olson;
1599 $params[
':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1600 $qry->QDo(
'INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params );
1602 if ( !isset($olson) || $olson ==
'' ) $olson = $tzid;
1606 $qry->QDo(
'SELECT new_sync_token(0,'.$collection_id.
')');
1608 $calitem_params[
':tzid'] = $tzid;
1609 $calitem_params[
':uid'] = $first->GetPValue(
'UID');
1610 $calitem_params[
':summary'] = $first->GetPValue(
'SUMMARY');
1611 $calitem_params[
':location'] = $first->GetPValue(
'LOCATION');
1612 $calitem_params[
':transp'] = $first->GetPValue(
'TRANSP');
1613 $calitem_params[
':description'] = $first->GetPValue(
'DESCRIPTION');
1614 $calitem_params[
':rrule'] = $first->GetPValue(
'RRULE');
1615 $calitem_params[
':url'] = $first->GetPValue(
'URL');
1616 $calitem_params[
':priority'] = $first->GetPValue(
'PRIORITY');
1617 $calitem_params[
':percent_complete'] = $first->GetPValue(
'PERCENT-COMPLETE');
1618 $calitem_params[
':status'] = $first->GetPValue(
'STATUS');
1620 $range = getVCalendarRange($vcal, $resource->timezone_name());
1621 $calitem_params[
':first_instance_start'] = isset($range->from) ? $range->from->UTC() :
null;
1622 $calitem_params[
':last_instance_end'] = isset($range->until) ? $range->until->UTC() :
null;
1625 $vcal->Render(
null,
true);
1627 if ( !$collection->IsSchedulingCollection() ) {
1628 if ( do_scheduling_requests($vcal, ($put_action_type ==
'INSERT'), $old_dav_data ) ) {
1629 $dav_params[
':dav_data'] = $vcal->Render(
null,
true);
1634 if ( !isset($dav_params[
':modified']) ) $dav_params[
':modified'] =
'now';
1635 if ( $put_action_type ==
'INSERT' ) {
1636 $sql =
'INSERT INTO caldav_data ( dav_id, user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag )
1637 VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )';
1638 $dav_params[
':collection_id'] = $collection_id;
1639 $dav_params[
':user_no'] = $user_no;
1640 $dav_params[
':dav_name'] = $path;
1641 $dav_params[
':created'] = (isset($created) && $created !=
'' ? $created : $dtstamp);
1644 $sql =
'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
1645 modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id';
1647 $qry =
new AwlQuery($sql,$dav_params);
1648 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) ) {
1649 fatal(
'Insert into calendar_item failed...');
1650 rollback_on_error( $caldav_context, $user_no, $path);
1655 if ( $put_action_type ==
'INSERT' ) {
1657INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
1658 dtstart, dtstart_orig, dtend, dtend_orig, summary, location,
class, transp,
1659 description, rrule, tz_id, last_modified, url, priority, created, due,
1660 percent_complete, status, collection_id, first_instance_start,
1662VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp, :dtstart,
1663 :dtstart_orig, $dtend, :dtend_orig, :summary, :location, :
class, :transp,
1664 :description, :rrule, :tzid, :modified, :url, :priority, :created, :due,
1665 :percent_complete, :status, :collection_id, :first_instance_start,
1669 $calitem_params[
':collection_id'] = $collection_id;
1670 $calitem_params[
':user_no'] = $user_no;
1671 $calitem_params[
':dav_name'] = $path;
1672 $calitem_params[
':created'] = $dav_params[
':created'];
1676UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
1677 dtstart=:dtstart, dtstart_orig=:dtstart_orig, dtend=$dtend,
1678 dtend_orig=:dtend_orig, summary=:summary, location=:location,
class=:
class,
1679 transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid,
1680 last_modified=:modified, url=:url, priority=:priority, due=:due,
1681 percent_complete=:percent_complete, status=:status,
1682 first_instance_start=:first_instance_start,
1683 last_instance_end=:last_instance_end
1689 write_alarms($dav_id, $first);
1690 write_attendees($dav_id, $vcal);
1692 if ( $log_action && function_exists(
'log_caldav_action') ) {
1693 log_caldav_action( $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1695 else if ( $log_action ) {
1696 dbg_error_log(
'PUT',
'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
1697 $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1700 $qry =
new AwlQuery( $sql, $calitem_params );
1701 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) ) {
1702 rollback_on_error( $caldav_context, $user_no, $path);
1705 $qry->QDo(
"SELECT write_sync_change( :collection_id, $sync_change, :dav_name)",
1706 array(
':collection_id' => $collection_id,
':dav_name' => $path ) );
1709 if ( function_exists(
'post_commit_action') ) {
1710 post_commit_action( $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1714 $cache = getCacheInstance();
1715 $cache_ns =
'collection-'.preg_replace(
'{/[^/]*$}',
'/', $path);
1716 $cache->delete( $cache_ns,
null );
1718 dbg_error_log(
'PUT',
'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
1734function simple_write_resource( $path, $caldav_data, $put_action_type, $write_action_log =
false ) {
1741 $etag = md5($caldav_data);
1742 $collection_path = preg_replace(
'#/[^/]*$#',
'/', $path );
1744 if ( $collection->IsCollection() || $collection->IsSchedulingCollection() ) {
1745 return write_resource( $dav_resource, $caldav_data, $collection, $session->user_no, $etag, $put_action_type,
false, $write_action_log );