Rewriting some TODO comments as @todo
[davical.git] / inc / WritableCollection.php
blob0dd63d3bb37b7b4911d6398749a3e45ed81e9933
1 <?php
2 include_once('DAVResource.php');
4 class WritableCollection extends DAVResource {
6 /**
7 * Writes the data to a member in the collection and returns the segment_name of the resource in our internal namespace.
8 * @param $data iCalendar The resource to be written.
9 * @param $create_resource boolean True if this is a new resource.
10 * @param $segment_name The name of the resource within the collection.
12 function WriteCalendarMember( $data, $create_resource, $segment_name = null ) {
13 if ( !$this->IsSchedulingCollection() && !$this->IsCalendar() ) return false;
15 // function write_resource( $user_no, $path, $caldav_data, $collection_id, $author, $etag, $ic, $put_action_type, $caldav_context, $log_action=true, $weak_etag=null ) {
16 global $tz_regex;
18 $resources = $ic->GetComponents('VTIMEZONE',false); // Not matching VTIMEZONE
19 if ( !isset($resources[0]) ) {
20 $resource_type = 'Unknown';
21 /** @todo Handle writing non-calendar resources, like address book entries or random file data */
22 rollback_on_error( $caldav_context, $user_no, $path, translate('No calendar content'), 412 );
23 return false;
25 else {
26 $first = $resources[0];
27 $resource_type = $first->GetType();
30 $qry = new AwlQuery();
31 $qry->Begin();
32 $params = array(
33 ':dav_name' => $path,
34 ':user_no' => $user_no,
35 ':etag' => $etag,
36 ':dav_data' => $caldav_data,
37 ':caldav_type' => $resource_type,
38 ':session_user' => $author,
39 ':weak_etag' => $weak_etag
41 if ( $put_action_type == 'INSERT' ) {
42 create_scheduling_requests($vcal);
43 $sql = 'INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag )
44 VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )';
45 $params[':collection_id'] = $collection_id;
47 else {
48 update_scheduling_requests($vcal);
49 $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
50 modified=current_timestamp, weak_etag=:weak_etag WHERE user_no=:user_no AND dav_name=:dav_name';
52 if ( !$qry->QDo($sql,$params) ) {
53 rollback_on_error( $caldav_context, $user_no, $path);
54 return false;
57 $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
58 if ( $qry->rows() == 1 && $row = $qry->Fetch() ) {
59 $dav_id = $row->dav_id;
63 $calitem_params = array(
64 ':dav_name' => $path,
65 ':user_no' => $user_no,
66 ':etag' => $etag
68 $dtstart = $first->GetPValue('DTSTART');
69 $calitem_params[':dtstart'] = $dtstart;
70 if ( (!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '' ) {
71 $dtstart = $first->GetPValue('DUE');
74 $dtend = $first->GetPValue('DTEND');
75 if ( isset($dtend) && $dtend != '' ) {
76 dbg_error_log( 'PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION') );
77 $calitem_params[':dtend'] = $dtend;
78 $dtend = ':dtend';
80 else {
81 $dtend = 'NULL';
82 if ( $first->GetPValue('DURATION') != '' AND $dtstart != '' ) {
83 $duration = preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') );
84 $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
85 $calitem_params[':duration'] = $duration;
87 elseif ( $first->GetType() == 'VEVENT' ) {
88 /**
89 * From RFC2445 4.6.1:
90 * For cases where a "VEVENT" calendar component specifies a "DTSTART"
91 * property with a DATE data type but no "DTEND" property, the events
92 * non-inclusive end is the end of the calendar date specified by the
93 * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
94 * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
95 * the event ends on the same calendar date and time of day specified by the
96 * "DTSTART" property.
98 * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
101 $value_type = $first->GetPParamValue('DTSTART','VALUE');
102 dbg_error_log('PUT','DTSTART without DTEND. DTSTART value type is %s', $value_type );
103 if ( isset($value_type) && $value_type == 'DATE' )
104 $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
105 else
106 $dtend = ':dtstart';
110 $last_modified = $first->GetPValue('LAST-MODIFIED');
111 if ( !isset($last_modified) || $last_modified == '' ) {
112 $last_modified = gmdate( 'Ymd\THis\Z' );
114 $calitem_params[':modified'] = $last_modified;
116 $dtstamp = $first->GetPValue('DTSTAMP');
117 if ( !isset($dtstamp) || $dtstamp == '' ) {
118 $dtstamp = $last_modified;
120 $calitem_params[':dtstamp'] = $dtstamp;
122 $class = $first->GetPValue('CLASS');
123 /* Check and see if we should over ride the class. */
124 /** @todo is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */
125 if ( public_events_only($user_no, $path) ) {
126 $class = 'PUBLIC';
130 * It seems that some calendar clients don't set a class...
131 * RFC2445, 4.8.1.3:
132 * Default is PUBLIC
134 if ( !isset($class) || $class == '' ) {
135 $class = 'PUBLIC';
137 $calitem_params[':class'] = $class;
140 /** Calculate what timezone to set, first, if possible */
141 $last_tz_locn = 'Turkmenikikamukau'; // I really hope this location doesn't exist!
142 $tzid = $first->GetPParamValue('DTSTART','TZID');
143 if ( !isset($tzid) || $tzid == '' ) $tzid = $first->GetPParamValue('DUE','TZID');
144 $timezones = $ic->GetComponents('VTIMEZONE');
145 foreach( $timezones AS $k => $tz ) {
146 if ( $tz->GetPValue('TZID') != $tzid ) {
148 * We'll pretend they didn't forget to give us a TZID and that they
149 * really hope the server is running in the timezone they supplied... but be noisy about it.
151 dbg_error_log( 'ERROR', ' Event includes TZID[%s] but uses TZID[%s]!', $tz->GetPValue('TZID'), $tzid );
152 $tzid = $tz->GetPValue('TZID');
154 // This is the one
155 $tz_locn = $tz->GetPValue('X-LIC-LOCATION');
156 if ( ! isset($tz_locn) ) {
157 if ( preg_match( '#([^/]+/[^/]+)$#', $tzid, $matches ) )
158 $tz_locn = $matches[1];
159 else if ( isset($tzid) && $tzid != '' ) {
160 dbg_error_log( 'ERROR', ' Couldn\'t guess Olsen TZ from TZID[%s]. This may end in tears...', $tzid );
163 else {
164 if ( ! preg_match( $tz_regex, $tz_locn ) ) {
165 if ( preg_match( '#([^/]+/[^/]+)$#', $tzid, $matches ) ) $tz_locn = $matches[1];
169 dbg_error_log( 'PUT', ' Using TZID[%s] and location of [%s]', $tzid, (isset($tz_locn) ? $tz_locn : '') );
170 if ( isset($tz_locn) && ($tz_locn != $last_tz_locn) && preg_match( $tz_regex, $tz_locn ) ) {
171 dbg_error_log( 'PUT', ' Setting timezone to %s', $tz_locn );
172 if ( $tz_locn != '' ) {
173 $qry->QDo('SET TIMEZONE TO \''.$tz_locn."'" );
175 $last_tz_locn = $tz_locn;
177 $params = array( ':tzid' => $tzid);
178 $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params );
179 if ( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
180 $params[':tzlocn'] = $tz_locn;
181 $params[':tzspec'] = (isset($tz) ? $tz->Render() : null );
182 $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params );
184 if ( !isset($tz_locn) || $tz_locn == '' ) $tz_locn = $tzid;
188 $created = $first->GetPValue('CREATED');
189 if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
190 $calitem_params[':created'] = $created;
192 $calitem_params[':tzid'] = $tzid;
193 $calitem_params[':uid'] = $first->GetPValue('UID');
194 $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
195 $calitem_params[':location'] = $first->GetPValue('LOCATION');
196 $calitem_params[':transp'] = $first->GetPValue('TRANSP');
197 $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
198 $calitem_params[':rrule'] = $first->GetPValue('RRULE');
199 $calitem_params[':url'] = $first->GetPValue('URL');
200 $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
201 $calitem_params[':due'] = $first->GetPValue('DUE');
202 $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
203 $calitem_params[':status'] = $first->GetPValue('STATUS');
204 if ( $put_action_type == 'INSERT' ) {
205 $sql = <<<EOSQL
206 INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
207 dtstart, dtend, summary, location, class, transp,
208 description, rrule, tz_id, last_modified, url, priority,
209 created, due, percent_complete, status, collection_id )
210 VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
211 :dtstart, $dtend, :summary, :location, :class, :transp,
212 :description, :rrule, :tzid, :modified, :url, :priority,
213 :created, :due, :percent_complete, :status, $collection_id )
214 EOSQL;
215 $sync_change = 201;
217 else {
218 $sql = <<<EOSQL
219 UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
220 dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp,
221 description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
222 created=:created, due=:due, percent_complete=:percent_complete, status=:status
223 WHERE user_no=:user_no AND dav_name=:dav_name
224 EOSQL;
225 $sync_change = 200;
228 write_alarms($dav_id, $first);
229 write_attendees($dav_id, $first);
231 if ( $log_action && function_exists('log_caldav_action') ) {
232 log_caldav_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
234 else if ( $log_action ) {
235 dbg_error_log( 'PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
236 $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
239 $qry = new AwlQuery( $sql, $calitem_params );
240 if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) {
241 rollback_on_error( $caldav_context, $user_no, $path);
242 return false;
244 $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $path ) );
245 $qry->Commit();
247 dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path);
250 return $segment_name;
254 * Writes the data to a member in the collection and returns the segment_name of the resource in our internal namespace.
255 * @param $data mixed The resource to be written.
256 * @param $create_resource boolean True if this is a new resource.
257 * @param $segment_name The name of the resource within the collection.
259 function WriteMember( $data, $create_resource, $segment_name = null ) {
260 if ( ! $this->IsCollection() ) return false;
261 if ( is_object($data) ) {
262 if ( gettype($data) == 'iCalendar' ) return $this->WriteCalendarMember($data,$create_resource,$segment_name);
263 else if ( gettype($data) == 'VCard' ) return $this->WriteAddressbookMember($data,$create_resource,$segment_name);
266 return $segment_name;