Merge remote branch 'origin/master' into sched
[davical.git] / inc / WritableCollection.php
blobe9545057bd5018e0b846fd88aab892073a732406
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
8 * resource in our internal namespace.
9 *
10 * @param vCalendar $vcal The resource to be written.
11 * @param boolean $create_resource True if this is a new resource.
12 * @param boolean $do_scheduling True if we should also do scheduling for this write. Default false.
13 * @param string $segment_name The name of the resource within the collection, or null if this
14 * call should invent one based on the UID of the vCalendar.
15 * @param boolean $log_action Whether to log this action. Defaults to false since this is normally called
16 * in situations where one is writing secondary data.
17 * @return string The segment_name of the resource within the collection, as written, or false on failure.
19 function WriteCalendarMember( vCalendar $vcal, $create_resource, $do_scheduling=false, $segment_name = null, $log_action=false ) {
20 if ( !$this->IsSchedulingCollection() && !$this->IsCalendar() ) {
21 dbg_error_log( 'PUT', '"%s" is not a calendar or scheduling collection!', $this->dav_name);
22 return false;
25 global $tz_regex, $session, $caldav_context;
27 $resources = $vcal->GetComponents('VTIMEZONE',false); // Not matching VTIMEZONE
28 $user_no = $this->user_no();
29 $collection_id = $this->collection_id();
31 if ( !isset($resources[0]) ) {
32 rollback_on_error( $caldav_context, $user_no, $this->dav_name.'/'.$segment_name, translate('No calendar content'), 412 );
33 return false;
35 else {
36 $first = $resources[0];
37 $resource_type = $first->GetType();
40 $uid = $first->GetPValue('UID');
41 if ( empty($segment_name) ) {
42 $segment_name = $uid.'.ics';
44 $path = $this->dav_name() . $segment_name;
46 $caldav_data = $vcal->Render();
47 $etag = md5($caldav_data);
48 $weak_etag = null;
50 $qry = new AwlQuery();
51 $existing_transaction_state = $qry->TransactionState();
52 if ( $existing_transaction_state == 0 ) $qry->Begin();
55 if ( $create_resource ) {
56 $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id');
58 else {
59 $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path));
61 if ( $qry->rows() != 1 || !($row = $qry->Fetch()) ) {
62 // No dav_id? => We're toast!
63 dbg_error_log( 'PUT', 'No dav_id!!!', $path);
64 rollback_on_error( $caldav_context, $user_no, $path);
65 return false;
67 $dav_id = $row->dav_id;
69 $calitem_params = array(
70 ':dav_name' => $path,
71 ':user_no' => $user_no,
72 ':etag' => $etag,
73 ':dav_id' => $dav_id
76 $dav_params = array_merge($calitem_params, array(
77 ':dav_data' => $caldav_data,
78 ':caldav_type' => $resource_type,
79 ':session_user' => $session->user_no,
80 ':weak_etag' => $weak_etag
81 ) );
83 if ( $create_resource ) {
84 if ( !$this->IsSchedulingCollection() && $do_scheduling ) do_scheduling_requests($vcal,true);
85 $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 )
86 VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )';
87 $dav_params[':collection_id'] = $collection_id;
89 else {
90 if ( !$this->IsSchedulingCollection() && $do_scheduling ) do_scheduling_requests($vcal,false);
91 $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user,
92 modified=current_timestamp, weak_etag=:weak_etag WHERE dav_id=:dav_id';
94 if ( !$qry->QDo($sql,$dav_params) ) {
95 rollback_on_error( $caldav_context, $user_no, $path);
96 return false;
99 $dtstart = $first->GetPValue('DTSTART');
100 $calitem_params[':dtstart'] = $dtstart;
101 if ( (!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '' ) {
102 $dtstart = $first->GetPValue('DUE');
105 $dtend = $first->GetPValue('DTEND');
106 if ( isset($dtend) && $dtend != '' ) {
107 dbg_error_log( 'PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION') );
108 $calitem_params[':dtend'] = $dtend;
109 $dtend = ':dtend';
111 else {
112 $dtend = 'NULL';
113 if ( $first->GetPValue('DURATION') != '' AND $dtstart != '' ) {
114 $duration = preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') );
115 $dtend = '(:dtstart::timestamp with time zone + :duration::interval)';
116 $calitem_params[':duration'] = $duration;
118 elseif ( $first->GetType() == 'VEVENT' ) {
120 * From RFC2445 4.6.1:
121 * For cases where a "VEVENT" calendar component specifies a "DTSTART"
122 * property with a DATE data type but no "DTEND" property, the events
123 * non-inclusive end is the end of the calendar date specified by the
124 * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
125 * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
126 * the event ends on the same calendar date and time of day specified by the
127 * "DTSTART" property.
129 * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
132 $value_type = $first->GetPParamValue('DTSTART','VALUE');
133 dbg_error_log('PUT','DTSTART without DTEND. DTSTART value type is %s', $value_type );
134 if ( isset($value_type) && $value_type == 'DATE' )
135 $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)';
136 else
137 $dtend = ':dtstart';
141 $last_modified = $first->GetPValue('LAST-MODIFIED');
142 if ( !isset($last_modified) || $last_modified == '' ) {
143 $last_modified = gmdate( 'Ymd\THis\Z' );
145 $calitem_params[':modified'] = $last_modified;
147 $dtstamp = $first->GetPValue('DTSTAMP');
148 if ( !isset($dtstamp) || $dtstamp == '' ) {
149 $dtstamp = $last_modified;
151 $calitem_params[':dtstamp'] = $dtstamp;
153 $class = $first->GetPValue('CLASS');
154 /* Check and see if we should over ride the class. */
155 /** @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. */
156 if ( public_events_only($user_no, $path) ) {
157 $class = 'PUBLIC';
161 * It seems that some calendar clients don't set a class...
162 * RFC2445, 4.8.1.3:
163 * Default is PUBLIC
165 if ( !isset($class) || $class == '' ) {
166 $class = 'PUBLIC';
168 $calitem_params[':class'] = $class;
171 /** Calculate what timezone to set, first, if possible */
172 $last_tz_locn = 'Turkmenikikamukau'; // I really hope this location doesn't exist!
173 $dtstart_prop = $first->GetProperty('DTSTART');
174 $tzid = $dtstart_prop->GetParameterValue('TZID');
175 if ( empty($tzid) && $first->GetType() == 'VTODO' ) {
176 $due_prop = $first->GetProperty('DUE');
177 $tzid = $due_prop->GetParameterValue('TZID');
179 $timezones = $vcal->GetComponents('VTIMEZONE');
180 foreach( $timezones AS $k => $tz ) {
181 if ( $tz->GetPValue('TZID') != $tzid ) {
183 * We'll skip any tz definitions that are for a TZID other than the DTSTART/DUE on the first VEVENT/VTODO
185 dbg_error_log( 'ERROR', ' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue('TZID'), $tzid );
186 continue;
188 // This is the one
189 $tz_locn = $tz->GetPValue('X-LIC-LOCATION');
190 if ( ! isset($tz_locn) ) {
191 if ( preg_match( '#([^/]+/[^/]+)$#', $tzid, $matches ) )
192 $tz_locn = $matches[1];
193 else if ( isset($tzid) && $tzid != '' ) {
194 dbg_error_log( 'ERROR', ' Couldn\'t guess Olsen TZ from TZID[%s]. This may end in tears...', $tzid );
197 else {
198 if ( ! preg_match( $tz_regex, $tz_locn ) ) {
199 if ( preg_match( '#([^/]+/[^/]+)$#', $tzid, $matches ) ) $tz_locn = $matches[1];
203 dbg_error_log( 'PUT', ' Using TZID[%s] and location of [%s]', $tzid, (isset($tz_locn) ? $tz_locn : '') );
204 if ( isset($tz_locn) && ($tz_locn != $last_tz_locn) && preg_match( $tz_regex, $tz_locn ) ) {
205 dbg_error_log( 'PUT', ' Setting timezone to %s', $tz_locn );
206 if ( $tz_locn != '' ) {
207 $qry->QDo('SET TIMEZONE TO \''.$tz_locn."'" );
209 $last_tz_locn = $tz_locn;
211 $params = array( ':tzid' => $tzid);
212 $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params );
213 if ( $qry->Exec('PUT',__LINE__,__FILE__) && $qry->rows() == 0 ) {
214 $params[':tzlocn'] = $tz_locn;
215 $params[':tzspec'] = (isset($tz) ? $tz->Render() : null );
216 $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params );
218 if ( !isset($tz_locn) || $tz_locn == '' ) $tz_locn = $tzid;
222 $created = $first->GetPValue('CREATED');
223 if ( $created == '00001231T000000Z' ) $created = '20001231T000000Z';
224 $calitem_params[':created'] = $created;
226 $calitem_params[':tzid'] = $tzid;
227 $calitem_params[':uid'] = $uid;
228 $calitem_params[':summary'] = $first->GetPValue('SUMMARY');
229 $calitem_params[':location'] = $first->GetPValue('LOCATION');
230 $calitem_params[':transp'] = $first->GetPValue('TRANSP');
231 $calitem_params[':description'] = $first->GetPValue('DESCRIPTION');
232 $calitem_params[':rrule'] = $first->GetPValue('RRULE');
233 $calitem_params[':url'] = $first->GetPValue('URL');
234 $calitem_params[':priority'] = $first->GetPValue('PRIORITY');
235 $calitem_params[':due'] = $first->GetPValue('DUE');
236 $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE');
237 $calitem_params[':status'] = $first->GetPValue('STATUS');
238 if ( $create_resource ) {
239 $sql = <<<EOSQL
240 INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp,
241 dtstart, dtend, summary, location, class, transp,
242 description, rrule, tz_id, last_modified, url, priority,
243 created, due, percent_complete, status, collection_id )
244 VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp,
245 :dtstart, $dtend, :summary, :location, :class, :transp,
246 :description, :rrule, :tzid, :modified, :url, :priority,
247 :created, :due, :percent_complete, :status, $collection_id )
248 EOSQL;
249 $sync_change = 201;
251 else {
252 $sql = <<<EOSQL
253 UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp,
254 dtstart=:dtstart, dtend=$dtend, summary=:summary, location=:location, class=:class, transp=:transp,
255 description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority,
256 created=:created, due=:due, percent_complete=:percent_complete, status=:status
257 WHERE user_no=:user_no AND dav_name=:dav_name
258 EOSQL;
259 $sync_change = 200;
262 if ( !$this->IsSchedulingCollection() ) {
263 write_alarms($dav_id, $first);
264 write_attendees($dav_id, $vcal);
265 if ( $log_action && function_exists('log_caldav_action') ) {
266 log_caldav_action( $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
268 else if ( $log_action ) {
269 dbg_error_log( 'PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.',
270 $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path );
274 $qry = new AwlQuery( $sql, $calitem_params );
275 if ( !$qry->Exec('PUT',__LINE__,__FILE__) ) {
276 rollback_on_error( $caldav_context, $user_no, $path);
277 return false;
279 $qry->QDo("SELECT write_sync_change( $collection_id, $sync_change, :dav_name)", array(':dav_name' => $path ) );
280 if ( $existing_transaction_state == 0 ) $qry->Commit();
282 dbg_error_log( 'PUT', 'User: %d, ETag: %s, Path: %s', $session->user_no, $etag, $path);
285 return $segment_name;
289 * Writes the data to a member in the collection and returns the segment_name of the
290 * resource in our internal namespace.
292 * A caller who wants scheduling not to happen for this write must already
293 * know they are dealing with a calendar, so should be calling WriteCalendarMember
294 * directly.
296 * @param $resource mixed The resource to be written.
297 * @param $create_resource boolean True if this is a new resource.
298 * @param $segment_name The name of the resource within the collection, or false on failure.
299 * @param boolean $log_action Whether to log this action. Defaults to true since this is normally called
300 * in situations where one is writing primary data.
301 * @return string The segment_name that was given, or one that was assigned if null was given.
303 function WriteMember( $resource, $create_resource, $segment_name = null, $log_action=true ) {
304 if ( ! $this->IsCollection() ) {
305 dbg_error_log( 'PUT', '"%s" is not a collection path', $this->dav_name);
306 return false;
308 if ( ! is_object($resource) ) {
309 dbg_error_log( 'PUT', 'No data supplied!' );
310 return false;
313 if ( $resource instanceof vCalendar ) {
314 return $this->WriteCalendarMember($resource,$create_resource,true,$segment_name,$log_action);
316 else if ( $resource instanceof VCard )
317 return $this->WriteAddressbookMember($resource,$create_resource,$segment_name, $log_action);
319 return $segment_name;