1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: 13: 14: 15: 16:
17:
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');
25:
26: $bad_events = null;
27:
28: 29: 30:
31: $GLOBALS['tz_regex'] = ':^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Etc|Europe|Indian|Mexico|Mideast|Pacific|US)/[a-z_]+$:i';
32:
33: 34: 35: 36: 37: 38: 39: 40:
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;
49: }
50: else {
51: global $request;
52: $request->DoResponse( $error_no, $message );
53: }
54:
55: }
56:
57: $c->messages[] = sprintf(translate('Status: %d, Message: %s, User: %d, Path: %s'), $error_no, $message, $user_no, $path);
58:
59: }
60:
61:
62:
63: 64: 65: 66: 67: 68: 69: 70: 71:
72: function controlRequestContainer( $username, $user_no, $path, $caldav_context, $public = null ) {
73: global $c, $request, $bad_events;
74:
75:
76: if ( preg_match( '#^(.*/)([^/]+)$#', $path, $matches ) ) {
77: $request_container = $matches[1];
78: }
79: else {
80:
81: $request_container = $path;
82: }
83:
84: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) {
85: $bad_events = array();
86: }
87:
88: 89: 90:
91: if ( $request_container == "/$username/" ) {
92: 93: 94:
95: dbg_error_log( 'WARN', ' Storing events directly in user\'s base folders is not recommended!');
96: }
97: else {
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 );
102: }
103: if ( !isset($c->readonly_webdav_collections) || $c->readonly_webdav_collections == true ) {
104: if ( $qry->rows() == 0 ) {
105: $request->DoResponse( 405 );
106: }
107: return;
108: }
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];
114: }
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 )';
117: $params = array(
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/>'
125: );
126: $qry->QDo( $sql, $params );
127: }
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 );
136: }
137: }
138: }
139:
140: }
141: }
142:
143:
144: 145: 146: 147: 148: 149:
150: function public_events_only( $user_no, $dav_name ) {
151: global $c;
152:
153: $sql = 'SELECT public_events_only FROM collection WHERE dav_name = :dav_name';
154:
155: $qry = new AwlQuery($sql, array(':dav_name' => $dav_name) );
156:
157: if( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 1 ) {
158: $collection = $qry->Fetch();
159:
160: if ($collection->public_events_only == 't') {
161: return true;
162: }
163: }
164:
165:
166: return false;
167: }
168:
169:
170: 171: 172: 173: 174:
175: function GetTZID( vComponent $comp ) {
176: $p = $comp->GetProperty('DTSTART');
177: if ( !isset($p) && $comp->GetType() == 'VTODO' ) {
178: $p = $comp->GetProperty('DUE');
179: }
180: if ( !isset($p) ) return null;
181: return $p->GetParameterValue('TZID');
182: }
183:
184:
185: 186: 187: 188:
189: function handle_schedule_request( $ical ) {
190: global $c, $session, $request;
191: $resources = $ical->GetComponents('VTIMEZONE',false);
192: $ic = $resources[0];
193: $etag = md5 ( $request->raw_post );
194: $reply = new XMLDocument( array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C" ) );
195: $responses = array();
196:
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 ) {
202: $attendees[] = $v;
203: }
204: }
205: dbg_error_log( "PUT", "Attempting to deliver scheduling request for %d attendees", count($attendees) );
206:
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" );
211: continue;
212: }
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 );
215: continue;
216: }
217:
218: if ( isset($c->enable_auto_schedule) && !$c->enable_auto_schedule ) {
219:
220:
221: $attendee->SetParameterValue ('SCHEDULE-STATUS','5.3;No scheduling support for user');
222: continue;
223: }
224:
225: dbg_error_log( "PUT", "Delivering to %s", $attendee_email );
226:
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');
230: continue;
231: }
232: $deliver_path = $attendee_principal->internal_url('schedule-inbox');
233:
234: $ar = new DAVResource($deliver_path);
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' ) );
239:
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"');
244: }
245:
246:
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');
257: }
258:
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');
263: $ar = new DAVResource($deliver_path);
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 );
267:
268: header('ETag: "'. $etag . '"' );
269: header('Schedule-Tag: "'.$etag . '"' );
270: $request->DoResponse( 201, 'Created' );
271: }
272:
273:
274: 275: 276: 277: 278:
279: function handle_schedule_reply ( vCalendar $ical ) {
280: global $c, $session, $request;
281: $resources = $ical->GetComponents('VTIMEZONE',false);
282: $ic = $resources[0];
283: $etag = md5 ( $request->raw_post );
284: $organizer = $ical->GetOrganizer();
285: $arrayOrganizer = array($organizer);
286:
287: if ( empty( $arrayOrganizer ) ) return false;
288:
289: $attendees = array_merge($arrayOrganizer,$ical->GetAttendees());
290: dbg_error_log( "PUT", "Attempting to deliver scheduling request for %d attendees", count($attendees) );
291:
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" );
300: continue;
301: }
302: $ar = new DAVResource($deliver_path);
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' ) );
306:
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"' );
311: continue;
312: }
313:
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 );
319: }
320: $request->DoResponse( 201, 'Created' );
321: }
322:
323:
324: 325: 326: 327: 328:
329: function do_scheduling_reply( vCalendar $resource, vProperty $organizer ) {
330: global $request;
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 );
335: return false;
336: }
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.' );
343: return false;
344: }
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.' );
349: return false;
350: }
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() ) {
356: $attendee = $v;
357: }
358: }
359: if ( empty($attendee) ) {
360: dbg_error_log( 'PUT', 'Could not find ATTENDEE in VEVENT - giving up on REPLY.' );
361: return false;
362: }
363: $schedule_original = new vCalendar($row->caldav_data);
364: $attendee->SetParameterValue('SCHEDULE-STATUS', '2.0');
365: $schedule_original->UpdateAttendeeStatus($request->principal->email(), clone($attendee) );
366:
367: $collection_path = preg_replace('{/[^/]+$}', '/', $row->dav_name );
368: $segment_name = str_replace($collection_path, '', $row->dav_name );
369: $organizer_calendar = new WritableCollection(array('path' => $collection_path));
370: $organizer_inbox = new WritableCollection(array('path' => $organizer_principal->internal_url('schedule-inbox')));
371:
372: $schedule_reply = GetItip(new vCalendar($schedule_original->Render(null, true)), 'REPLY', $attendee->Value(), array('CUTYPE'=>true, 'SCHEDULE-STATUS'=>true));
373:
374: dbg_error_log( 'PUT', 'Writing scheduling REPLY from %s to %s', $request->principal->email(), $organizer_principal->email() );
375:
376: $response = '3.7';
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());
380: $response = '5.2';
381: }
382: else {
383: if ( ! $organizer_inbox->HavePrivilegeTo('schedule-deliver-reply') ) {
384: $response = '3.8';
385: }
386: else if ( $organizer_inbox->WriteCalendarMember($schedule_reply, false, false, $request->principal->username().$segment_name) !== false ) {
387: $response = '1.2';
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.');
392: }
393: }
394: }
395:
396: $schedule_request = clone($schedule_original);
397: $schedule_request->AddProperty('METHOD', 'REQUEST');
398:
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;
403:
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;
409:
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 );
413: continue;
414: }
415:
416:
417:
418:
419:
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);
423: continue;
424: }
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 );
431: }
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');
437:
438: $schedule_target = new Principal('email',$email);
439: $response = '3.7';
440: if ( $schedule_target->Exists() ) {
441:
442:
443: $r = new DAVResource($row);
444: $attendee_calendar = new WritableCollection(array('path' => $r->parent_path()));
445: if ($attendee_calendar->IsCalendar()) {
446: dbg_error_log( 'PUT', "found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
447: } else {
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')));
450: }
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());
454: $response = '5.2';
455: }
456: else {
457: $attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
458: if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
459: $response = '3.8';
460: }
461: else if ( $attendee_inbox->WriteCalendarMember($schedule_request, false) !== false ) {
462: $response = '1.2';
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.');
467: }
468: }
469: }
470: }
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;
474:
475: $resource->UpdateAttendeeStatus($email, clone($attendee));
476:
477: }
478:
479: return $scheduling_actions;
480: }
481:
482:
483: 484: 485: 486: 487: 488: 489:
490: function do_scheduling_requests( vCalendar $resource, $create, $old_data = null ) {
491: global $request, $c;
492: if ( !isset($request) || (isset($c->enable_auto_schedule) && !$c->enable_auto_schedule) ) return false;
493:
494: if ( ! is_object($resource) ) {
495: trace_bug( 'do_scheduling_requests called with non-object parameter (%s)', gettype($resource) );
496: return false;
497: }
498:
499: $organizer = $resource->GetOrganizer();
500: if ( $organizer === false || empty($organizer) ) {
501: dbg_error_log( 'PUT', 'Event has no organizer - no scheduling required.' );
502: return false;
503: }
504: $organizer_email = preg_replace( '/^mailto:/i', '', $organizer->Value() );
505:
506:
507: $resource->Render(null, true);
508:
509: if ( $request->principal->email() != $organizer_email ) {
510: return do_scheduling_reply($resource,$organizer);
511: }
512:
513:
514: $orig_resource = new vCalendar($resource->Render(null, true));
515:
516: $schedule_request = new vCalendar($resource->Render(null, true));
517: $schedule_request->AddProperty('METHOD', 'REQUEST');
518:
519: $old_attendees = array();
520: if ( !empty($old_data) ) {
521: $old_resource = new vCalendar($old_data);
522: $old_attendees = $old_resource->GetAttendees();
523: }
524: $attendees = $resource->GetAttendees();
525: if ( count($attendees) == 0 && count($old_attendees) == 0 ) {
526: dbg_error_log( 'PUT', 'Event has no attendees - no scheduling required.', count($attendees) );
527: return false;
528: }
529: $removed_attendees = array();
530: foreach( $old_attendees AS $attendee ) {
531: $email = preg_replace( '/^mailto:/i', '', $attendee->Value() );
532: if ( $email == $request->principal->email() ) continue;
533: $removed_attendees[$email] = $attendee;
534: }
535:
536: $uids = $resource->GetPropertiesByPath('/VCALENDAR/*/UID');
537: if ( count($uids) == 0 ) {
538: dbg_error_log( 'PUT', 'No UID in VCALENDAR - giving up on REPLY.' );
539: return false;
540: }
541: $uid = $uids[0]->Value();
542:
543: dbg_error_log( 'PUT', 'Writing scheduling resources for %d attendees', count($attendees) );
544: $scheduling_actions = false;
545: foreach( $attendees AS $attendee ) {
546: $email = preg_replace( '/^mailto:/i', '', $attendee->Value() );
547: if ( $email == $request->principal->email() ) {
548: dbg_error_log( "PUT", "not delivering to owner '%s'", $request->principal->email() );
549: continue;
550: }
551:
552: if ( $create ) {
553: $attendee_is_new = true;
554: }
555: else {
556: $attendee_is_new = !isset($removed_attendees[$email]);
557: if ( !$attendee_is_new ) unset($removed_attendees[$email]);
558: }
559:
560: $agent = $attendee->GetParameterValue('SCHEDULE-AGENT');
561: if ( $agent && $agent != 'SERVER' ) {
562: dbg_error_log( "PUT", "not delivering to %s, schedule agent set to value other than server", $email );
563: continue;
564: }
565: $schedule_target = new Principal('email',$email);
566: $response = '3.7';
567: dbg_error_log( 'PUT', 'Handling scheduling resources for %s on %s which is %s', $email,
568: ($create?'create':'update'), ($attendee_is_new? 'new' : 'an update') );
569: if ( $schedule_target->Exists() ) {
570:
571:
572: $sql = 'SELECT caldav_data.dav_name, caldav_data.caldav_data, caldav_data.collection_id FROM caldav_data JOIN calendar_item USING(dav_id) ';
573: $sql .= 'WHERE caldav_data.collection_id IN (SELECT collection_id FROM collection WHERE is_calendar AND user_no =?) ';
574: $sql .= 'AND uid=? LIMIT 1';
575: $qry = new AwlQuery($sql,$schedule_target->user_no(), $uid);
576: if ( !$qry->Exec('PUT',__LINE__,__FILE__) || $qry->rows() < 1 ) {
577: dbg_error_log( 'PUT', "Could not find event in attendee's calendars" );
578: $attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
579: } else {
580: $row = $qry->Fetch();
581: $r = new DAVResource($row);
582: $attendee_calendar = new WritableCollection(array('path' => $r->parent_path()));
583: if ($attendee_calendar->IsCalendar()) {
584: dbg_error_log( 'PUT', "found the event in attendee's calendar %s", $attendee_calendar->dav_name() );
585: } else {
586: dbg_error_log( 'PUT', 'could not find the event in any calendar, using schedule-default-calendar');
587: $attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
588: }
589: }
590: if ( !$attendee_calendar->Exists() ) {
591: dbg_error_log('ERROR','Default calendar at "%s" does not exist for user "%s"',
592: $attendee_calendar->dav_name(), $schedule_target->username());
593: $response = '5.2';
594: }
595: else {
596: $attendee_inbox = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-inbox')));
597: if ( ! $attendee_inbox->HavePrivilegeTo('schedule-deliver-invite') ) {
598: $response = '3.8';
599: }
600: else if ( $attendee_inbox->WriteCalendarMember($schedule_request, $attendee_is_new) !== false ) {
601: $response = '1.2';
602: if ( $attendee_calendar->WriteCalendarMember($orig_resource, $attendee_is_new) === false ) {
603: dbg_error_log('ERROR','Could not write %s calendar member to %s', ($attendee_is_new?'new':'updated'),
604: $attendee_calendar->dav_name(), $attendee_calendar->dav_name(), $schedule_target->username());
605: trace_bug('Failed to write scheduling resource.');
606: }
607: }
608: }
609: }
610: else {
611: $remote = new iSchedule ();
612: $answer = $remote->sendRequest ( $email, 'VEVENT/REQUEST', $schedule_request->Render() );
613: if ( $answer === false ) {
614: $response = '3.7';
615: }
616: else {
617: foreach ( $answer as $a )
618: {
619: if ( $a === false ) {
620: $response = '3.7';
621: }
622: elseif ( substr( $a, 0, 1 ) >= 1 ) {
623: $response = $a;
624: }
625: else {
626: $response = '2.0';
627: }
628: }
629: }
630: }
631: dbg_error_log( 'PUT', 'Status for attendee <%s> set to "%s"', $attendee->Value(), $response );
632: $attendee->SetParameterValue( 'SCHEDULE-STATUS', $response );
633: $scheduling_actions = true;
634: }
635:
636: if ( !$create ) {
637: foreach( $removed_attendees AS $attendee ) {
638: $schedule_target = new Principal('email',$email);
639: if ( $schedule_target->Exists() ) {
640: $attendee_calendar = new WritableCollection(array('path' => $schedule_target->internal_url('schedule-default-calendar')));
641: }
642: }
643: }
644: return $scheduling_actions;
645: }
646:
647:
648: 649: 650: 651: 652: 653: 654: 655: 656:
657: function import_collection( $import_content, $user_no, $path, $caldav_context, $appending = false ) {
658: global $c;
659:
660: if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put'])) ) {
661: $fh = fopen('/var/log/davical/PUT-2.debug','w');
662: if ( $fh ) {
663: fwrite($fh,$import_content);
664: fclose($fh);
665: }
666: }
667:
668: if ( preg_match( '{^begin:(vcard|vcalendar)}i', $import_content, $matches) ) {
669: if ( strtoupper($matches[1]) == 'VCARD' )
670: import_addressbook_collection( $import_content, $user_no, $path, $caldav_context, $appending );
671: elseif ( strtoupper($matches[1]) == 'VCALENDAR' )
672: import_calendar_collection( $import_content, $user_no, $path, $caldav_context, $appending );
673:
674:
675: $cache = getCacheInstance();
676: $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path);
677: $cache->delete( $cache_ns, null );
678: }
679: else {
680: dbg_error_log('PUT', 'Can only import files which are VCARD or VCALENDAR');
681: }
682: }
683:
684:
685: 686: 687: 688: 689: 690: 691:
692: function import_addressbook_collection( $vcard_content, $user_no, $path, $caldav_context, $appending = false ) {
693: global $c, $session;
694:
695: $addressbook = new vComponent("BEGIN:ADDRESSES\r\n".$vcard_content."\r\nEND:ADDRESSES\r\n");
696:
697: require_once('vcard.php');
698:
699: $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name';
700: $qry = new AwlQuery( $sql, array( ':dav_name' => $path) );
701: if ( ! $qry->Exec('PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error in: '.$sql );
702: if ( ! $qry->rows() == 1 ) {
703: dbg_error_log( 'ERROR', ' PUT: Collection does not exist at "%s" for user %d', $path, $user_no );
704: rollback_on_error( $caldav_context, $user_no, $path, sprintf('Error: Collection does not exist at "%s" for user %d', $path, $user_no ));
705: }
706: $collection = $qry->Fetch();
707: $collection_id = $collection->collection_id;
708:
709:
710: $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array(
711: ':collection_id' => $collection_id
712: ));
713: $current_data = array();
714: while( $row = $qry->Fetch() )
715: $current_data[$row->dav_name] = $row->caldav_data;
716:
717: if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
718: $base_params = array(
719: ':collection_id' => $collection_id,
720: ':session_user' => $session->user_no,
721: ':caldav_type' => 'VCARD'
722: );
723:
724: $dav_data_insert = <<<EOSQL
725: INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
726: VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id )
727: EOSQL;
728:
729: $dav_data_update = <<<EOSQL
730: UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
731: modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
732: EOSQL;
733:
734:
735: $resources = $addressbook->GetComponents();
736: if ( count($resources) > 0 )
737: $qry->QDo('SELECT new_sync_token(0,'.$collection_id.')');
738:
739: foreach( $resources AS $k => $resource ) {
740: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
741:
742: $vcard = new vCard( $resource->Render() );
743:
744: $uid = $vcard->GetPValue('UID');
745: if ( empty($uid) ) {
746: $uid = uuid();
747: $vcard->AddProperty('UID',$uid);
748: }
749:
750: $last_modified = $vcard->GetPValue('REV');
751: if ( empty($last_modified) ) {
752: $last_modified = gmdate( 'Ymd\THis\Z' );
753: $vcard->AddProperty('REV',$last_modified);
754: }
755:
756: $created = $vcard->GetPValue('X-CREATED');
757: if ( empty($last_modified) ) {
758: $created = gmdate( 'Ymd\THis\Z' );
759: $vcard->AddProperty('X-CREATED',$created);
760: }
761:
762: $rendered_card = $vcard->Render();
763:
764:
765: $dav_name = sprintf( '%s%s.vcf', $path, preg_replace('{[&?\\/@%+:]}','',$uid) );
766:
767: $dav_data_params = $base_params;
768: $dav_data_params[':user_no'] = $user_no;
769: $dav_data_params[':dav_name'] = $dav_name;
770: $dav_data_params[':etag'] = md5($rendered_card);
771: $dav_data_params[':dav_data'] = $rendered_card;
772: $dav_data_params[':modified'] = $last_modified;
773: $dav_data_params[':created'] = $created;
774:
775:
776: $inserting = true;
777: if ( isset($current_data[$dav_name]) ) {
778: if ( $rendered_card == $current_data[$dav_name] ) {
779: unset($current_data[$dav_name]);
780: continue;
781: }
782: $sync_change = 200;
783: unset($current_data[$dav_name]);
784: $inserting = false;
785: }
786: else
787: $sync_change = 201;
788:
789: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
790:
791:
792: if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
793: rollback_on_error( $caldav_context, $user_no, $path, 'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
794:
795:
796: $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $dav_name));
797: if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
798: $dav_id = $row->dav_id;
799: }
800:
801: $vcard->Write( $row->dav_id, !$inserting );
802:
803: $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $dav_name ) );
804:
805: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
806: }
807:
808: if ( !$appending && count($current_data) > 0 ) {
809: $params = array( ':collection_id' => $collection_id );
810: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
811: foreach( $current_data AS $dav_name => $data ) {
812: $params[':dav_name'] = $dav_name;
813: $qry->QDo('DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
814: $qry->QDo('SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
815: }
816: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
817: }
818:
819: if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
820: if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on COMMIT');
821: }
822:
823: }
824:
825:
826: 827: 828: 829: 830: 831: 832: 833: 834:
835: function import_calendar_collection( $ics_content, $user_no, $path, $caldav_context, $appending = false ) {
836: global $c, $session, $tz_regex;
837: $calendar = new vComponent($ics_content);
838: $timezones = $calendar->GetComponents('VTIMEZONE',true);
839: $components = $calendar->GetComponents('VTIMEZONE',false);
840:
841:
842:
843: $after = null;
844: if ( isset($_GET['after']) ) {
845: $after = $_GET['after'];
846: if ( strtoupper(substr($after, 0, 1)) == 'P' || strtoupper(substr($after, 0, 1)) == '-P' ) {
847: $duration = new Rfc5545Duration($after);
848: $duration = $duration->asSeconds();
849: $after = time() - (abs($duration));
850: }
851: else {
852: $after = new RepeatRuleDateTime($after);
853: $after = $after->epoch();
854: }
855: }
856:
857: $displayname = $calendar->GetPValue('X-WR-CALNAME');
858: if ( !$appending && isset($displayname) ) {
859: $sql = 'UPDATE collection SET dav_displayname = :displayname WHERE dav_name = :dav_name';
860: $qry = new AwlQuery( $sql, array( ':displayname' => $displayname, ':dav_name' => $path) );
861: if ( ! $qry->Exec('PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on: '.$sql );
862: }
863:
864:
865: $tz_ids = array();
866: foreach( $timezones AS $k => $tz ) {
867: $tz_ids[$tz->GetPValue('TZID')] = $k;
868: }
869:
870:
871: $resources = array();
872: foreach( $components AS $k => $comp ) {
873: $uid = $comp->GetPValue('UID');
874: if ( $uid == null || $uid == '' ) {
875: $uid = uuid();
876: $comp->AddProperty('UID',$uid);
877: dbg_error_log( 'LOG WARN', ' PUT: New collection resource does not have a UID - we assign one!' );
878: }
879: if ( !isset($resources[$uid]) ) $resources[$uid] = array();
880: $resources[$uid][] = $comp;
881:
882:
883: $tzid = GetTZID($comp);
884: if ( !empty($tzid) && !isset($resources[$uid][$tzid]) && isset($tz_ids[$tzid]) ) {
885: $resources[$uid][$tzid] = $timezones[$tz_ids[$tzid]];
886: }
887: }
888:
889:
890: $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name';
891: $qry = new AwlQuery( $sql, array( ':dav_name' => $path) );
892: if ( ! $qry->Exec('PUT',__LINE__,__FILE__) ) rollback_on_error( $caldav_context, $user_no, $path, 'Database error on: '.$sql );
893: if ( ! $qry->rows() == 1 ) {
894: dbg_error_log( 'ERROR', ' PUT: Collection does not exist at "%s" for user %d', $path, $user_no );
895: rollback_on_error( $caldav_context, $user_no, $path, sprintf( 'Error: Collection does not exist at "%s" for user %d', $path, $user_no ));
896: }
897: $collection = $qry->Fetch();
898: $collection_id = $collection->collection_id;
899:
900:
901: $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array(
902: ':collection_id' => $collection_id
903: ));
904: $current_data = array();
905: while( $row = $qry->Fetch() )
906: $current_data[$row->dav_name] = $row->caldav_data;
907:
908: if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) $qry->Begin();
909: $base_params = array( ':collection_id' => $collection_id );
910:
911: $dav_data_insert = <<<EOSQL
912: INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id )
913: VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id )
914: EOSQL;
915:
916: $dav_data_update = <<<EOSQL
917: UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
918: modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name
919: EOSQL;
920:
921: $calitem_insert = <<<EOSQL
922: INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
923: description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id )
924: VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, :dtstart, ##dtend##, :summary, :location, :class, :transp,
925: :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, :collection_id)
926: EOSQL;
927:
928: $calitem_update = <<<EOSQL
929: UPDATE calendar_item SET user_no=:user_no, dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
930: dtstart=:dtstart, dtend=##dtend##, summary=:summary, location=:location,
931: class=:class, transp=:transp, description=:description, rrule=:rrule,
932: tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
933: due=:due, percent_complete=:percent_complete, status=:status
934: WHERE collection_id=:collection_id AND dav_name=:dav_name
935: EOSQL;
936:
937: $last_olson = '';
938: if ( count($resources) > 0 )
939: $qry->QDo('SELECT new_sync_token(0,'.$collection_id.')');
940:
941: foreach( $resources AS $uid => $resource ) {
942:
943:
944: $vcal = new vCalendar();
945: $vcal->SetComponents($resource);
946: $icalendar = $vcal->Render();
947: $dav_name = sprintf( '%s%s.ics', $path, preg_replace('{[&?\\/@%+:]}','',$uid) );
948:
949: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
950:
951:
952: $first = $resource[0];
953:
954: $dav_data_params = $base_params;
955: $dav_data_params[':user_no'] = $user_no;
956:
957: $dav_data_params[':dav_name'] = $dav_name;
958: $dav_data_params[':etag'] = md5($icalendar);
959: $calitem_params = $dav_data_params;
960: $dav_data_params[':dav_data'] = $icalendar;
961: $dav_data_params[':caldav_type'] = $first->GetType();
962: $dav_data_params[':session_user'] = $session->user_no;
963:
964: $dtstart = $first->GetPValue('DTSTART');
965: $calitem_params[':dtstart'] = $dtstart;
966: if ( (!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '' ) {
967: $dtstart = $first->GetPValue('DUE');
968: if ( isset($after) ) $dtstart_date = new RepeatRuleDateTime($first->GetProperty('DUE'));
969: }
970: else if ( isset($after) ) {
971: $dtstart_date = new RepeatRuleDateTime($first->GetProperty('DTSTART'));
972: }
973:
974: $calitem_params[':rrule'] = $first->GetPValue('RRULE');
975:
976:
977: if ( isset($after) && empty($calitem_params[':rrule']) && $dtstart_date->epoch() < $after ) continue;
978:
979:
980: $inserting = true;
981: if ( isset($current_data[$dav_name]) ) {
982: if ( $icalendar == $current_data[$dav_name] ) {
983: if ( $after == null ) {
984: unset($current_data[$dav_name]);
985: continue;
986: }
987: }
988: $sync_change = 200;
989: unset($current_data[$dav_name]);
990: $inserting = false;
991: }
992: else
993: $sync_change = 201;
994:
995:
996: if ( !$qry->QDo( ($inserting ? $dav_data_insert : $dav_data_update), $dav_data_params) )
997: rollback_on_error( $caldav_context, $user_no, $path, 'Database error on:'. ($inserting ? $dav_data_insert : $dav_data_update));
998:
999:
1000: $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $dav_data_params[':dav_name']));
1001: if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
1002: $dav_id = $row->dav_id;
1003: }
1004:
1005: $dtend = $first->GetPValue('DTEND');
1006: if ( isset($dtend) && $dtend != '' ) {
1007: dbg_error_log( 'PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION') );
1008: $calitem_params[':dtend'] = $dtend;
1009: $dtend = ':dtend';
1010: }
1011: else {
1012: $dtend = 'NULL';
1013: if ( $first->GetPValue('DURATION') != '' AND $dtstart != '' ) {
1014: $duration = trim(preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') ));
1015: if ( $duration == '' ) $duration = '0 seconds';
1016: $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
1017: $calitem_params[':duration'] = $duration;
1018: }
1019: elseif ( $first->GetType() == 'VEVENT' ) {
1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032:
1033: $dtstart_prop = $first->GetProperty('DTSTART');
1034: if ( empty($dtstart_prop) ) {
1035: dbg_error_log('PUT','Invalid VEVENT without DTSTART, UID="%s" in collection %d', $uid, $collection_id);
1036: continue;
1037: }
1038: $value_type = $dtstart_prop->GetParameterValue('VALUE');
1039: dbg_error_log('PUT','DTSTART without DTEND. DTSTART value type is %s', $value_type );
1040: if ( isset($value_type) && $value_type == 'DATE' )
1041: $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1042: else
1043: $dtend = ':dtstart';
1044: }
1045: }
1046:
1047: $last_modified = $first->GetPValue('LAST-MODIFIED');
1048: if ( !isset($last_modified) || $last_modified == '' ) $last_modified = gmdate( 'Ymd\THis\Z' );
1049: $calitem_params[':modified'] = $last_modified;
1050:
1051: $dtstamp = $first->GetPValue('DTSTAMP');
1052: if ( empty($dtstamp) ) $dtstamp = $last_modified;
1053: $calitem_params[':dtstamp'] = $dtstamp;
1054:
1055:
1056: $class = ($collection->public_events_only == 't' ? 'PUBLIC' : $first->GetPValue('CLASS') );
1057: if ( !isset($class) || $class == '' ) $class = 'PUBLIC';
1058: $calitem_params[':class'] = $class;
1059:
1060:
1061:
1062: $tzid = GetTZID($first);
1063: if ( !empty($tzid) && !empty($resource[$tzid]) ) {
1064: $tz = $resource[$tzid];
1065: $olson = $vcal->GetOlsonName($tz);
1066: dbg_error_log( 'PUT', ' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson : '') );
1067: if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1068: dbg_error_log( 'PUT', ' Setting timezone to %s', $olson );
1069: $qry->QDo('SET TIMEZONE TO \''.$olson."'" );
1070: $last_olson = $olson;
1071: }
1072: $params = array( ':tzid' => $tzid);
1073: $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1074: if ( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1075: $params[':olson_name'] = $olson;
1076: $params[':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1077: $params[':last_modified'] = (isset($tz) ? $tz->GetPValue('LAST-MODIFIED') : null );
1078: if ( empty($params[':last_modified']) ) {
1079: $params[':last_modified'] = gmdate('Ymd\THis\Z');
1080: }
1081: $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone, last_modified) VALUES(:tzid,:olson_name,false,:vtimezone,:last_modified)', $params );
1082: }
1083: }
1084: else {
1085: $tz = $olson = $tzid = null;
1086: }
1087:
1088: $sql = str_replace( '##dtend##', $dtend, ($inserting ? $calitem_insert : $calitem_update) );
1089: $calitem_params[':tzid'] = $tzid;
1090: $calitem_params[':uid'] = $first->GetPValue('UID');
1091: $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
1092: $calitem_params[':location'] = $first->GetPValue('LOCATION');
1093: $calitem_params[':transp'] = $first->GetPValue('TRANSP');
1094: $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
1095: $calitem_params[':url'] = $first->GetPValue('URL');
1096: $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
1097: $calitem_params[':due'] = $first->GetPValue('DUE');
1098: $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
1099: $calitem_params[':status'] = $first->GetPValue('STATUS');
1100:
1101: if ( $inserting ) {
1102: $created = $first->GetPValue('CREATED');
1103: if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
1104: $calitem_params[':created'] = $created;
1105: }
1106:
1107:
1108: if ( !$qry->QDo($sql,$calitem_params) ) rollback_on_error( $caldav_context, $user_no, $path);
1109:
1110: write_alarms($dav_id, $first);
1111: write_attendees($dav_id, $vcal);
1112:
1113: $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $dav_name ) );
1114:
1115: do_scheduling_requests( $vcal, true );
1116: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1117: }
1118:
1119: if ( !$appending && count($current_data) > 0 ) {
1120: $params = array( ':collection_id' => $collection_id );
1121: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Begin();
1122: foreach( $current_data AS $dav_name => $data ) {
1123: $params[':dav_name'] = $dav_name;
1124: $qry->QDo('DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params);
1125: $qry->QDo('SELECT write_sync_change(:collection_id, 404, :dav_name)', $params);
1126: }
1127: if ( isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import ) $qry->Commit();
1128: }
1129:
1130: if ( !(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) ) {
1131: if ( ! $qry->Commit() ) rollback_on_error( $caldav_context, $user_no, $path);
1132: }
1133: }
1134:
1135:
1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143:
1144: function write_alarms( $dav_id, vComponent $ical ) {
1145: $qry = new AwlQuery('DELETE FROM calendar_alarm WHERE dav_id = '.$dav_id );
1146: $qry->Exec('PUT',__LINE__,__FILE__);
1147:
1148: $alarms = $ical->GetComponents('VALARM');
1149: if ( count($alarms) < 1 ) return;
1150:
1151: $qry->SetSql('INSERT INTO calendar_alarm ( dav_id, action, trigger, summary, description, component, next_trigger )
1152: VALUES( '.$dav_id.', :action, :trigger, :summary, :description, :component,
1153: :related::timestamp with time zone + :related_trigger::interval )' );
1154: $qry->Prepare();
1155: foreach( $alarms AS $v ) {
1156: $trigger = array_merge($v->GetProperties('TRIGGER'));
1157: if ( $trigger == null ) continue;
1158: $trigger = $trigger[0];
1159: $related = null;
1160: $related_trigger = '0M';
1161: $trigger_type = $trigger->GetParameterValue('VALUE');
1162: if ( !isset($trigger_type) || $trigger_type == 'DURATION' ) {
1163: switch ( $trigger->GetParameterValue('RELATED') ) {
1164: case 'DTEND': $related = $ical->GetProperty('DTEND'); break;
1165: case 'DUE': $related = $ical->GetProperty('DUE'); break;
1166: default: $related = $ical->GetProperty('DTSTART');
1167: }
1168: $duration = $trigger->Value();
1169: if ( !preg_match('{^-?P(:?\d+W)?(:?\d+D)?(:?T(:?\d+H)?(:?\d+M)?(:?\d+S)?)?$}', $duration ) ) continue;
1170: $minus = (substr($duration,0,1) == '-');
1171: $related_trigger = trim(preg_replace( '#[PT-]#', ' ', $duration ));
1172: if ( $minus ) {
1173: $related_trigger = preg_replace( '{(\d+[WDHMS])}', '-$1 ', $related_trigger );
1174: }
1175: else {
1176: $related_trigger = preg_replace( '{(\d+[WDHMS])}', '$1 ', $related_trigger );
1177: }
1178: }
1179: else if ( $trigger_type == 'DATE-TIME' ) {
1180: $related = $trigger;
1181: }
1182: else {
1183: if ( false === strtotime($trigger->Value()) ) continue;
1184: $related = $trigger;
1185: }
1186: $related_date = new RepeatRuleDateTime($related);
1187: $qry->Bind(':action', $v->GetPValue('ACTION'));
1188: $qry->Bind(':trigger', $trigger->Render());
1189: $qry->Bind(':summary', $v->GetPValue('SUMMARY'));
1190: $qry->Bind(':description', $v->GetPValue('DESCRIPTION'));
1191: $qry->Bind(':component', $v->Render());
1192: $qry->Bind(':related', $related_date->UTC() );
1193: $qry->Bind(':related_trigger', $related_trigger );
1194: $qry->Exec('PUT',__LINE__,__FILE__);
1195: }
1196: }
1197:
1198:
1199: 1200: 1201: 1202: 1203: 1204: 1205:
1206: function write_attendees( $dav_id, vCalendar $ical ) {
1207: $qry = new AwlQuery('DELETE FROM calendar_attendee WHERE dav_id = '.$dav_id );
1208: $qry->Exec('PUT',__LINE__,__FILE__);
1209:
1210: $attendees = $ical->GetAttendees();
1211: if ( count($attendees) < 1 ) return;
1212:
1213: $qry->SetSql('INSERT INTO calendar_attendee ( dav_id, status, partstat, cn, attendee, role, rsvp, property )
1214: VALUES( '.$dav_id.', :status, :partstat, :cn, :attendee, :role, :rsvp, :property )' );
1215: $qry->Prepare();
1216: $processed = array();
1217: foreach( $attendees AS $v ) {
1218: $attendee = $v->Value();
1219: if ( isset($processed[$attendee]) ) {
1220: dbg_error_log( 'LOG', 'Duplicate attendee "%s" in resource "%d"', $attendee, $dav_id );
1221: dbg_error_log( 'LOG', 'Original: "%s"', $processed[$attendee] );
1222: dbg_error_log( 'LOG', 'Duplicate: "%s"', $v->Render() );
1223: continue;
1224: }
1225: $qry->Bind(':attendee', $attendee );
1226: $qry->Bind(':status', $v->GetParameterValue('STATUS') );
1227: $qry->Bind(':partstat', $v->GetParameterValue('PARTSTAT') );
1228: $qry->Bind(':cn', $v->GetParameterValue('CN') );
1229: $qry->Bind(':role', $v->GetParameterValue('ROLE') );
1230: $qry->Bind(':rsvp', $v->GetParameterValue('RSVP') );
1231: $qry->Bind(':property', $v->Render() );
1232: $qry->Exec('PUT',__LINE__,__FILE__);
1233: $processed[$attendee] = $v->Render();
1234: }
1235: }
1236:
1237:
1238: 1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254:
1255: function write_resource( DAVResource $resource, $caldav_data, DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action=true, $weak_etag=null ) {
1256: global $tz_regex, $session;
1257:
1258: $path = $resource->bound_from();
1259: $user_no = $collection->user_no();
1260: $vcal = new vCalendar( $caldav_data );
1261: $resources = $vcal->GetComponents('VTIMEZONE',false);
1262: if ( !isset($resources[0]) ) {
1263: $resource_type = 'Unknown';
1264:
1265: rollback_on_error( $caldav_context, $user_no, $path, translate('No calendar content'), 412 );
1266: return false;
1267: }
1268: else {
1269: $first = $resources[0];
1270: if ( !($first instanceof vComponent) ) {
1271: print $vcal->Render();
1272: fatal('This is not a vComponent!');
1273: }
1274: $resource_type = $first->GetType();
1275: }
1276:
1277: $collection_id = $collection->collection_id();
1278:
1279: $qry = new AwlQuery();
1280: $qry->Begin();
1281:
1282: $dav_params = array(
1283: ':etag' => $etag,
1284: ':dav_data' => $caldav_data,
1285: ':caldav_type' => $resource_type,
1286: ':session_user' => $author,
1287: ':weak_etag' => $weak_etag
1288: );
1289:
1290: $calitem_params = array(
1291: ':etag' => $etag
1292: );
1293:
1294: if ( $put_action_type == 'INSERT' ) {
1295: $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data');
1296: }
1297: else {
1298: $qry->QDo('SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
1299: }
1300: if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
1301:
1302: trace_bug( 'No dav_id for "%s" on %s!!!', $path, ($put_action_type == 'INSERT' ? 'create': 'update'));
1303: rollback_on_error( $caldav_context, $user_no, $path);
1304: return false;
1305: }
1306: $dav_id = $row->dav_id;
1307: $old_dav_data = $row->caldav_data;
1308: $dav_params[':dav_id'] = $dav_id;
1309: $calitem_params[':dav_id'] = $dav_id;
1310:
1311: $due = null;
1312: if ( $first->GetType() == 'VTODO' ) $due = $first->GetPValue('DUE');
1313: $calitem_params[':due'] = $due;
1314: $dtstart = $first->GetPValue('DTSTART');
1315: if ( empty($dtstart) ) $dtstart = $due;
1316: if (preg_match("/^1[0-8][0-9][0-9][01][0-9][0-3][0-9]$/", $dtstart))
1317: $dtstart = $dtstart . "T000000Z";
1318: $calitem_params[':dtstart'] = $dtstart;
1319:
1320: $dtend = $first->GetPValue('DTEND');
1321: if ( isset($dtend) && $dtend != '' ) {
1322: dbg_error_log( 'PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION') );
1323: if (preg_match("/^1[0-8][0-9][0-9][01][0-9][0-3][0-9]$/", $dtend))
1324: $dtend = $dtend . "T000000Z";
1325: $calitem_params[':dtend'] = $dtend;
1326: $dtend = ':dtend';
1327: }
1328: else {
1329:
1330: $dtend = 'NULL';
1331: if ( $first->GetPValue('DURATION') != '' AND $dtstart != '' ) {
1332: $duration = trim(preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') ));
1333: if ( $duration == '' ) $duration = '0 seconds';
1334: $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
1335: $calitem_params[':duration'] = $duration;
1336: }
1337: elseif ( $first->GetType() == 'VEVENT' ) {
1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 1350:
1351: $dtstart_prop = $first->GetProperty('DTSTART');
1352: $value_type = $dtstart_prop->GetParameterValue('VALUE');
1353: dbg_error_log('PUT','DTSTART without DTEND. DTSTART value type is %s', $value_type );
1354: if ( isset($value_type) && $value_type == 'DATE' )
1355: $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
1356: else
1357: $dtend = ':dtstart';
1358: }
1359: }
1360:
1361: $dtstamp = $first->GetPValue('DTSTAMP');
1362: if ( !isset($dtstamp) || $dtstamp == '' ) {
1363:
1364: $dtstamp = gmdate( 'Ymd\THis\Z' );
1365: }
1366: $calitem_params[':dtstamp'] = $dtstamp;
1367:
1368: $last_modified = $first->GetPValue('LAST-MODIFIED');
1369: if ( !isset($last_modified) || $last_modified == '' ) $last_modified = $dtstamp;
1370: $dav_params[':modified'] = $last_modified;
1371: $calitem_params[':modified'] = $last_modified;
1372:
1373: $created = $first->GetPValue('CREATED');
1374: if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
1375:
1376: $class = $first->GetPValue('CLASS');
1377:
1378:
1379: if ( public_events_only($user_no, $path) ) {
1380: $class = 'PUBLIC';
1381: }
1382:
1383: 1384: 1385: 1386: 1387:
1388: if ( !isset($class) || $class == '' ) {
1389: $class = 'PUBLIC';
1390: }
1391: $calitem_params[':class'] = $class;
1392:
1393:
1394: $last_olson = 'Turkmenikikamukau';
1395: $tzid = GetTZID($first);
1396: if ( !empty($tzid) ) {
1397: $timezones = $vcal->GetComponents('VTIMEZONE');
1398: foreach( $timezones AS $k => $tz ) {
1399: if ( $tz->GetPValue('TZID') != $tzid ) {
1400: 1401: 1402:
1403: dbg_error_log( 'ERROR', ' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue('TZID'), $tzid );
1404: continue;
1405: }
1406: $olson = olson_from_tzstring($tzid);
1407: if ( empty($olson) ) {
1408: $olson = $tz->GetPValue('X-LIC-LOCATION');
1409: if ( !empty($olson) ) {
1410: $olson = olson_from_tzstring($olson);
1411: }
1412: }
1413: }
1414:
1415: dbg_error_log( 'PUT', ' Using TZID[%s] and location of [%s]', $tzid, (isset($olson) ? $olson : '') );
1416: if ( !empty($olson) && ($olson != $last_olson) && preg_match( $tz_regex, $olson ) ) {
1417: dbg_error_log( 'PUT', ' Setting timezone to %s', $olson );
1418: if ( $olson != '' ) {
1419: $qry->QDo('SET TIMEZONE TO \''.$olson."'" );
1420: }
1421: $last_olson = $olson;
1422: }
1423: $params = array( ':tzid' => $tzid);
1424: $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params );
1425: if ( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
1426: $params[':olson_name'] = $olson;
1427: $params[':vtimezone'] = (isset($tz) ? $tz->Render() : null );
1428: $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params );
1429: }
1430: if ( !isset($olson) || $olson == '' ) $olson = $tzid;
1431:
1432: }
1433:
1434: $qry->QDo('SELECT new_sync_token(0,'.$collection_id.')');
1435:
1436: $calitem_params[':tzid'] = $tzid;
1437: $calitem_params[':uid'] = $first->GetPValue('UID');
1438: $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
1439: $calitem_params[':location'] = $first->GetPValue('LOCATION');
1440: $calitem_params[':transp'] = $first->GetPValue('TRANSP');
1441: $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
1442: $calitem_params[':rrule'] = $first->GetPValue('RRULE');
1443: $calitem_params[':url'] = $first->GetPValue('URL');
1444: $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
1445: $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
1446: $calitem_params[':status'] = $first->GetPValue('STATUS');
1447:
1448:
1449: $vcal->Render(null, true);
1450:
1451: if ( !$collection->IsSchedulingCollection() ) {
1452: if ( do_scheduling_requests($vcal, ($put_action_type == 'INSERT'), $old_dav_data ) ) {
1453: $dav_params[':dav_data'] = $vcal->Render(null, true);
1454: $etag = null;
1455: }
1456: }
1457:
1458: if ( !isset($dav_params[':modified']) ) $dav_params[':modified'] = 'now';
1459: if ( $put_action_type == 'INSERT' ) {
1460: $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 )
1461: VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )';
1462: $dav_params[':collection_id'] = $collection_id;
1463: $dav_params[':user_no'] = $user_no;
1464: $dav_params[':dav_name'] = $path;
1465: $dav_params[':created'] = (isset($created) && $created != '' ? $created : $dtstamp);
1466: }
1467: else {
1468: $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
1469: modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id';
1470: }
1471: $qry = new AwlQuery($sql,$dav_params);
1472: if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) {
1473: fatal('Insert into calendar_item failed...');
1474: rollback_on_error( $caldav_context, $user_no, $path);
1475: return false;
1476: }
1477:
1478:
1479: if ( $put_action_type == 'INSERT' ) {
1480: $sql = <<<EOSQL
1481: INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
1482: dtstart, dtend, summary, location, class, transp,
1483: description, rrule, tz_id, last_modified, url, priority,
1484: created, due, percent_complete, status, collection_id )
1485: VALUES ( :user_no, :dav_name, :dav_id, :etag, :uid, :dtstamp,
1486: :dtstart, $dtend, :summary, :location, :class, :transp,
1487: :description, :rrule, :tzid, :modified, :url, :priority,
1488: :created, :due, :percent_complete, :status, :collection_id )
1489: EOSQL;
1490: $sync_change = 201;
1491: $calitem_params[':collection_id'] = $collection_id;
1492: $calitem_params[':user_no'] = $user_no;
1493: $calitem_params[':dav_name'] = $path;
1494: $calitem_params[':created'] = $dav_params[':created'];
1495: }
1496: else {
1497: $sql = <<<EOSQL
1498: UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
1499: dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location,
1500: class=:class, transp=:transp, description=:description, rrule=:rrule,
1501: tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
1502: due=:due, percent_complete=:percent_complete, status=:status
1503: WHERE dav_id=:dav_id
1504: EOSQL;
1505: $sync_change = 200;
1506: }
1507:
1508: write_alarms($dav_id, $first);
1509: write_attendees($dav_id, $vcal);
1510:
1511: if ( $log_action && function_exists('log_caldav_action') ) {
1512: log_caldav_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
1513: }
1514: else if ( $log_action ) {
1515: dbg_error_log( 'PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
1516: $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
1517: }
1518:
1519: $qry = new AwlQuery( $sql, $calitem_params );
1520: if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) {
1521: rollback_on_error( $caldav_context, $user_no, $path);
1522: return false;
1523: }
1524: $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $path ) );
1525: $qry->Commit();
1526:
1527: if ( function_exists('post_commit_action') ) {
1528: post_commit_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
1529: }
1530:
1531:
1532: $cache = getCacheInstance();
1533: $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $path);
1534: $cache->delete( $cache_ns, null );
1535:
1536: dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
1537:
1538: return true;
1539: }
1540:
1541:
1542:
1543: 1544: 1545: 1546: 1547: 1548: 1549: 1550: 1551:
1552: function simple_write_resource( $path, $caldav_data, $put_action_type, $write_action_log = false ) {
1553: global $session;
1554:
1555: 1556: 1557:
1558: $dav_resource = new DAVResource($path);
1559: $etag = md5($caldav_data);
1560: $collection_path = preg_replace( '#/[^/]*$#', '/', $path );
1561: $collection = new DAVResource($collection_path);
1562: if ( $collection->IsCollection() || $collection->IsSchedulingCollection() ) {
1563: return write_resource( $dav_resource, $caldav_data, $collection, $session->user_no, $etag, $put_action_type, false, $write_action_log );
1564: }
1565: return false;
1566: }
1567: