1: <?php
2: include_once('DAVResource.php');
3:
4: class WritableCollection extends DAVResource {
5:
6: 7: 8: 9: 10:
11: private static function GetTZID( vComponent $comp ) {
12: $p = $comp->GetProperty('DTSTART');
13: if ( !isset($p) && $comp->GetType() == 'VTODO' ) {
14: $p = $comp->GetProperty('DUE');
15: }
16: if ( !isset($p) ) return null;
17: return $p->GetParameterValue('TZID');
18: }
19:
20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
33: function WriteCalendarMember( vCalendar $vcal, $create_resource, $do_scheduling=false, $segment_name = null, $log_action=false ) {
34: if ( !$this->IsSchedulingCollection() && !$this->IsCalendar() ) {
35: dbg_error_log( 'PUT', '"%s" is not a calendar or scheduling collection!', $this->dav_name);
36: return false;
37: }
38:
39: global $session, $caldav_context;
40:
41: $resources = $vcal->GetComponents('VTIMEZONE',false);
42: $user_no = $this->user_no();
43: $collection_id = $this->collection_id();
44:
45: if ( !isset($resources[0]) ) {
46: dbg_error_log( 'PUT', 'No calendar content!');
47: rollback_on_error( $caldav_context, $user_no, $this->dav_name.'/'.$segment_name, translate('No calendar content'), 412 );
48: return false;
49: }
50: else {
51: $first = $resources[0];
52: $resource_type = $first->GetType();
53: }
54:
55: $uid = $vcal->GetUID();
56: if ( empty($segment_name) ) {
57: $segment_name = $uid.'.ics';
58: }
59: $path = $this->dav_name() . $segment_name;
60:
61: $caldav_data = $vcal->Render();
62: $etag = md5($caldav_data);
63: $weak_etag = null;
64:
65: $qry = new AwlQuery();
66: $existing_transaction_state = $qry->TransactionState();
67: if ( $existing_transaction_state == 0 ) $qry->Begin();
68:
69:
70: if ( $create_resource ) {
71: $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id');
72: }
73: else {
74: $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
75: }
76: if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
77: if ( !$create_resource ) {
78:
79: $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id');
80: if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
81:
82: trace_bug( 'No dav_id for "%s" on %s!!!', $path, ($create_resource ? 'create': 'update'));
83: rollback_on_error( $caldav_context, $user_no, $path);
84: return false;
85: }
86: $create_resource = true;
87: dbg_error_log( 'PUT', 'Unexpected need to create resource at "%s"', $path);
88: }
89: }
90: $dav_id = $row->dav_id;
91:
92: $calitem_params = array(
93: ':dav_name' => $path,
94: ':user_no' => $user_no,
95: ':etag' => $etag,
96: ':dav_id' => $dav_id
97: );
98:
99: $dav_params = array_merge($calitem_params, array(
100: ':dav_data' => $caldav_data,
101: ':caldav_type' => $resource_type,
102: ':session_user' => $session->user_no,
103: ':weak_etag' => $weak_etag
104: ) );
105:
106: if ( !$this->IsSchedulingCollection() && $do_scheduling ) {
107: if ( do_scheduling_requests($vcal, $create_resource ) ) {
108: $dav_params[':dav_data'] = $vcal->Render(null, true);
109: $etag = null;
110: }
111: }
112:
113: if ( $create_resource ) {
114: $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 )
115: VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )';
116: $dav_params[':collection_id'] = $collection_id;
117: }
118: else {
119: $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
120: modified=current_timestamp, weak_etag=:weak_etag WHERE dav_id=:dav_id';
121: }
122: if ( !$qry->QDo($sql,$dav_params) ) {
123: rollback_on_error( $caldav_context, $user_no, $path);
124: return false;
125: }
126:
127: $dtstart = $first->GetPValue('DTSTART');
128: $calitem_params[':dtstart'] = $dtstart;
129: if ( (!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '' ) {
130: $dtstart = $first->GetPValue('DUE');
131: }
132:
133: $dtend = $first->GetPValue('DTEND');
134: if ( isset($dtend) && $dtend != '' ) {
135: dbg_error_log( 'PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION') );
136: $calitem_params[':dtend'] = $dtend;
137: $dtend = ':dtend';
138: }
139: else {
140: $dtend = 'NULL';
141: if ( $first->GetPValue('DURATION') != '' AND $dtstart != '' ) {
142: $duration = preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') );
143: $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
144: $calitem_params[':duration'] = $duration;
145: }
146: elseif ( $first->GetType() == 'VEVENT' ) {
147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159:
160: $value_type = $first->GetProperty('DTSTART')->GetParameterValue('VALUE');
161: dbg_error_log('PUT','DTSTART without DTEND. DTSTART value type is %s', $value_type );
162: if ( isset($value_type) && $value_type == 'DATE' )
163: $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
164: else
165: $dtend = ':dtstart';
166: }
167: }
168:
169: $last_modified = $first->GetPValue('LAST-MODIFIED');
170: if ( !isset($last_modified) || $last_modified == '' ) {
171: $last_modified = gmdate( 'Ymd\THis\Z' );
172: }
173: $calitem_params[':modified'] = $last_modified;
174:
175: $dtstamp = $first->GetPValue('DTSTAMP');
176: if ( !isset($dtstamp) || $dtstamp == '' ) {
177: $dtstamp = $last_modified;
178: }
179: $calitem_params[':dtstamp'] = $dtstamp;
180:
181: $class = $first->GetPValue('CLASS');
182: 183: 184: 185:
186: if ( $this->IsPublicOnly() || !isset($class) || $class == '' ) {
187: $class = 'PUBLIC';
188: }
189: $calitem_params[':class'] = $class;
190:
191:
192: $last_olson = 'Turkmenikikamukau';
193: $tzid = self::GetTZID($first);
194: if ( !empty($tzid) ) {
195: $tz = $vcal->GetTimeZone($tzid);
196: $olson = $vcal->GetOlsonName($tz);
197:
198: if ( !empty($olson) && ($olson != $last_olson) ) {
199: dbg_error_log( 'PUT', ' Setting timezone to %s', $olson );
200: $qry->QDo('SET TIMEZONE TO \''.$olson."'" );
201: $last_olson = $olson;
202: }
203: }
204:
205: $created = $first->GetPValue('CREATED');
206: if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
207: $calitem_params[':created'] = $created;
208:
209: $calitem_params[':tzid'] = $tzid;
210: $calitem_params[':uid'] = $uid;
211: $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
212: $calitem_params[':location'] = $first->GetPValue('LOCATION');
213: $calitem_params[':transp'] = $first->GetPValue('TRANSP');
214: $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
215: $calitem_params[':rrule'] = $first->GetPValue('RRULE');
216: $calitem_params[':url'] = $first->GetPValue('URL');
217: $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
218: $calitem_params[':due'] = $first->GetPValue('DUE');
219: $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
220: $calitem_params[':status'] = $first->GetPValue('STATUS');
221: if ( $create_resource ) {
222: $sql = <<<EOSQL
223: INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
224: dtstart, dtend, summary, location, class, transp,
225: description, rrule, tz_id, last_modified, url, priority,
226: created, due, percent_complete, status, collection_id )
227: VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
228: :dtstart, $dtend, :summary, :location, :class, :transp,
229: :description, :rrule, :tzid, :modified, :url, :priority,
230: :created, :due, :percent_complete, :status, $collection_id )
231: EOSQL;
232: $sync_change = 201;
233: }
234: else {
235: $sql = <<<EOSQL
236: UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
237: dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp,
238: description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
239: created=:created, due=:due, percent_complete=:percent_complete, status=:status
240: WHERE user_no=:user_no AND dav_name=:dav_name
241: EOSQL;
242: $sync_change = 200;
243: }
244:
245: if ( !$this->IsSchedulingCollection() ) {
246: $this->WriteCalendarAlarms($dav_id, $vcal);
247: $this->WriteCalendarAttendees($dav_id, $vcal);
248: $put_action_type = ($create_resource ? 'INSERT' : 'UPDATE');
249: if ( $log_action && function_exists('log_caldav_action') ) {
250: log_caldav_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
251: }
252: else if ( $log_action ) {
253: dbg_error_log( 'PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
254: $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
255: }
256: }
257:
258: $qry = new AwlQuery( $sql, $calitem_params );
259: if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) {
260: rollback_on_error( $caldav_context, $user_no, $path);
261: return false;
262: }
263: $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $path ) );
264: if ( $existing_transaction_state == 0 ) $qry->Commit();
265:
266: dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $session->user_no, $etag, $path);
267:
268:
269: return $segment_name;
270: }
271:
272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286:
287: function WriteMember( $resource, $create_resource, $segment_name = null, $log_action=true ) {
288: if ( ! $this->IsCollection() ) {
289: dbg_error_log( 'PUT', '"%s" is not a collection path', $this->dav_name);
290: return false;
291: }
292: if ( ! is_object($resource) ) {
293: dbg_error_log( 'PUT', 'No data supplied!' );
294: return false;
295: }
296:
297: if ( $resource instanceof vCalendar ) {
298: return $this->WriteCalendarMember($resource,$create_resource,true,$segment_name,$log_action);
299: }
300: else if ( $resource instanceof VCard )
301: trace_bug( "Calling undefined function WriteAddressbookMember!? Please report this to the davical project: davical-general@lists.sourceforge.net" );
302: return $this->WriteAddressbookMember($resource,$create_resource,$segment_name, $log_action);
303:
304: return $segment_name;
305: }
306:
307:
308: 309: 310: 311: 312: 313:
314: function WriteCalendarAlarms( $dav_id, vCalendar $vcal ) {
315: $qry = new AwlQuery('DELETE FROM calendar_alarm WHERE dav_id = '.$dav_id );
316: $qry->Exec('PUT',__LINE__,__FILE__);
317:
318: $components = $vcal->GetComponents();
319:
320: $qry->SetSql('INSERT INTO calendar_alarm ( dav_id, action, trigger, summary, description, component, next_trigger )
321: VALUES( '.$dav_id.', :action, :trigger, :summary, :description, :component,
322: :related::timestamp with time zone + :related_trigger::interval )' );
323: $qry->Prepare();
324: foreach( $components AS $component ) {
325: if ( $component->GetType() == 'VTIMEZONE' ) continue;
326: $alarms = $component->GetComponents('VALARM');
327: if ( count($alarms) < 1 ) return;
328:
329: foreach( $alarms AS $v ) {
330: $trigger = array_merge($v->GetProperties('TRIGGER'));
331: if ( $trigger == null ) continue;
332: $trigger = $trigger[0];
333: $related = null;
334: $related_trigger = '0M';
335: $trigger_type = $trigger->GetParameterValue('VALUE');
336: if ( !isset($trigger_type) || $trigger_type == 'DURATION' ) {
337: switch ( $trigger->GetParameterValue('RELATED') ) {
338: case 'DTEND': $related = $component->GetPValue('DTEND'); break;
339: case 'DUE': $related = $component->GetPValue('DUE'); break;
340: default: $related = $component->GetPValue('DTSTART');
341: }
342: $duration = $trigger->Value();
343: if ( !preg_match('{^-?P(:?\d+W)?(:?\d+D)?(:?T(:?\d+H)?(:?\d+M)?(:?\d+S)?)?$}', $duration ) ) continue;
344: $minus = (substr($duration,0,1) == '-');
345: $related_trigger = trim(preg_replace( '#[PT-]#', ' ', $duration ));
346: if ( $minus ) {
347: $related_trigger = preg_replace( '{(\d+[WDHMS])}', '-$1 ', $related_trigger );
348: }
349: else {
350: $related_trigger = preg_replace( '{(\d+[WDHMS])}', '$1 ', $related_trigger );
351: }
352: }
353: else {
354: if ( false === strtotime($trigger->Value()) ) continue;
355: }
356: $qry->Bind(':action', $v->GetPValue('ACTION'));
357: $qry->Bind(':trigger', $trigger->Render());
358: $qry->Bind(':summary', $v->GetPValue('SUMMARY'));
359: $qry->Bind(':description', $v->GetPValue('DESCRIPTION'));
360: $qry->Bind(':component', $v->Render());
361: $qry->Bind(':related', $related );
362: $qry->Bind(':related_trigger', $related_trigger );
363: $qry->Exec('PUT',__LINE__,__FILE__);
364: }
365: }
366: }
367:
368:
369: 370: 371: 372: 373: 374: 375:
376: function WriteCalendarAttendees( $dav_id, vCalendar $vcal ) {
377: $qry = new AwlQuery('DELETE FROM calendar_attendee WHERE dav_id = '.$dav_id );
378: $qry->Exec('PUT',__LINE__,__FILE__);
379:
380: $attendees = $vcal->GetAttendees();
381: if ( count($attendees) < 1 ) return;
382:
383: $qry->SetSql('INSERT INTO calendar_attendee ( dav_id, status, partstat, cn, attendee, role, rsvp, property )
384: VALUES( '.$dav_id.', :status, :partstat, :cn, :attendee, :role, :rsvp, :property )' );
385: $qry->Prepare();
386: $processed = array();
387: foreach( $attendees AS $v ) {
388: $attendee = $v->Value();
389: if ( isset($processed[$attendee]) ) {
390: dbg_error_log( 'LOG', 'Duplicate attendee "%s" in resource "%d"', $attendee, $dav_id );
391: dbg_error_log( 'LOG', 'Original: "%s"', $processed[$attendee] );
392: dbg_error_log( 'LOG', 'Duplicate: "%s"', $v->Render() );
393: continue;
394: }
395: $qry->Bind(':attendee', $attendee );
396: $qry->Bind(':status', $v->GetParameterValue('STATUS') );
397: $qry->Bind(':partstat', $v->GetParameterValue('PARTSTAT') );
398: $qry->Bind(':cn', $v->GetParameterValue('CN') );
399: $qry->Bind(':role', $v->GetParameterValue('ROLE') );
400: $qry->Bind(':rsvp', $v->GetParameterValue('RSVP') );
401: $qry->Bind(':property', $v->Render() );
402: $qry->Exec('PUT',__LINE__,__FILE__);
403: $processed[$attendee] = $v->Render();
404: }
405: }
406:
407: 408: 409: 410: 411: 412: 413:
414: function actualDeleteCalendarMember( $member_dav_name ) {
415: global $session, $caldav_context;
416:
417:
418: $segment_name = str_replace( $this->dav_name(), '', $member_dav_name );
419: if ( strstr($segment_name, '/') !== false ) {
420: @dbg_error_log( "DELETE", "DELETE: Refused to delete member '%s' from calendar '%s'!", $member_dav_name, $this->dav_name() );
421: return false;
422: }
423:
424:
425: $cache = getCacheInstance();
426: $myLock = $cache->acquireLock('collection-'.$this->dav_name());
427:
428: $qry = new AwlQuery();
429: $params = array( ':dav_name' => $member_dav_name );
430:
431: if ( $qry->QDo("SELECT write_sync_change(collection_id, 404, caldav_data.dav_name) FROM caldav_data WHERE dav_name = :dav_name", $params )
432: && $qry->QDo("DELETE FROM property WHERE dav_name = :dav_name", $params )
433: && $qry->QDo("DELETE FROM locks WHERE dav_name = :dav_name", $params )
434: && $qry->QDo("DELETE FROM caldav_data WHERE dav_name = :dav_name", $params ) ) {
435: @dbg_error_log( "DELETE", "DELETE: Calendar member %s deleted from calendar '%s'", $member_dav_name, $this->dav_name() );
436:
437: $cache->releaseLock($myLock);
438:
439: return true;
440: }
441:
442: $cache->releaseLock($myLock);
443: return false;
444:
445: }
446:
447:
448: 449: 450: 451:
452: public function whatChangedSince( $some_old_token ) {
453: $params = array( ':collection_id' => $this->collection_id() );
454: if ( $some_old_token == 0 || empty($some_old_token) ) {
455: $sql = <<<EOSQL
456: SELECT calendar_item.*, caldav_data.*, addressbook_resource.*, 201 AS sync_status,
457: COALESCE(addressbook_resource.uid,calendar_item.uid) AS uid
458: FROM caldav_data
459: LEFT JOIN calendar_item USING (dav_id)
460: LEFT JOIN addressbook_resource USING (dav_id)
461: WHERE caldav_data.collection_id = :collection_id
462: ORDER BY caldav_data.collection_id, caldav_data.dav_id
463: EOSQL;
464: }
465: else {
466: $params[':sync_token'] = $some_old_token;
467: $sql = <<<EOSQL
468: SELECT calendar_item.*, caldav_data.*, addressbook_resource.*, sync_changes.*,
469: COALESCE(addressbook_resource.uid,calendar_item.uid) AS uid
470: FROM sync_changes
471: LEFT JOIN caldav_data USING (collection_id,dav_id)
472: LEFT JOIN calendar_item USING (collection_id,dav_id)
473: LEFT JOIN addressbook_resource USING (dav_id)
474: WHERE sync_changes.collection_id = :collection_id
475: AND sync_time >= (SELECT modification_time FROM sync_tokens WHERE sync_token = :sync_token)
476: ORDER BY sync_changes.collection_id, sync_changes.dav_id, sync_changes.sync_time
477: EOSQL;
478:
479: }
480: $qry = new AwlQuery($sql, $params );
481:
482: $changes = array();
483: if ( $qry->Exec('WritableCollection') && $qry->rows() ) {
484: while( $change = $qry->Fetch() ) {
485: $changes[$change->uid] = $change;
486: }
487: }
488:
489: return $changes;
490: }
491: }
492: