18 require_once(
'AwlCache.php');
19 require_once(
'vComponent.php');
20 require_once(
'vCalendar.php');
21 require_once(
'WritableCollection.php');
22 require_once(
'schedule-functions.php');
23 include_once(
'iSchedule.php');
24 include_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';
41 function 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);
72 function 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 )
116 VALUES( :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 );
150 function 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') {
175 function 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');
189 function handle_schedule_request( $ical ) {
190 global $c, $session, $request;
191 $resources = $ical->GetComponents(
'VTIMEZONE',
false);
193 $etag = md5 ( $request->raw_post );
194 $reply =
new XMLDocument( array(
"DAV:" =>
"",
"urn:ietf:params:xml:ns:caldav" =>
"C" ) );
195 $responses = array();
197 $attendees = $ic->GetProperties(
'ATTENDEE');
198 $wr_attendees = $ic->GetProperties(
'X-WR-ATTENDEE');
199 if ( count ( $wr_attendees ) > 0 ) {
200 dbg_error_log(
"PUT",
"Non-compliant iCal request. Using X-WR-ATTENDEE property" );
201 foreach( $wr_attendees AS $k => $v ) {
205 dbg_error_log(
"PUT",
"Attempting to deliver scheduling request for %d attendees", count($attendees) );
207 foreach( $attendees AS $k => $attendee ) {
208 $attendee_email = preg_replace(
'/^mailto:/',
'', $attendee->Value() );
209 if ( $attendee_email == $request->principal->email() ) {
210 dbg_error_log(
"PUT",
"not delivering to owner" );
213 if ( $attendee->GetParameterValue (
'PARTSTAT' ) !=
'NEEDS-ACTION' || preg_match (
'/^[35]\.[3-9]/', $attendee->GetParameterValue (
'SCHEDULE-STATUS' ) ) ) {
214 dbg_error_log(
"PUT",
"attendee %s does not need action", $attendee_email );
218 if ( isset($c->enable_auto_schedule) && !$c->enable_auto_schedule ) {
221 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'5.3;No scheduling support for user');
225 dbg_error_log(
"PUT",
"Delivering to %s", $attendee_email );
227 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$attendee_email,
'options'=> array (
'allow_by_email' =>
true ) ) );
228 if ( ! $attendee_principal->Exists() ){
229 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'5.3;No scheduling support for user');
232 $deliver_path = $attendee_principal->internal_url(
'schedule-inbox');
235 $priv = $ar->HavePrivilegeTo(
'schedule-deliver-invite' );
236 if ( ! $ar->HavePrivilegeTo(
'schedule-deliver-invite' ) ){
237 $reply =
new XMLDocument( array(
'DAV:' =>
'') );
238 $privnodes = array( $reply->href($attendee_principal->url(
'schedule-inbox')),
new XMLElement(
'privilege' ) );
240 $reply->NSElement( $privnodes[1],
'schedule-deliver-invite' );
241 $xml =
new XMLElement(
'need-privileges',
new XMLElement(
'resource', $privnodes) );
242 $xmldoc = $reply->Render(
'error',$xml);
243 $request->DoResponse( 403, $xmldoc,
'text/xml; charset="utf-8"');
247 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'1.2;Scheduling message has been delivered');
248 $ncal =
new vCalendar( array(
'METHOD' =>
'REQUEST') );
249 $ncal->AddComponent( array_merge( $ical->GetComponents(
'VEVENT',
false), array($ic) ));
250 $content = $ncal->Render();
251 $cid = $ar->GetProperty(
'collection_id');
252 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 );
253 $item_etag = md5($content);
254 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->
user_no, $item_etag,
255 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
256 $attendee->SetParameterValue (
'SCHEDULE-STATUS',
'1.2;Scheduling message has been delivered');
259 $ncal =
new vCalendar(array(
'METHOD' =>
'REQUEST'));
260 $ncal->AddComponent ( array_merge ( $ical->GetComponents(
'VEVENT',
false) , array ($ic) ));
261 $content = $ncal->Render();
262 $deliver_path = $request->principal->internal_url(
'schedule-inbox');
264 $item_etag = md5($content);
265 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->
user_no, $item_etag,
266 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
268 header(
'ETag: "'. $etag .
'"' );
269 header(
'Schedule-Tag: "'.$etag .
'"' );
270 $request->DoResponse( 201,
'Created' );
279 function handle_schedule_reply ( vCalendar $ical ) {
280 global $c, $session, $request;
281 $resources = $ical->GetComponents(
'VTIMEZONE',
false);
283 $etag = md5 ( $request->raw_post );
284 $organizer = $ical->GetOrganizer();
285 $arrayOrganizer = array($organizer);
287 if ( empty( $arrayOrganizer ) )
return false;
289 $attendees = array_merge($arrayOrganizer,$ical->GetAttendees());
290 dbg_error_log(
"PUT",
"Attempting to deliver scheduling request for %d attendees", count($attendees) );
292 foreach( $attendees AS $k => $attendee ) {
293 $attendee_email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
294 dbg_error_log(
"PUT",
"Delivering to %s", $attendee_email );
295 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$attendee_email,
'options'=> array (
'allow_by_email' =>
true ) ) );
296 $deliver_path = $attendee_principal->internal_url(
'schedule-inbox');
297 $attendee_email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
298 if ( $attendee_email == $request->principal->email ) {
299 dbg_error_log(
"PUT",
"not delivering to owner" );
303 if ( ! $ar->HavePrivilegeTo(
'schedule-deliver-reply' ) ){
304 $reply =
new XMLDocument( array(
'DAV:' =>
'') );
305 $privnodes = array( $reply->href($attendee_principal->url(
'schedule-inbox')),
new XMLElement(
'privilege' ) );
307 $reply->NSElement( $privnodes[1],
'schedule-deliver-reply' );
308 $xml =
new XMLElement(
'need-privileges',
new XMLElement(
'resource', $privnodes) );
309 $xmldoc = $reply->Render(
'error',$xml);
310 $request->DoResponse( 403, $xmldoc,
'text/xml; charset="utf-8"' );
314 $ncal =
new vCalendar( array(
'METHOD' =>
'REPLY') );
315 $ncal->AddComponent ( array_merge ( $ical->GetComponents(
'VEVENT',
false) , array ($ic) ));
316 $content = $ncal->Render();
317 write_resource(
new DAVResource($deliver_path . $etag .
'.ics'), $content, $ar, $request->
user_no, md5($content),
318 $put_action_type=
'INSERT', $caldav_context=
true, $log_action=
true, $etag );
320 $request->DoResponse( 201,
'Created' );
329 function do_scheduling_reply( vCalendar $resource, vProperty $organizer ) {
331 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
332 $organizer_principal =
new Principal(
'email',$organizer_email );
333 if ( !$organizer_principal->Exists() ) {
334 dbg_error_log(
'PUT',
'Organizer "%s" not found - cannot perform scheduling reply.', $organizer );
337 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data FROM caldav_data JOIN calendar_item USING(dav_id) ';
338 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
339 $sql .=
'AND uid=? LIMIT 1';
340 $uids = $resource->GetPropertiesByPath(
'/VCALENDAR/*/UID');
341 if ( count($uids) == 0 ) {
342 dbg_error_log(
'PUT',
'No UID in VCALENDAR - giving up on REPLY.' );
345 $uid = $uids[0]->Value();
346 $qry =
new AwlQuery($sql,$organizer_principal->user_no(), $uid);
347 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
348 dbg_error_log(
'PUT',
'Could not find original event from organizer - giving up on REPLY.' );
351 $row = $qry->Fetch();
352 $attendees = $resource->GetAttendees();
353 foreach( $attendees AS $v ) {
354 $email = preg_replace(
'/^mailto:/i',
'', $v->Value() );
355 if ( $email == $request->principal->email() ) {
359 if ( empty($attendee) ) {
360 dbg_error_log(
'PUT',
'Could not find ATTENDEE in VEVENT - giving up on REPLY.' );
363 $schedule_original =
new vCalendar($row->caldav_data);
364 $attendee->SetParameterValue(
'SCHEDULE-STATUS',
'2.0');
365 $schedule_original->UpdateAttendeeStatus($request->principal->email(), clone($attendee) );
367 $collection_path = preg_replace(
'{/[^/]+$}',
'/', $row->dav_name );
368 $segment_name = str_replace($collection_path,
'', $row->dav_name );
370 $organizer_inbox =
new WritableCollection(array(
'path' => $organizer_principal->internal_url(
'schedule-inbox')));
372 $schedule_reply = GetItip(
new vCalendar($schedule_original->Render(null,
true)),
'REPLY', $attendee->Value(), array(
'CUTYPE'=>
true,
'SCHEDULE-STATUS'=>
true));
374 dbg_error_log(
'PUT',
'Writing scheduling REPLY from %s to %s', $request->principal->email(), $organizer_principal->email() );
377 if ( !$organizer_calendar->Exists() ) {
378 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
379 $organizer_calendar->dav_name(), $organizer_principal->username());
383 if ( ! $organizer_inbox->HavePrivilegeTo(
'schedule-deliver-reply') ) {
386 else if ( $organizer_inbox->WriteCalendarMember($schedule_reply,
false,
false, $request->principal->username().$segment_name) !==
false ) {
388 if ( $organizer_calendar->WriteCalendarMember($schedule_original,
false,
false, $segment_name) === false ) {
389 dbg_error_log(
'ERROR',
'Could not write updated calendar member to %s',
390 $organizer_calendar->dav_name());
391 trace_bug(
'Failed to write scheduling resource.');
396 $schedule_request = clone($schedule_original);
397 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
399 dbg_error_log(
'PUT',
'Status for organizer <%s> set to "%s"', $organizer->Value(), $response );
400 $organizer->SetParameterValue(
'SCHEDULE-STATUS', $response );
401 $resource->UpdateOrganizerStatus($organizer);
402 $scheduling_actions =
true;
404 $calling_attendee = clone($attendee);
405 $attendees = $schedule_original->GetAttendees();
406 foreach( $attendees AS $attendee ) {
407 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
408 if ( $email == $request->principal->email() || $email == $organizer_principal->email() )
continue;
410 $agent = $attendee->GetParameterValue(
'SCHEDULE-AGENT');
411 if ( $agent && $agent !=
'SERVER' ) {
412 dbg_error_log(
"PUT",
"not delivering to %s, schedule agent set to value other than server", $email );
420 $attendee_principal =
new DAVPrincipal ( array (
'email'=>$email,
'options'=> array (
'allow_by_email' =>
true ) ) );
421 if ( ! $attendee_principal->Exists() ){
422 dbg_error_log(
'PUT',
'Could not find attendee %s', $email);
425 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
426 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
427 $sql .=
'AND uid=? LIMIT 1';
428 $qry =
new AwlQuery($sql,$attendee_principal->user_no(), $uid);
429 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
430 dbg_error_log(
'PUT',
"Could not find attendee's event %s", $uid );
432 $row = $qry->Fetch();
433 $schedule_original =
new vCalendar($row->caldav_data);
434 $schedule_original->UpdateAttendeeStatus($request->principal->email(), clone($calling_attendee) );
435 $schedule_request = clone($schedule_original);
436 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
438 $schedule_target =
new Principal(
'email',$email);
440 if ( $schedule_target->Exists() ) {
445 if ($attendee_calendar->IsCalendar()) {
446 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
448 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
449 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
451 if ( !$attendee_calendar->Exists() ) {
452 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
453 $attendee_calendar->dav_name(), $schedule_target->username());
457 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
458 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
461 else if ( $attendee_inbox->WriteCalendarMember($schedule_request,
false) !== false ) {
463 if ( $attendee_calendar->WriteCalendarMember($schedule_original,
false) === false ) {
464 dbg_error_log(
'ERROR',
'Could not write updated calendar member to %s',
465 $attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
466 trace_bug(
'Failed to write scheduling resource.');
471 dbg_error_log(
'PUT',
'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
472 $attendee->SetParameterValue(
'SCHEDULE-STATUS', $response );
473 $scheduling_actions =
true;
475 $resource->UpdateAttendeeStatus($email, clone($attendee));
479 return $scheduling_actions;
490 function do_scheduling_requests( vCalendar $resource, $create, $old_data = null ) {
492 if ( !isset($request) || (isset($c->enable_auto_schedule) && !$c->enable_auto_schedule) )
return false;
494 if ( ! is_object($resource) ) {
495 trace_bug(
'do_scheduling_requests called with non-object parameter (%s)', gettype($resource) );
499 $organizer = $resource->GetOrganizer();
500 if ( $organizer ===
false || empty($organizer) ) {
501 dbg_error_log(
'PUT',
'Event has no organizer - no scheduling required.' );
504 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
507 $resource->Render(null,
true);
509 if ( $request->principal->email() != $organizer_email ) {
510 return do_scheduling_reply($resource,$organizer);
513 if (isset($c->enable_attendee_group_resolution) && $c->enable_attendee_group_resolution) {
514 $mail_domain = preg_replace(
'/^.*@/i',
'', $c->admin_email );
515 $attendees = $resource->GetAttendees();
516 $new_attendees = array();
517 foreach( $attendees AS $attendee ) {
518 $v = $attendee->Value();
520 if ($v ==
"invalid:nomail") {
521 $localname = $attendee->GetParameterValue(
"CN");
522 }
else if ((preg_match(
'/^@/', $v) == 1) || (preg_match(
'/mailto:@/',$v) == 1)) {
523 $localname = preg_replace(
'/^.*@/',
'', $v);
524 }
else if (preg_match(
'/@/', $v) != 1) {
526 }
else if (preg_match(
'/@'.$mail_domain.
'/', $v) == 1) {
527 $localname = preg_replace(
'/@.*$/',
'', $v);
528 $localname = preg_replace(
'/^mailto:/',
'', $localname);
531 dbg_error_log(
'PUT',
'try to resolve local attendee %s', $localname);
532 $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)));
533 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() >= 1 ) {
534 dbg_error_log(
'PUT',
'resolved local name %s to %d individual attendees', $localname, $qry->rows());
535 while ($row = $qry->Fetch()) {
536 if ($row->email == $request->principal->email())
continue;
537 dbg_error_log(
'PUT',
'adding individual attendee %s <%s>', $row->fullname, $row->email);
538 $a = clone($attendee);
539 $a->SetParameterValue(
"CN", $row->fullname);
540 $a->SetParameterValue(
"PARTSTAT",
"NEEDS-ACTION");
541 $a->Value(
"mailto:" . $row->email);
542 $new_attendees[] = $a;
545 $new_attendees[] = clone($attendee);
548 $new_attendees[] = clone($attendee);
551 $events = $resource->GetComponents(
"VEVENT");
553 $event->SetProperties($new_attendees,
'ATTENDEE');
555 $resource->UpdateAttendeeStatus(
"this-is-nonsense",
new vProperty(
"ATTENDEE:dummy"));
556 $attendees = $resource->GetAttendees();
560 $orig_resource =
new vCalendar($resource->Render(null,
true));
562 $schedule_request =
new vCalendar($resource->Render(null,
true));
563 $schedule_request->AddProperty(
'METHOD',
'REQUEST');
565 $old_attendees = array();
566 if ( !empty($old_data) ) {
567 $old_resource =
new vCalendar($old_data);
568 $old_attendees = $old_resource->GetAttendees();
570 $attendees = $resource->GetAttendees();
571 if ( count($attendees) == 0 && count($old_attendees) == 0 ) {
572 dbg_error_log(
'PUT',
'Event has no attendees - no scheduling required.', count($attendees) );
575 $removed_attendees = array();
576 foreach( $old_attendees AS $attendee ) {
577 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
578 if ( $email == $request->principal->email() )
continue;
579 $removed_attendees[$email] = $attendee;
582 $uids = $resource->GetPropertiesByPath(
'/VCALENDAR/*/UID');
583 if ( count($uids) == 0 ) {
584 dbg_error_log(
'PUT',
'No UID in VCALENDAR - giving up on REPLY.' );
587 $uid = $uids[0]->Value();
589 dbg_error_log(
'PUT',
'Writing scheduling resources for %d attendees', count($attendees) );
590 $scheduling_actions =
false;
591 foreach( $attendees AS $attendee ) {
592 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
593 if ( $email == $request->principal->email() ) {
594 dbg_error_log(
"PUT",
"not delivering to owner '%s'", $request->principal->email() );
599 $attendee_is_new =
true;
602 $attendee_is_new = !isset($removed_attendees[$email]);
603 if ( !$attendee_is_new ) unset($removed_attendees[$email]);
606 $agent = $attendee->GetParameterValue(
'SCHEDULE-AGENT');
607 if ( $agent && $agent !=
'SERVER' ) {
608 dbg_error_log(
"PUT",
"not delivering to %s, schedule agent set to value other than server", $email );
611 $schedule_target =
new Principal(
'email',$email);
613 dbg_error_log(
'PUT',
'Handling scheduling resources for %s on %s which is %s', $email,
614 ($create?
'create':
'update'), ($attendee_is_new?
'new' :
'an update') );
615 if ( $schedule_target->Exists() ) {
618 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
619 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
620 $sql .=
'AND uid=? LIMIT 1';
621 $qry =
new AwlQuery($sql,$schedule_target->user_no(), $uid);
622 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
623 dbg_error_log(
'PUT',
"Could not find event in attendee's calendars" );
624 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
627 $row = $qry->Fetch();
630 if ($attendee_calendar->IsCalendar()) {
631 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
633 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
634 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
637 if ( !$attendee_calendar->Exists() ) {
638 dbg_error_log(
'ERROR',
'Default calendar at "%s" does not exist for user "%s"',
639 $attendee_calendar->dav_name(), $schedule_target->username());
643 if ($attendee_is_new || !$row) {
644 $this_schedule_request = clone($schedule_request);
645 $this_resource = clone($resource);
647 dbg_error_log(
'PUT',
"adjusting only some major properties in %s's instance of the event", $schedule_target->username());
648 $this_resource =
new vCalendar($row->caldav_data);
649 $these_events = $this_resource->GetComponents(
"VEVENT");
650 $this_event = $these_events[0];
651 $events = $resource->GetComponents(
"VEVENT");
654 $this_event->SetProperties( $event->GetProperties(
'DTSTAMP'),
'DTSTAMP' );
655 $this_event->SetProperties( $event->GetProperties(
'SEQUENCE'),
'SEQUENCE' );
656 $this_event->SetProperties( $event->GetProperties(
'DTSTART'),
'DTSTART' );
657 $this_event->SetProperties( $event->GetProperties(
'DTEND'),
'DTEND' );
658 $this_event->SetProperties( $event->GetProperties(
'DURATION'),
'DURATION' );
659 $this_event->SetProperties( $event->GetProperties(
'SUMMARY'),
'SUMMARY' );
660 $this_event->SetProperties( $event->GetProperties(
'LOCATION'),
'LOCATION' );
661 $this_event->SetProperties( $event->GetProperties(
'DESCRIPTION'),
'DESCRIPTION' );
662 $this_event->SetProperties( $event->GetProperties(
'GEO'),
'GEO' );
663 $this_event->SetProperties( $event->GetProperties(
'RESOURCES'),
'RESOURCES' );
664 $this_event->SetProperties( $event->GetProperties(
'STATUS'),
'STATUS' );
665 $this_event->SetProperties( $event->GetProperties(
'ATTENDEE'),
'ATTENDEE' );
666 $this_resource->SetComponents($these_events,
"VEVENT");
667 $this_schedule_request = clone($this_resource);
668 $this_schedule_request->AddProperty(
'METHOD',
'REQUEST');
670 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
671 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
674 else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request, $attendee_is_new) !== false ) {
676 if ( $attendee_calendar->WriteCalendarMember($this_resource, $attendee_is_new) === false ) {
677 dbg_error_log(
'ERROR',
'Could not write %s calendar member to %s', ($attendee_is_new?
'new':
'updated'),
678 $attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
679 trace_bug(
'Failed to write scheduling resource.');
686 $answer = $remote->sendRequest ( $email,
'VEVENT/REQUEST', $schedule_request->Render() );
687 if ( $answer ===
false ) {
691 foreach ( $answer as $a )
693 if ( $a ===
false ) {
696 elseif ( substr( $a, 0, 1 ) >= 1 ) {
705 dbg_error_log(
'PUT',
'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
706 $attendee->SetParameterValue(
'SCHEDULE-STATUS', $response );
707 $scheduling_actions =
true;
711 foreach( $removed_attendees AS $attendee ) {
712 $email = preg_replace(
'/^mailto:/i',
'', $attendee->Value() );
713 $schedule_target =
new Principal(
'email',$email);
714 if ( $schedule_target->Exists() ) {
717 $sql =
'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
718 $sql .=
'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
719 $sql .=
'AND uid=? LIMIT 1';
720 $qry =
new AwlQuery($sql,$schedule_target->user_no(), $uid);
721 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
722 dbg_error_log(
'PUT',
"Could not find event in attendee's calendars" );
723 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
725 $row = $qry->Fetch();
728 if ($attendee_calendar->IsCalendar()) {
729 dbg_error_log(
'PUT',
"found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
731 dbg_error_log(
'PUT',
'could not find the event in any calendar, using schedule-default-calendar');
732 $attendee_calendar =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-default-calendar')));
734 dbg_error_log(
'PUT',
"marking %s's instance of the event to show that the user's invitation has been revoked", $schedule_target->username());
735 $this_resource =
new vCalendar($row->caldav_data);
736 $these_events = $this_resource->GetComponents(
"VEVENT");
737 $this_event = $these_events[0];
738 $properties[] =
new vProperty(
"DESCRIPTION:Your invitation to this event has been revoked." );
739 $this_event->SetProperties( $properties,
'DESCRIPTION' );
740 $properties[] =
new vProperty(
"STATUS:CANCELLED" );
741 $this_event->SetProperties( $properties,
'STATUS' );
742 $this_event->SetProperties( null,
'ATTENDEE' );
743 $this_resource->SetComponents($these_events,
"VEVENT");
744 $this_schedule_request = clone($this_resource);
745 $this_schedule_request->AddProperty(
'METHOD',
'REQUEST');
746 $attendee_inbox =
new WritableCollection(array(
'path' => $schedule_target->internal_url(
'schedule-inbox')));
747 if ( ! $attendee_inbox->HavePrivilegeTo(
'schedule-deliver-invite') ) {
750 else if ( $attendee_inbox->WriteCalendarMember($this_schedule_request,
false) !== false ) {
752 if ( $attendee_calendar->WriteCalendarMember($this_resource,
false) === false ) {
753 dbg_error_log(
'ERROR',
'Could not write updated calendar member');
754 trace_bug(
'Failed to write scheduling resource.');
761 return $scheduling_actions;
774 function import_collection( $import_content, $user_no, $path, $caldav_context, $appending =
false ) {
777 if ( ! ini_get(
'open_basedir') && (isset($c->dbg[
'ALL']) || isset($c->dbg[
'put'])) ) {
778 $fh = fopen(
'/var/log/davical/PUT-2.debug',
'w');
780 fwrite($fh,$import_content);
785 if ( preg_match(
'{^begin:(vcard|vcalendar)}i', $import_content, $matches) ) {
786 if ( strtoupper($matches[1]) ==
'VCARD' )
787 import_addressbook_collection( $import_content, $user_no, $path, $caldav_context, $appending );
788 elseif ( strtoupper($matches[1]) ==
'VCALENDAR' )
789 import_calendar_collection( $import_content, $user_no, $path, $caldav_context, $appending );
792 $cache = getCacheInstance();
793 $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}
', '/
', $path);
794 $cache->delete( $cache_ns, null );
797 dbg_error_log('PUT
', 'Can only
import files which are VCARD or VCALENDAR
');
809 function import_addressbook_collection( $vcard_content, $user_no, $path, $caldav_context, $appending = false ) {
811 // We hack this into an enclosing component because vComponent only expects a single root component
812 $addressbook = new vComponent("BEGIN:ADDRESSES\r\n".$vcard_content."\r\nEND:ADDRESSES\r\n");
814 require_once('vcard.php
');
816 $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name
';
817 $qry = new AwlQuery( $sql, array( ':dav_name
' => $path) );
818 if ( ! $qry->Exec('PUT
',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error in:
'.$sql );
819 if ( ! $qry->rows() == 1 ) {
820 dbg_error_log( 'ERROR
', ' PUT: Collection does not exist at
"%s" for user %d
', $path, $user_no );
821 rollback_on_error( $caldav_context, $user_no, $path, sprintf('Error: Collection does not exist at
"%s" for user %d
', $path, $user_no ));
823 $collection = $qry->Fetch();
824 $collection_id = $collection->collection_id;
826 // Fetch the current collection data
827 $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id
', array(
828 ':collection_id
' => $collection_id
830 $current_data = array();
831 while( $row = $qry->Fetch() )
832 $current_data[$row->dav_name] = $row->caldav_data;
834 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
835 $base_params = array(
836 ':collection_id
' => $collection_id,
837 ':session_user
' => $session->user_no,
838 ':caldav_type
' => 'VCARD
'
841 $dav_data_insert = <<<EOSQL
842 INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
843 VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id )
846 $dav_data_update = <<<EOSQL
847 UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
848 modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
852 $resources = $addressbook->GetComponents();
853 if ( count($resources) > 0 )
854 $qry->QDo('SELECT new_sync_token(0,
'.$collection_id.')
');
856 foreach( $resources AS $k => $resource ) {
857 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
859 $vcard = new vCard( $resource->Render() );
861 $uid = $vcard->GetPValue('UID
');
864 $vcard->AddProperty('UID
',$uid);
867 $last_modified = $vcard->GetPValue('REV
');
868 if ( empty($last_modified) ) {
869 $last_modified = gmdate( 'Ymd\THis\Z
' );
870 $vcard->AddProperty('REV
',$last_modified);
873 $created = $vcard->GetPValue('X-CREATED
');
874 if ( empty($last_modified) ) {
875 $created = gmdate( 'Ymd\THis\Z
' );
876 $vcard->AddProperty('X-CREATED
',$created);
879 $rendered_card = $vcard->Render();
881 // We don't allow any of &?\/@%+: in the UID to appear in the path, but anything
else is fair game.
882 $dav_name = sprintf(
'%s%s.vcf', $path, preg_replace(
'{[&?\\/@%+:]}',
'',$uid) );
884 $dav_data_params = $base_params;
885 $dav_data_params[
':user_no'] = $user_no;
886 $dav_data_params[
':dav_name'] = $dav_name;
887 $dav_data_params[
':etag'] = md5($rendered_card);
888 $dav_data_params[
':dav_data'] = $rendered_card;
889 $dav_data_params[
':modified'] = $last_modified;
890 $dav_data_params[
':created'] = $created;
894 if ( isset($current_data[$dav_name]) ) {
895 if ( $rendered_card == $current_data[$dav_name] ) {
896 unset($current_data[$dav_name]);
900 unset($current_data[$dav_name]);
906 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
909 if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
910 rollback_on_error( $caldav_context, $user_no, $path,
'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
913 $qry->QDo(
'SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $dav_name));
914 if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
915 $dav_id = $row->dav_id;
918 $vcard->Write( $row->dav_id, !$inserting );
920 $qry->QDo(
"SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(
':dav_name' => $dav_name ) );
922 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
925 if ( !$appending && count($current_data) > 0 ) {
926 $params = array(
':collection_id' => $collection_id );
927 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
928 foreach( $current_data AS $dav_name => $data ) {
929 $params[
':dav_name'] = $dav_name;
930 $qry->QDo(
'DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
931 $qry->QDo(
'SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
933 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
936 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
937 if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on COMMIT');
952 function import_calendar_collection( $ics_content, $user_no, $path, $caldav_context, $appending =
false ) {
953 global $c, $session, $tz_regex;
954 $calendar =
new vComponent($ics_content);
955 $timezones = $calendar->GetComponents(
'VTIMEZONE',
true);
956 $components = $calendar->GetComponents(
'VTIMEZONE',
false);
961 if ( isset($_GET[
'after']) ) {
962 $after = $_GET[
'after'];
963 if ( strtoupper(substr($after, 0, 1)) ==
'P' || strtoupper(substr($after, 0, 1)) ==
'-P' ) {
965 $duration = $duration->asSeconds();
966 $after = time() - (abs($duration));
970 $after = $after->epoch();
974 $displayname = $calendar->GetPValue(
'X-WR-CALNAME');
975 if ( !$appending && isset($displayname) ) {
976 $sql =
'UPDATE collection SET dav_displayname = :displayname WHERE dav_name = :dav_name';
977 $qry =
new AwlQuery( $sql, array(
':displayname' => $displayname,
':dav_name' => $path) );
978 if ( ! $qry->Exec(
'PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on: '.$sql );
983 foreach( $timezones AS $k => $tz ) {
984 $tz_ids[$tz->GetPValue(
'TZID')] = $k;
988 $resources = array();
989 foreach( $components AS $k => $comp ) {
990 $uid = $comp->GetPValue(
'UID');
991 if ( $uid == null || $uid ==
'' ) {
993 $comp->AddProperty(
'UID',$uid);
994 dbg_error_log(
'LOG WARN',
' PUT: New collection resource does not have a UID - we assign one!' );
996 if ( !isset($resources[$uid]) ) $resources[$uid] = array();
997 $resources[$uid][] = $comp;
1000 $tzid = GetTZID($comp);
1001 if ( !empty($tzid) && !isset($resources[$uid][$tzid]) && isset($tz_ids[$tzid]) ) {
1002 $resources[$uid][$tzid] = $timezones[$tz_ids[$tzid]];
1007 $sql =
'SELECT * FROM collection WHERE dav_name = :dav_name';
1008 $qry =
new AwlQuery( $sql, array(
':dav_name' => $path) );
1009 if ( ! $qry->Exec(
'PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path,
'Database error on: '.$sql );
1010 if ( ! $qry->rows() == 1 ) {
1011 dbg_error_log(
'ERROR',
' PUT: Collection does not exist at "%s" for user %d', $path, $user_no );
1012 rollback_on_error( $caldav_context, $user_no, $path, sprintf(
'Error: Collection does not exist at "%s" for user %d', $path, $user_no ));
1014 $collection = $qry->Fetch();
1015 $collection_id = $collection->collection_id;
1018 $qry->QDo(
'SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array(
1019 ':collection_id' => $collection_id
1021 $current_data = array();
1022 while( $row = $qry->Fetch() )
1023 $current_data[$row->dav_name] = $row->caldav_data;
1025 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
1026 $base_params = array(
':collection_id' => $collection_id );
1028 $dav_data_insert = <<<EOSQL
1029 INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
1030 VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id )
1033 $dav_data_update = <<<EOSQL
1034 UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
1035 modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
1038 $calitem_insert = <<<EOSQL
1039 INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
1040 description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id )
1041 VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, :dtstart,
##dtend##, :summary, :location, :class, :transp,
1042 :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, :collection_id)
1045 $calitem_update = <<<EOSQL
1046 UPDATE calendar_item SET user_no=:user_no, dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
1047 dtstart=:dtstart, dtend=##dtend##, summary=:summary, location=:location,
1048 class=:
class, transp=:transp, description=:description, rrule=:rrule,
1049 tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
1050 due=:due, percent_complete=:percent_complete, status=:status
1051 WHERE collection_id=:collection_id AND dav_name=:dav_name
1055 if ( count($resources) > 0 )
1056 $qry->QDo(
'SELECT new_sync_token(0,'.$collection_id.
')');
1058 foreach( $resources AS $uid => $resource ) {
1061 $vcal =
new vCalendar();
1062 $vcal->SetComponents($resource);
1063 $icalendar = $vcal->Render();
1064 $dav_name = sprintf(
'%s%s.ics', $path, preg_replace(
'{[&?\\/@%+:]}',
'',$uid) );
1066 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
1069 $first = $resource[0];
1071 $dav_data_params = $base_params;
1072 $dav_data_params[
':user_no'] = $user_no;
1074 $dav_data_params[
':dav_name'] = $dav_name;
1075 $dav_data_params[
':etag'] = md5($icalendar);
1076 $calitem_params = $dav_data_params;
1077 $dav_data_params[
':dav_data'] = $icalendar;
1078 $dav_data_params[
':caldav_type'] = $first->GetType();
1079 $dav_data_params[
':session_user'] = $session->user_no;
1081 $dtstart = $first->GetPValue(
'DTSTART');
1082 $calitem_params[
':dtstart'] = $dtstart;
1083 if ( (!isset($dtstart) || $dtstart ==
'') && $first->GetPValue(
'DUE') !=
'' ) {
1084 $dtstart = $first->GetPValue(
'DUE');
1085 if ( isset($after) ) $dtstart_date =
new RepeatRuleDateTime($first->GetProperty(
'DUE'));
1087 else if ( isset($after) ) {
1091 $calitem_params[
':rrule'] = $first->GetPValue(
'RRULE');
1094 if ( isset($after) && empty($calitem_params[
':rrule']) && $dtstart_date->epoch() < $after )
continue;
1098 if ( isset($current_data[$dav_name]) ) {
1099 if ( $icalendar == $current_data[$dav_name] ) {
1100 if ( $after == null ) {
1101 unset($current_data[$dav_name]);
1106 unset($current_data[$dav_name]);
1113 if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
1114 rollback_on_error( $caldav_context, $user_no, $path,
'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
1117 $qry->QDo(
'SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $dav_data_params[
':dav_name']));
1118 if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
1119 $dav_id = $row->dav_id;
1122 $dtend = $first->GetPValue(
'DTEND');
1123 if ( isset($dtend) && $dtend !=
'' ) {
1124 dbg_error_log(
'PUT',
' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue(
'DURATION') );
1125 $calitem_params[
':dtend'] = $dtend;
1130 if ( $first->GetPValue(
'DURATION') !=
'' AND $dtstart !=
'' ) {
1131 $duration = trim(preg_replace(
'#[PT]#',
' ', $first->GetPValue(
'DURATION') ));
1132 if ( $duration ==
'' ) $duration =
'0 seconds';
1133 $dtend =
'(:dtstart::timestamp with time zone + :duration::interval)';
1134 $calitem_params[
':duration'] = $duration;
1136 elseif ( $first->GetType() ==
'VEVENT' ) {
1150 $dtstart_prop = $first->GetProperty(
'DTSTART');
1151 if ( empty($dtstart_prop) ) {
1152 dbg_error_log(
'PUT',
'Invalid VEVENT without DTSTART, UID="%s" in collection %d', $uid, $collection_id);
1155 $value_type = $dtstart_prop->GetParameterValue(
'VALUE');
1156 dbg_error_log(
'PUT',
'DTSTART without DTEND. DTSTART value type is %s', $value_type );
1157 if ( isset($value_type) && $value_type ==
'DATE' )
1158 $dtend =
'(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1160 $dtend =
':dtstart';
1164 $last_modified = $first->GetPValue(
'LAST-MODIFIED');
1165 if ( !isset($last_modified) || $last_modified ==
'' ) $last_modified = gmdate(
'Ymd\THis\Z' );
1166 $calitem_params[
':modified'] = $last_modified;
1168 $dtstamp = $first->GetPValue(
'DTSTAMP');
1169 if ( empty($dtstamp) ) $dtstamp = $last_modified;
1170 $calitem_params[
':dtstamp'] = $dtstamp;
1173 $class = ($collection->public_events_only ==
't' ?
'PUBLIC' : $first->GetPValue(
'CLASS') );
1174 if ( !isset($class) || $class ==
'' ) $class =
'PUBLIC';
1175 $calitem_params[
':class'] = $class;
1179 $tzid = GetTZID($first);
1180 if ( !empty($tzid) && !empty($resource[$tzid]) ) {
1181 $tz = $resource[$tzid];
1182 $olson = $vcal->GetOlsonName($tz);
1183 dbg_error_log(
'PUT',
' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson :
'') );
1184 if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1185 dbg_error_log(
'PUT',
' Setting timezone to %s', $olson );
1186 $qry->QDo(
'SET TIMEZONE TO \''.$olson.
"'" );
1187 $last_olson = $olson;
1189 $params = array(
':tzid' => $tzid);
1190 $qry =
new AwlQuery(
'SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1191 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1192 $params[
':olson_name'] = $olson;
1193 $params[
':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1194 $params[
':last_modified'] = (isset($tz) ? $tz->GetPValue(
'LAST-MODIFIED') : null );
1195 if ( empty($params[
':last_modified']) ) {
1196 $params[
':last_modified'] = gmdate(
'Ymd\THis\Z');
1198 $qry->QDo(
'INSERT INTO timezones (tzid, olson_name, active, vtimezone, last_modified) VALUES(:tzid,:olson_name,false,:vtimezone,:last_modified)', $params );
1202 $tz = $olson = $tzid = null;
1205 $sql = str_replace(
'##dtend##', $dtend, ($inserting ? $calitem_insert : $calitem_update) );
1206 $calitem_params[
':tzid'] = $tzid;
1207 $calitem_params[
':uid'] = $first->GetPValue(
'UID');
1208 $calitem_params[
':summary'] = $first->GetPValue(
'SUMMARY');
1209 $calitem_params[
':location'] = $first->GetPValue(
'LOCATION');
1210 $calitem_params[
':transp'] = $first->GetPValue(
'TRANSP');
1211 $calitem_params[
':description'] = $first->GetPValue(
'DESCRIPTION');
1212 $calitem_params[
':url'] = $first->GetPValue(
'URL');
1213 $calitem_params[
':priority'] = $first->GetPValue(
'PRIORITY');
1214 $calitem_params[
':due'] = $first->GetPValue(
'DUE');
1215 $calitem_params[
':percent_complete'] = $first->GetPValue(
'PERCENT-COMPLETE');
1216 $calitem_params[
':status'] = $first->GetPValue(
'STATUS');
1219 $created = $first->GetPValue(
'CREATED');
1220 if ( $created ==
'00001231T000000Z' ) $created =
'20001231T000000Z';
1221 $calitem_params[
':created'] = $created;
1225 if ( !$qry->QDo($sql,$calitem_params) ) rollback_on_error( $caldav_context, $user_no, $path);
1227 write_alarms($dav_id, $first);
1228 write_attendees($dav_id, $vcal);
1230 $qry->QDo(
"SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(
':dav_name' => $dav_name ) );
1232 do_scheduling_requests( $vcal,
true );
1233 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1236 if ( !$appending && count($current_data) > 0 ) {
1237 $params = array(
':collection_id' => $collection_id );
1238 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
1239 foreach( $current_data AS $dav_name => $data ) {
1240 $params[
':dav_name'] = $dav_name;
1241 $qry->QDo(
'DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
1242 $qry->QDo(
'SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
1244 if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1247 if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
1248 if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path);
1261 function write_alarms( $dav_id, vComponent $ical ) {
1262 $qry =
new AwlQuery(
'DELETE FROM calendar_alarm WHERE dav_id = '.$dav_id );
1263 $qry->Exec(
'PUT',__LINE__,__FILE__);
1265 $alarms = $ical->GetComponents(
'VALARM');
1266 if ( count($alarms) < 1 )
return;
1268 $qry->SetSql(
'INSERT INTO calendar_alarm ( dav_id, action, trigger, summary, description, component, next_trigger )
1269 VALUES( '.$dav_id.
', :action, :trigger, :summary, :description, :component,
1270 :related::timestamp with time zone + :related_trigger::interval )' );
1272 foreach( $alarms AS $v ) {
1273 $trigger = array_merge($v->GetProperties(
'TRIGGER'));
1274 if ( $trigger == null )
continue;
1275 $trigger = $trigger[0];
1277 $related_trigger =
'0M';
1278 $trigger_type = $trigger->GetParameterValue(
'VALUE');
1279 if ( !isset($trigger_type) || $trigger_type ==
'DURATION' ) {
1280 switch ( $trigger->GetParameterValue(
'RELATED') ) {
1281 case 'DTEND': $related = $ical->GetProperty(
'DTEND');
break;
1282 case 'DUE': $related = $ical->GetProperty(
'DUE');
break;
1283 default: $related = $ical->GetProperty(
'DTSTART');
1285 $duration = $trigger->Value();
1286 if ( !preg_match(
'{^-?P(:?\d+W)?(:?\d+D)?(:?T(:?\d+H)?(:?\d+M)?(:?\d+S)?)?$}', $duration ) )
continue;
1287 $minus = (substr($duration,0,1) ==
'-');
1288 $related_trigger = trim(preg_replace(
'#[PT-]#',
' ', $duration ));
1289 if ($related_trigger ==
'') $related_trigger =
'0 seconds';
1291 $related_trigger = preg_replace(
'{(\d+[WDHMS])}',
'-$1 ', $related_trigger );
1294 $related_trigger = preg_replace(
'{(\d+[WDHMS])}',
'$1 ', $related_trigger );
1297 else if ( $trigger_type ==
'DATE-TIME' ) {
1298 $related = $trigger;
1301 if (
false === strtotime($trigger->Value()) )
continue;
1302 $related = $trigger;
1305 $qry->Bind(
':action', $v->GetPValue(
'ACTION'));
1306 $qry->Bind(
':trigger', $trigger->Render());
1307 $qry->Bind(
':summary', $v->GetPValue(
'SUMMARY'));
1308 $qry->Bind(
':description', $v->GetPValue(
'DESCRIPTION'));
1309 $qry->Bind(
':component', $v->Render());
1310 $qry->Bind(
':related', $related_date->UTC() );
1311 $qry->Bind(
':related_trigger', $related_trigger );
1312 $qry->Exec(
'PUT',__LINE__,__FILE__);
1324 function write_attendees( $dav_id, vCalendar $ical ) {
1325 $qry =
new AwlQuery(
'DELETE FROM calendar_attendee WHERE dav_id = '.$dav_id );
1326 $qry->Exec(
'PUT',__LINE__,__FILE__);
1328 $attendees = $ical->GetAttendees();
1329 if ( count($attendees) < 1 )
return;
1331 $qry->SetSql(
'INSERT INTO calendar_attendee ( dav_id, status, partstat, cn, attendee, role, rsvp, property )
1332 VALUES( '.$dav_id.
', :status, :partstat, :cn, :attendee, :role, :rsvp, :property )' );
1334 $processed = array();
1335 foreach( $attendees AS $v ) {
1336 $attendee = $v->Value();
1337 if ( isset($processed[$attendee]) ) {
1338 dbg_error_log(
'LOG',
'Duplicate attendee "%s" in resource "%d"', $attendee, $dav_id );
1339 dbg_error_log(
'LOG',
'Original: "%s"', $processed[$attendee] );
1340 dbg_error_log(
'LOG',
'Duplicate: "%s"', $v->Render() );
1343 $qry->Bind(
':attendee', $attendee );
1344 $qry->Bind(
':status', $v->GetParameterValue(
'STATUS') );
1345 $qry->Bind(
':partstat', $v->GetParameterValue(
'PARTSTAT') );
1346 $qry->Bind(
':cn', $v->GetParameterValue(
'CN') );
1347 $qry->Bind(
':role', $v->GetParameterValue(
'ROLE') );
1348 $qry->Bind(
':rsvp', $v->GetParameterValue(
'RSVP') );
1349 $qry->Bind(
':property', $v->Render() );
1350 $qry->Exec(
'PUT',__LINE__,__FILE__);
1351 $processed[$attendee] = $v->Render();
1372 function write_resource(
DAVResource $resource, $caldav_data,
DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action=
true, $weak_etag=null ) {
1373 global $tz_regex, $session;
1376 $user_no = $collection->
user_no();
1377 $vcal =
new vCalendar( $caldav_data );
1378 $resources = $vcal->GetComponents(
'VTIMEZONE',
false);
1379 if ( !isset($resources[0]) ) {
1380 $resource_type =
'Unknown';
1382 rollback_on_error( $caldav_context, $user_no, $path, translate(
'No calendar content'), 412 );
1386 $first = $resources[0];
1387 if ( !($first instanceof vComponent) ) {
1388 print $vcal->Render();
1389 fatal(
'This is not a vComponent!');
1391 $resource_type = $first->GetType();
1396 $qry =
new AwlQuery();
1399 $calitem_params = array(
1403 if ( $put_action_type ==
'INSERT' ) {
1404 $qry->QDo(
'SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data');
1407 $qry->QDo(
'SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(
':dav_name' => $path));
1409 if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
1411 trace_bug(
'No dav_id for "%s" on %s!!!', $path, ($put_action_type ==
'INSERT' ?
'create':
'update'));
1412 rollback_on_error( $caldav_context, $user_no, $path);
1415 $dav_id = $row->dav_id;
1416 $old_dav_data = $row->caldav_data;
1419 if ($old_dav_data) {
1421 $oldvcal =
new vCalendar( $old_dav_data );
1422 $oldr = $oldvcal->GetComponents(
'VTIMEZONE',
false);
1423 $oldfirst = $oldr[0];
1424 $dtstart = $first->GetPValue(
'DTSTART');
1425 $olddtstart = $oldfirst->GetPValue(
'DTSTART');
1426 if (strcmp($dtstart, $olddtstart)) $modified =
true;
1427 $dtend = $first->GetPValue(
'DTEND');
1428 $olddtend = $oldfirst->GetPValue(
'DTEND');
1429 if (strcmp($dtend, $olddtend)) $modified =
true;
1430 $duration = $first->GetPValue(
'DURATION');
1431 $oldduration = $oldfirst->GetPValue(
'DURATION');
1432 $organizer = $vcal->GetOrganizer();
1433 if (strcmp($duration, $oldduration)) $modified =
true;
1434 if (($modified ==
true) && !($organizer ===
false || empty($organizer))) {
1435 dbg_error_log(
'PUT',
"event time attributes have been modified, reset all attendees' PARTSTAT to NEEDS-ACTION.");
1436 $organizer_email = preg_replace(
'/^mailto:/i',
'', $organizer->Value() );
1437 $attendees = $first->GetProperties(
'ATTENDEE');
1438 foreach( $attendees AS $attendee ) {
1439 $attendee_email = preg_replace(
'/^mailto:/',
'', $attendee->Value() );
1440 if ( $attendee_email != $organizer_email ) {
1441 $attendee->SetParameterValue(
"PARTSTAT",
"NEEDS-ACTION");
1442 $attendee->SetParameterValue(
"SCHEDULE-STATUS",
"1.0");
1445 $first->SetProperties($attendees,
'ATTENDEE');
1446 $caldav_data = $vcal->Render(null,
true);
1447 dbg_error_log(
'PUT',
"event time attributes have been modified, reset all attendees' PARTSTAT completed.");
1451 $dav_params = array(
1453 ':dav_data' => $caldav_data,
1454 ':caldav_type' => $resource_type,
1455 ':session_user' => $author,
1456 ':weak_etag' => $weak_etag
1459 $dav_params[
':dav_id'] = $dav_id;
1460 $calitem_params[
':dav_id'] = $dav_id;
1463 if ( $first->GetType() ==
'VTODO' ) $due = $first->GetPValue(
'DUE');
1464 $calitem_params[
':due'] = $due;
1465 $dtstart = $first->GetPValue(
'DTSTART');
1466 if ( empty($dtstart) ) $dtstart = $due;
1467 if (preg_match(
"/^1[0-8][0-9][0-9][01][0-9][0-3][0-9]$/", $dtstart))
1468 $dtstart = $dtstart .
"T000000Z";
1469 $calitem_params[
':dtstart'] = $dtstart;
1471 $dtend = $first->GetPValue(
'DTEND');
1472 if ( isset($dtend) && $dtend !=
'' ) {
1473 dbg_error_log(
'PUT',
' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue(
'DURATION') );
1474 if (preg_match(
"/^1[0-8][0-9][0-9][01][0-9][0-3][0-9]$/", $dtend))
1475 $dtend = $dtend .
"T000000Z";
1476 $calitem_params[
':dtend'] = $dtend;
1482 if ( $first->GetPValue(
'DURATION') !=
'' AND $dtstart !=
'' ) {
1483 $duration = trim(preg_replace(
'#[PT]#',
' ', $first->GetPValue(
'DURATION') ));
1484 if ( $duration ==
'' ) $duration =
'0 seconds';
1485 $dtend =
'(:dtstart::timestamp with time zone + :duration::interval)';
1486 $calitem_params[
':duration'] = $duration;
1488 elseif ( $first->GetType() ==
'VEVENT' ) {
1502 $dtstart_prop = $first->GetProperty(
'DTSTART');
1503 $value_type = $dtstart_prop->GetParameterValue(
'VALUE');
1504 dbg_error_log(
'PUT',
'DTSTART without DTEND. DTSTART value type is %s', $value_type );
1505 if ( isset($value_type) && $value_type ==
'DATE' )
1506 $dtend =
'(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1508 $dtend =
':dtstart';
1512 $dtstamp = $first->GetPValue(
'DTSTAMP');
1513 if ( !isset($dtstamp) || $dtstamp ==
'' ) {
1515 $dtstamp = gmdate(
'Ymd\THis\Z' );
1517 $calitem_params[
':dtstamp'] = $dtstamp;
1519 $last_modified = $first->GetPValue(
'LAST-MODIFIED');
1520 if ( !isset($last_modified) || $last_modified ==
'' ) $last_modified = $dtstamp;
1521 $dav_params[
':modified'] = $last_modified;
1522 $calitem_params[
':modified'] = $last_modified;
1524 $created = $first->GetPValue(
'CREATED');
1525 if ( $created ==
'00001231T000000Z' ) $created =
'20001231T000000Z';
1527 $class = $first->GetPValue(
'CLASS');
1530 if ( public_events_only($user_no, $path) ) {
1539 if ( !isset($class) || $class ==
'' ) {
1542 $calitem_params[
':class'] = $class;
1545 $last_olson =
'Turkmenikikamukau';
1546 $tzid = GetTZID($first);
1547 if ( !empty($tzid) ) {
1548 $timezones = $vcal->GetComponents(
'VTIMEZONE');
1549 foreach( $timezones AS $k => $tz ) {
1550 if ( $tz->GetPValue(
'TZID') != $tzid ) {
1554 dbg_error_log(
'ERROR',
' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue(
'TZID'), $tzid );
1557 $olson = olson_from_tzstring($tzid);
1558 if ( empty($olson) ) {
1559 $olson = $tz->GetPValue(
'X-LIC-LOCATION');
1560 if ( !empty($olson) ) {
1561 $olson = olson_from_tzstring($olson);
1566 dbg_error_log(
'PUT',
' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson :
'') );
1567 if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1568 dbg_error_log(
'PUT',
' Setting timezone to %s', $olson );
1569 if ( $olson !=
'' ) {
1570 $qry->QDo(
'SET TIMEZONE TO \''.$olson.
"'" );
1572 $last_olson = $olson;
1574 $params = array(
':tzid' => $tzid);
1575 $qry =
new AwlQuery(
'SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1576 if ( $qry->Exec(
'PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1577 $params[
':olson_name'] = $olson;
1578 $params[
':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1579 $qry->QDo(
'INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params );
1581 if ( !isset($olson) || $olson ==
'' ) $olson = $tzid;
1585 $qry->QDo(
'SELECT new_sync_token(0,'.$collection_id.
')');
1587 $calitem_params[
':tzid'] = $tzid;
1588 $calitem_params[
':uid'] = $first->GetPValue(
'UID');
1589 $calitem_params[
':summary'] = $first->GetPValue(
'SUMMARY');
1590 $calitem_params[
':location'] = $first->GetPValue(
'LOCATION');
1591 $calitem_params[
':transp'] = $first->GetPValue(
'TRANSP');
1592 $calitem_params[
':description'] = $first->GetPValue(
'DESCRIPTION');
1593 $calitem_params[
':rrule'] = $first->GetPValue(
'RRULE');
1594 $calitem_params[
':url'] = $first->GetPValue(
'URL');
1595 $calitem_params[
':priority'] = $first->GetPValue(
'PRIORITY');
1596 $calitem_params[
':percent_complete'] = $first->GetPValue(
'PERCENT-COMPLETE');
1597 $calitem_params[
':status'] = $first->GetPValue(
'STATUS');
1600 $vcal->Render(null,
true);
1603 if ( do_scheduling_requests($vcal, ($put_action_type ==
'INSERT'), $old_dav_data ) ) {
1604 $dav_params[
':dav_data'] = $vcal->Render(null,
true);
1609 if ( !isset($dav_params[
':modified']) ) $dav_params[
':modified'] =
'now';
1610 if ( $put_action_type ==
'INSERT' ) {
1611 $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 )
1612 VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )';
1613 $dav_params[
':collection_id'] = $collection_id;
1614 $dav_params[
':user_no'] = $user_no;
1615 $dav_params[
':dav_name'] = $path;
1616 $dav_params[
':created'] = (isset($created) && $created !=
'' ? $created : $dtstamp);
1619 $sql =
'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
1620 modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id';
1622 $qry =
new AwlQuery($sql,$dav_params);
1623 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) ) {
1624 fatal(
'Insert into calendar_item failed...');
1625 rollback_on_error( $caldav_context, $user_no, $path);
1630 if ( $put_action_type ==
'INSERT' ) {
1632 INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
1633 dtstart, dtend, summary, location,
class, transp,
1634 description, rrule, tz_id, last_modified, url, priority,
1635 created, due, percent_complete, status, collection_id )
1636 VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp,
1637 :dtstart, $dtend, :summary, :location, :class, :transp,
1638 :description, :rrule, :tzid, :modified, :url, :priority,
1639 :created, :due, :percent_complete, :status, :collection_id )
1642 $calitem_params[':collection_id'] = $collection_id;
1643 $calitem_params[':user_no'] = $user_no;
1644 $calitem_params[':dav_name'] = $path;
1645 $calitem_params[':created'] = $dav_params[':created'];
1649 UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
1650 dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location,
1651 class=:
class, transp=:transp, description=:description, rrule=:rrule,
1652 tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
1653 due=:due, percent_complete=:percent_complete, status=:status
1654 WHERE dav_id=:dav_id
1659 write_alarms($dav_id, $first);
1660 write_attendees($dav_id, $vcal);
1662 if ( $log_action && function_exists(
'log_caldav_action') ) {
1663 log_caldav_action( $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1665 else if ( $log_action ) {
1666 dbg_error_log(
'PUT',
'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
1667 $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1670 $qry =
new AwlQuery( $sql, $calitem_params );
1671 if ( !$qry->Exec(
'PUT',__LINE__,__FILE__) ) {
1672 rollback_on_error( $caldav_context, $user_no, $path);
1675 $qry->QDo(
"SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(
':dav_name' => $path ) );
1678 if ( function_exists(
'post_commit_action') ) {
1679 post_commit_action( $put_action_type, $first->GetPValue(
'UID'), $user_no, $collection_id, $path );
1683 $cache = getCacheInstance();
1684 $cache_ns =
'collection-'.preg_replace(
'{/[^/]*$}',
'/', $path);
1685 $cache->delete( $cache_ns, null );
1687 dbg_error_log(
'PUT',
'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
1703 function simple_write_resource( $path, $caldav_data, $put_action_type, $write_action_log =
false ) {
1710 $etag = md5($caldav_data);
1711 $collection_path = preg_replace(
'#/[^/]*$#',
'/', $path );
1714 return write_resource( $dav_resource, $caldav_data, $collection, $session->
user_no, $etag, $put_action_type,
false, $write_action_log );
IsSchedulingCollection($type= 'any')