~aw/ubuntu/lucid/davical/trunk

« back to all changes in this revision

Viewing changes to inc/caldav-PUT-functions.php

  • Committer: Bazaar Package Importer
  • Author(s): Andrew McMillan
  • Date: 2008-11-18 18:33:17 UTC
  • Revision ID: james.westby@ubuntu.com-20081118183317-8cbi6iy140mcnr4e
Tags: 0.9.6.2
* Fix creation of database when template1 has pl/pgsql installed.
* Improvements to regression testing.
* Switch to use iCalComponent object rather than iCalendar, in many cases.
* Calculate DTEND when it is not set according to RFC2445.
* Much improved XML namespace handling.
* Configuration setting to control whether I should see appointments where I
  am organizer/attendee, even if they are PRIVATE and not in my own calendar.
* Updated French translation (closes: #505861)
* Add facility to delete collections through the management interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
include_once("iCalendar.php");
19
19
 
20
20
/**
 
21
* A regex which will match most reasonable timezones acceptable to PostgreSQL.
 
22
*/
 
23
$tz_regex = ':^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Etc|Europe|Indian|Mexico|Mideast|Pacific|US)/[a-z]+$:i';
 
24
 
 
25
/**
21
26
* This function launches an error
22
27
* @param boolean $caldav_context Whether we are responding via CalDAV or interactively
23
28
* @param int $user_no the user wich will receive this ics file
123
128
 
124
129
/**
125
130
* Create scheduling requests in the schedule inbox for the
126
 
* @param iCalendar $ic The iCalendar object we should create scheduling requests for.
 
131
* @param iCalComponent $component The VEVENT/VTODO/... resource we are scheduling
127
132
*/
128
 
function create_scheduling_requests( $ic ) {
129
 
  $component =& $ic->component->FirstNonTimezone();
130
 
  $attendees = $component->GetProperties('ATTENDEE');
 
133
function create_scheduling_requests( $resource ) {
 
134
  $attendees = $resource->GetPropertiesByPath('/VCALENDAR/*/ATTENDEE');
131
135
  if ( preg_match( '# iCal/\d#', $_SERVER['HTTP_USER_AGENT']) ) {
132
136
    dbg_error_log( "POST", "Non-compliant iCal request.  Using X-WR-ATTENDEE property" );
133
 
    $wr_attendees = $component->GetProperties('X-WR-ATTENDEE');
 
137
    $wr_attendees = $resource->GetPropertiesByPath('/VCALENDAR/*/X-WR-ATTENDEE');
134
138
    foreach( $wr_attendees AS $k => $v ) {
135
139
      $attendees[] = $v;
136
140
    }
151
155
* Any VEVENTs with the same UID will be concatenated together
152
156
*/
153
157
function import_collection( $ics_content, $user_no, $path, $caldav_context ) {
154
 
  global $c, $session;
155
 
  // According to RFC2445 we should always end with CRLF, but the CalDAV spec says
156
 
  // that normalising XML parsers often muck with it and may remove the CR.
157
 
  $icalendar = preg_replace('/\r?\n /', '', $ics_content );
 
158
  global $c, $session, $tz_regex;
 
159
 
158
160
  if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put'])) ) {
159
161
    $fh = fopen('/tmp/PUT-2.txt','w');
160
162
    if ( $fh ) {
161
 
      fwrite($fh,$icalendar);
 
163
      fwrite($fh,$ics_content);
162
164
      fclose($fh);
163
165
    }
164
166
  }
165
167
 
166
 
  $lines = preg_split('/\r?\n/', $icalendar );
167
 
 
168
 
  $events = array();
169
 
  $event_ids = array();
170
 
  $timezones = array();
171
 
 
172
 
  $current = "";
173
 
  unset($event_id);
174
 
  $state = "";
175
 
  $tzid = 'unknown';
176
 
  foreach( $lines AS $lno => $line ) {
177
 
    if ( $state == "" ) {
178
 
      if ( preg_match( '/^BEGIN:(VEVENT|VTIMEZONE|VTODO|VJOURNAL)$/', $line, $matches ) ) {
179
 
        $current .= $line."\n";
180
 
        $state = $matches[1];
181
 
        dbg_error_log( "PUT", "CalendarLine[%04d] - %s: %s", $lno, $state, $line );
182
 
      }
183
 
    }
184
 
    else {
185
 
      $current .= $line."\n";
186
 
      if ( preg_match( '/^UID:(.*)$/', $line, &$matches) ) {
187
 
        $event_id = $matches[1];
188
 
        dbg_error_log( "PUT", " Processing event with UID of '%s'", $event_id );
189
 
      }
190
 
      else if ( $line == "END:$state" ) {
191
 
        switch ( $state ) {
192
 
          case 'VTIMEZONE':
193
 
            $timezones[$tzid] = $current;
194
 
            dbg_error_log( "PUT", " Ended VTIMEZONE for TZID '%s' ", $tzid );
195
 
            break;
196
 
          case 'VEVENT':
197
 
          case 'VTODO':
198
 
          case 'VJOURNAL':
199
 
          default:
200
 
            if ( isset($event_ids[$event_id]) ) {
201
 
              // All of the VEVENT (or whatever) with the same UID are concatenated
202
 
              $events[$event_ids[$event_id]]['data'] .= $current;
203
 
            }
204
 
            else if ( isset($event_id) ) {
205
 
              $event_ids[$event_id] = count($events);
206
 
              $events[] = array( 'data' => $current, 'tzid' => $tzid );
207
 
            }
208
 
            unset($event_id);
209
 
            dbg_error_log( "PUT", " Ended %s with TZID '%s' ", $state, $tzid );
210
 
            break;
211
 
        }
212
 
        $state = "";
213
 
        $current = "";
214
 
        $tzid = 'unknown';
215
 
      }
216
 
      else if ( preg_match( '/TZID[:=]([^:]+)(:|$)/', $line, $matches ) ) {
217
 
        $tzid = $matches[1];
218
 
        dbg_error_log( "PUT", " Found TZID of '%s' in '%s'", $tzid, $line );
219
 
      }
220
 
    }
221
 
  }
222
 
  dbg_error_log( "PUT", " Finished input after $lno lines" );
 
168
  $calendar = new iCalComponent($ics_content);
 
169
  $timezones = $calendar->GetComponents('VTIMEZONE',true);
 
170
  $components = $calendar->GetComponents('VTIMEZONE',false);
 
171
 
 
172
  $tz_ids    = array();
 
173
  foreach( $timezones AS $k => $tz ) {
 
174
    $tz_ids[$tz->GetPValue('TZID')] = $k;
 
175
  }
 
176
 
 
177
  /** Build an array of resources.  Each resource is an array of iCalComponent */
 
178
  $resources = array();
 
179
  foreach( $components AS $k => $comp ) {
 
180
    $uid = $comp->GetPValue('UID');
 
181
    if ( !isset($resources[$uid]) ) $resources[$uid] = array();
 
182
    $resources[$uid][] = $comp;
 
183
 
 
184
    /** Ensure we have the timezone component for this in our array as well */
 
185
    $tzid = $comp->GetPParamValue('DTSTART', 'TZID');
 
186
    if ( !isset($tzid) || $tzid == "" ) $tzid = $comp->GetPParamValue('DUE','TZID');
 
187
    if ( !isset($resources[$uid][$tzid]) && isset($tz_ids[$tzid]) ) {
 
188
      $resources[$uid][$tzid] = $timezones[$tz_ids[$tzid]];
 
189
    }
 
190
  }
 
191
 
223
192
 
224
193
  $sql = "SELECT * FROM collection WHERE user_no = ? AND dav_name = ?;";
225
194
  $qry = new PgQuery( $sql, $user_no, $path );
230
199
  }
231
200
  $collection = $qry->Fetch();
232
201
 
233
 
  $qry = new PgQuery("BEGIN; DELETE FROM calendar_item WHERE user_no=? AND dav_name ~ ?; DELETE FROM caldav_data WHERE user_no=? AND dav_name ~ ?;", $user_no, $path.'[^/]+$', $user_no, $path.'[^/]+$');
234
 
  if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $user_no, $path );
235
 
 
236
 
  foreach( $events AS $k => $event ) {
237
 
    dbg_error_log( "PUT", "Putting event %d with data: %s", $k, $event['data'] );
238
 
    $icalendar = iCalendar::iCalHeader() . $event['data'] . (isset($timezones[$event['tzid']])?$timezones[$event['tzid']]:"") . iCalendar::iCalFooter();
239
 
    $ic = new iCalendar( array( 'icalendar' => $icalendar ) );
 
202
  $qry = new PgQuery("BEGIN; DELETE FROM calendar_item WHERE user_no=? AND collection_id = ?; DELETE FROM caldav_data WHERE user_no=? AND collection_id = ?;", $user_no, $collection->collection_id, $user_no, $collection->collection_id);
 
203
  if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $user_no, $collection->collection_id );
 
204
 
 
205
  $last_tz_locn = '';
 
206
  foreach( $resources AS $uid => $resource ) {
 
207
    /** Construct the VCALENDAR data */
 
208
    $vcal = new iCalComponent();
 
209
    $vcal->VCalendar();
 
210
    $vcal->SetComponents($resource);
 
211
    $icalendar = $vcal->Render();
 
212
 
 
213
    /** As ever, we mostly deal with the first resource component */
 
214
    $first = $resource[0];
 
215
 
 
216
    $sql = '';
240
217
    $etag = md5($icalendar);
241
 
    $event_path = sprintf( "%s%d.ics", $path, $k);
 
218
    $type = $first->GetType();
 
219
    $resource_path = sprintf( "%s%s.ics", $path, $uid );
242
220
    $qry = new PgQuery( "INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id ) VALUES( ?, ?, ?, ?, ?, ?, current_timestamp, current_timestamp, ? )",
243
 
                          $user_no, $event_path, $etag, $icalendar, $ic->type, $session->user_no, $collection->collection_id );
 
221
                          $user_no, $resource_path, $etag, $icalendar, $type, $session->user_no, $collection->collection_id );
244
222
    if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $user_no, $path );
245
223
 
246
 
    $sql = "";
247
 
    if ( preg_match(':^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Etc|Europe|Indian|Mexico|Mideast|Pacific|US)/[a-z]+$:i', $ic->tz_locn ) ) {
248
 
      // We only set the timezone if it looks reasonable enough for us
249
 
      $sql = ( $ic->tz_locn == '' ? '' : "SET TIMEZONE TO ".qpg($ic->tz_locn).";" );
250
 
    }
251
 
 
252
 
    $dtstart = $ic->Get('dtstart');
253
 
    if ( (!isset($dtstart) || $dtstart == "") && $ic->Get('due') != "" ) {
254
 
      $dtstart = $ic->Get('due');
255
 
    }
256
 
 
257
 
    $dtend = $ic->Get('dtend');
258
 
    if ( (!isset($dtend) || "$dtend" == "") && $ic->Get('duration') != "" AND $dtstart != "" ) {
259
 
      $duration = preg_replace( '#[PT]#', ' ', $ic->Get('duration') );
260
 
      $dtend = '('.qpg($dtstart).'::timestamp with time zone + '.qpg($duration).'::interval)';
 
224
    $dtstart = $first->GetPValue('DTSTART');
 
225
    if ( (!isset($dtstart) || $dtstart == "") && $first->GetPValue('DUE') != "" ) {
 
226
      $dtstart = $first->GetPValue('DUE');
 
227
    }
 
228
 
 
229
    $dtend = $first->GetPValue('DTEND');
 
230
    if ( (!isset($dtend) || "$dtend" == "") ) {
 
231
      if ( $first->GetPValue('DURATION') != "" AND $dtstart != "" ) {
 
232
        $duration = preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') );
 
233
        $dtend = '('.qpg($dtstart).'::timestamp with time zone + '.qpg($duration).'::interval)';
 
234
      }
 
235
      elseif ( $first->GetType() == 'VEVENT' ) {
 
236
        /**
 
237
        * From RFC2445 4.6.1:
 
238
        * For cases where a "VEVENT" calendar component specifies a "DTSTART"
 
239
        * property with a DATE data type but no "DTEND" property, the events
 
240
        * non-inclusive end is the end of the calendar date specified by the
 
241
        * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
 
242
        * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
 
243
        * the event ends on the same calendar date and time of day specified by the
 
244
        * "DTSTART" property.
 
245
        *
 
246
        * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
 
247
        *
 
248
        */
 
249
        $value_type = $first->GetPParamValue('DTSTART','VALUE');
 
250
        dbg_error_log("PUT","DTSTART without DTEND. DTSTART value type is %s", $value_type );
 
251
        if ( isset($value_type) && $value_type == 'DATE' )
 
252
          $dtend = '('.qpg($dtstart)."::timestamp with time zone::date + '1 day'::interval)";
 
253
        else
 
254
          $dtend = qpg($dtstart);
 
255
 
 
256
      }
 
257
      if ( $dtend == "" ) $dtend = 'NULL';
261
258
    }
262
259
    else {
263
 
      dbg_error_log( "PUT", " DTEND: '%s', DTSTART: '%s', DURATION: '%s'", $dtend, $dtstart, $ic->Get('duration') );
 
260
      dbg_error_log( "PUT", " DTEND: '%s', DTSTART: '%s', DURATION: '%s'", $dtend, $dtstart, $first->GetPValue('DURATION') );
264
261
      $dtend = qpg($dtend);
265
262
    }
266
263
 
267
 
    $last_modified = $ic->Get("last-modified");
268
 
    if ( !isset($last_modified) || $last_modified == '' ) {
269
 
      $last_modified = gmdate( 'Ymd\THis\Z' );
270
 
    }
271
 
 
272
 
    $dtstamp = $ic->Get("dtstamp");
273
 
    if ( !isset($dtstamp) || $dtstamp == '' ) {
274
 
      $dtstamp = $last_modified;
275
 
    }
276
 
 
277
 
    $class = $ic->Get("class");
278
 
    /* Check and see if we should over ride the class. */
279
 
    if ( public_events_only($user_no, $path) ) {
280
 
      $class = 'PUBLIC';
281
 
    }
282
 
 
283
 
    /*
284
 
     * It seems that some calendar clients don't set a class...
285
 
     * RFC2445, 4.8.1.3:
286
 
     * Default is PUBLIC
287
 
     */
288
 
    if ( !isset($class) || $class == '' ) {
289
 
      $class = 'PUBLIC';
 
264
    $last_modified = $first->GetPValue("LAST-MODIFIED");
 
265
    if ( !isset($last_modified) || $last_modified == '' ) $last_modified = gmdate( 'Ymd\THis\Z' );
 
266
 
 
267
    $dtstamp = $first->GetPValue("DTSTAMP");
 
268
    if ( !isset($dtstamp) || $dtstamp == '' ) $dtstamp = $last_modified;
 
269
 
 
270
    /** RFC2445, 4.8.1.3: Default is PUBLIC, or also if overridden by the collection settings */
 
271
    $class = ($collection->public_events_only == 't' ? 'PUBLIC' : $first->GetPValue("CLASS") );
 
272
    if ( !isset($class) || $class == '' ) $class = 'PUBLIC';
 
273
 
 
274
 
 
275
    /** Calculate what timezone to set, first, if possible */
 
276
    $tzid = $first->GetPParamValue('DTSTART','TZID');
 
277
    if ( !isset($tzid) || $tzid == "" ) $tzid = $first->GetPParamValue('DUE','TZID');
 
278
    if ( isset($tzid) && $tzid != "" ) {
 
279
      if ( isset($resource[$tzid]) ) {
 
280
        $tz = $resource[$tzid];
 
281
        $tz_locn = $tz->GetPValue('X-LIC-LOCATION');
 
282
      }
 
283
      else {
 
284
        unset($tz);
 
285
        unset($tz_locn);
 
286
      }
 
287
      dbg_error_log( "PUT", " Using TZID[%s] and location of [%s]", $tzid, (isset($tz_locn) ? $tz_locn : '') );
 
288
      if ( ! isset($tz_locn) || ! preg_match( $tz_regex, $tz_locn ) ) {
 
289
        if ( preg_match( '#/([^/]+/[^/]+)$#', $tzid, $matches ) ) {
 
290
          $tz_locn = $matches[1];
 
291
        }
 
292
      }
 
293
      if ( isset($tz_locn) && ($tz_locn != $last_tz_locn) && preg_match( $tz_regex, $tz_locn ) ) {
 
294
        dbg_error_log( "PUT", " Setting timezone to %s", $tz_locn );
 
295
        $sql .= ( $tz_locn == '' ? '' : "SET TIMEZONE TO ".qpg($tz_locn).";" );
 
296
        $last_tz_locn = $tz_locn;
 
297
      }
 
298
      $qry = new PgQuery("SELECT tz_locn FROM time_zone WHERE tz_id = ?", $tzid );
 
299
      if ( $qry->Exec() && $qry->rows == 0 ) {
 
300
        $qry = new PgQuery("INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(?,?,?)", $tzid, $tz_locn, (isset($tz) ? $tz->Render() : null) );
 
301
        $qry->Exec();
 
302
      }
 
303
      if ( !isset($tz_locn) || $tz_locn == "" ) $tz_locn = $tzid;
 
304
    }
 
305
    else {
 
306
      $tzid = null;
290
307
    }
291
308
 
292
309
    $sql .= <<<EOSQL
293
 
  INSERT INTO calendar_item (user_no, dav_name, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
294
 
                      description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, collection_id )
295
 
                   VALUES ( ?, ?, ?, ?, ?, ?, $dtend, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
 
310
    INSERT INTO calendar_item (user_no, dav_name, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp,
 
311
                      description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id )
 
312
                   VALUES ( ?, ?, ?, ?, ?, ?, $dtend, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
296
313
EOSQL;
297
314
 
298
 
    $qry = new PgQuery( $sql, $user_no, $event_path, $etag, $ic->Get('uid'), $dtstamp,
299
 
                              $ic->Get('dtstart'), $ic->Get('summary'), $ic->Get('location'),
300
 
                              $class, $ic->Get('transp'), $ic->Get('description'), $ic->Get('rrule'), $ic->Get('tz_id'),
301
 
                              $last_modified, $ic->Get('url'), $ic->Get('priority'), $ic->Get('created'),
302
 
                              $ic->Get('due'), $ic->Get('percent-complete'), $collection->collection_id
 
315
    $qry = new PgQuery( $sql, $user_no, $resource_path, $etag, $first->GetPValue('UID'), $dtstamp,
 
316
                              $first->GetPValue('DTSTART'), $first->GetPValue('SUMMARY'), $first->GetPValue('LOCATION'),
 
317
                              $class, $first->GetPValue('TRANSP'), $first->GetPValue('DESCRIPTION'), $first->GetPValue('RRULE'), $tzid,
 
318
                              $last_modified, $first->GetPValue('URL'), $first->GetPValue('PRIORITY'), $first->GetPValue('CREATED'),
 
319
                              $first->GetPValue('DUE'), $first->GetPValue('PERCENT-COMPLETE'), $first->GetPValue('STATUS'), $collection->collection_id
303
320
                        );
304
321
    if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $user_no, $path);
305
322
 
306
 
    create_scheduling_requests( $ic );
 
323
    create_scheduling_requests( $vcal );
307
324
  }
308
325
 
309
326
  $qry = new PgQuery("COMMIT;");
319
336
* @return string Either 'INSERT' or 'UPDATE': the type of action that the PUT resulted in
320
337
*/
321
338
function putCalendarResource( &$request, $author, $caldav_context ) {
 
339
  global $tz_regex;
 
340
 
322
341
  $etag = md5($request->raw_post);
323
 
  $ic = new iCalendar(array( 'icalendar' => $request->raw_post ));
 
342
  $ic = new iCalComponent( $request->raw_post );
324
343
 
325
 
  dbg_log_array( "PUT", 'EVENT', $ic->properties['VCALENDAR'][0], true );
 
344
  dbg_log_array( "PUT", 'EVENT', $ic->components, true );
326
345
 
327
346
  /**
328
347
  * We read any existing object so we can check the ETag.
383
402
    }
384
403
  }
385
404
 
 
405
  $resources = $ic->GetComponents('VTIMEZONE',false); // Not matching VTIMEZONE
 
406
  $first = $resources[0];
 
407
 
386
408
  if ( $put_action_type == 'INSERT' ) {
387
409
    $qry = new PgQuery( "BEGIN; INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id ) VALUES( ?, ?, ?, ?, ?, ?, current_timestamp, current_timestamp, ? )",
388
 
                           $request->user_no, $request->path, $etag, $request->raw_post, $ic->type, $author, $request->collection_id );
 
410
                           $request->user_no, $request->path, $etag, $request->raw_post, $first->GetType(), $author, $request->collection_id );
389
411
    if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $request->user_no, $request->path);
390
412
  }
391
413
  else {
392
414
    $qry = new PgQuery( "BEGIN;UPDATE caldav_data SET caldav_data=?, dav_etag=?, caldav_type=?, logged_user=?, modified=current_timestamp WHERE user_no=? AND dav_name=?",
393
 
                           $request->raw_post, $etag, $ic->type, $author, $request->user_no, $request->path );
 
415
                           $request->raw_post, $etag, $first->GetType(), $author, $request->user_no, $request->path );
394
416
    if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $request->user_no, $request->path);
395
417
  }
396
418
 
397
 
  /**
398
 
  * Build the SQL for inserting/updating the calendar_item record
399
 
  */
400
 
  $sql = "";
401
 
  if ( preg_match(':^(Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Brazil|Canada|Chile|Etc|Europe|Indian|Mexico|Mideast|Pacific|US)/[a-z]+$:i', $ic->tz_locn ) ) {
402
 
    // We only set the timezone if it looks reasonable enough for us
403
 
    $sql = ( $ic->tz_locn == '' ? '' : "SET TIMEZONE TO ".qpg($ic->tz_locn).";" );
404
 
  }
405
 
 
406
 
  $dtstart = $ic->Get('DTSTART');
407
 
  if ( (!isset($dtstart) || $dtstart == "") && $ic->Get('DUE') != "" ) {
408
 
    $dtstart = $ic->Get('DUE');
409
 
  }
410
 
 
411
 
  $dtend = $ic->Get('DTEND');
412
 
  if ( (!isset($dtend) || "$dtend" == "") && $ic->Get('DURATION') != "" AND $dtstart != "" ) {
413
 
    $duration = preg_replace( '#[PT]#', ' ', $ic->Get('DURATION') );
414
 
    $dtend = '('.qpg($dtstart).'::timestamp with time zone + '.qpg($duration).'::interval)';
 
419
  $dtstart = $first->GetPValue('DTSTART');
 
420
  if ( (!isset($dtstart) || $dtstart == "") && $first->GetPValue('DUE') != "" ) {
 
421
    $dtstart = $first->GetPValue('DUE');
 
422
  }
 
423
 
 
424
  $dtend = $first->GetPValue('DTEND');
 
425
  if ( (!isset($dtend) || "$dtend" == "") ) {
 
426
    if ( $first->GetPValue('DURATION') != "" AND $dtstart != "" ) {
 
427
      $duration = preg_replace( '#[PT]#', ' ', $first->GetPValue('DURATION') );
 
428
      $dtend = '('.qpg($dtstart).'::timestamp with time zone + '.qpg($duration).'::interval)';
 
429
    }
 
430
    elseif ( $first->GetType() == 'VEVENT' ) {
 
431
      /**
 
432
      * From RFC2445 4.6.1:
 
433
      * For cases where a "VEVENT" calendar component specifies a "DTSTART"
 
434
      * property with a DATE data type but no "DTEND" property, the events
 
435
      * non-inclusive end is the end of the calendar date specified by the
 
436
      * "DTSTART" property. For cases where a "VEVENT" calendar component specifies
 
437
      * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property,
 
438
      * the event ends on the same calendar date and time of day specified by the
 
439
      * "DTSTART" property.
 
440
      *
 
441
      * So we're looking for 'VALUE=DATE', to identify the duration, effectively.
 
442
      *
 
443
      */
 
444
      $value_type = $first->GetPParamValue('DTSTART','VALUE');
 
445
      dbg_error_log("PUT","DTSTART without DTEND. DTSTART value type is %s", $value_type );
 
446
      if ( isset($value_type) && $value_type == 'DATE' )
 
447
        $dtend = '('.qpg($dtstart)."::timestamp with time zone::date + '1 day'::interval)";
 
448
      else
 
449
        $dtend = qpg($dtstart);
 
450
 
 
451
    }
 
452
    if ( $dtend == "" ) $dtend = 'NULL';
415
453
  }
416
454
  else {
417
 
    dbg_error_log( "PUT", " DTEND: '%s', DTSTART: '%s', DURATION: '%s'", $dtend, $dtstart, $ic->Get('DURATION') );
 
455
    dbg_error_log( "PUT", " DTEND: '%s', DTSTART: '%s', DURATION: '%s'", $dtend, $dtstart, $first->GetPValue('DURATION') );
418
456
    $dtend = qpg($dtend);
419
457
  }
420
458
 
421
 
  $last_modified = $ic->Get("LAST-MODIFIED");
 
459
  $last_modified = $first->GetPValue("LAST-MODIFIED");
422
460
  if ( !isset($last_modified) || $last_modified == '' ) {
423
461
    $last_modified = gmdate( 'Ymd\THis\Z' );
424
462
  }
425
463
 
426
 
  $dtstamp = $ic->Get("DTSTAMP");
 
464
  $dtstamp = $first->GetPValue("DTSTAMP");
427
465
  if ( !isset($dtstamp) || $dtstamp == '' ) {
428
466
    $dtstamp = $last_modified;
429
467
  }
430
468
 
431
 
  $class = $ic->Get("class");
 
469
  $class = $first->GetPValue("CLASS");
432
470
  /* Check and see if we should over ride the class. */
433
471
  if ( public_events_only($request->user_no, $request->path) ) {
434
472
    $class = 'PUBLIC';
444
482
  }
445
483
 
446
484
 
 
485
  /**
 
486
  * Build the SQL for inserting/updating the calendar_item record
 
487
  */
 
488
  $sql = '';
 
489
 
 
490
  /** Calculate what timezone to set, first, if possible */
 
491
  $tzid = $first->GetPParamValue('DTSTART','TZID');
 
492
  if ( !isset($tzid) || $tzid == "" ) $tzid = $first->GetPParamValue('DUE','TZID');
 
493
  $timezones = $ic->GetComponents('VTIMEZONE');
 
494
  foreach( $timezones AS $k => $tz ) {
 
495
    if ( $tz->GetPValue('TZID') == $tzid ) {
 
496
      // This is the one
 
497
      $tz_locn = $tz->GetPValue('X-LIC-LOCATION');
 
498
      dbg_error_log( "PUT", " Using TZID[%s] and location of [%s]", $tzid, $tz_locn );
 
499
      if ( ! isset($tz_locn) || ! preg_match( $tz_regex, $tz_locn ) ) {
 
500
        if ( preg_match( '#/([^/]+/[^/]+)$#', $tzid, $matches ) ) {
 
501
          $tz_locn = $matches[1];
 
502
        }
 
503
      }
 
504
      if ( isset($tz_locn) && preg_match( $tz_regex, $tz_locn ) ) {
 
505
        dbg_error_log( "PUT", " Setting timezone to %s", $tz_locn );
 
506
        $sql = ( $tz_locn == '' ? '' : "SET TIMEZONE TO ".qpg($tz_locn).";" );
 
507
      }
 
508
    }
 
509
    $qry = new PgQuery("SELECT tz_locn FROM time_zone WHERE tz_id = ?", $tzid );
 
510
    if ( $qry->Exec() && $qry->rows == 0 ) {
 
511
      $qry = new PgQuery("INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(?,?,?)", $tzid, $tz_locn, $tz->Render() );
 
512
      $qry->Exec();
 
513
    }
 
514
    if ( !isset($tz_locn) || $tz_locn == "" ) {
 
515
      $tz_locn = $tzid;
 
516
    }
 
517
  }
 
518
 
447
519
  if ( $put_action_type != 'INSERT' ) {
448
520
    $sql .= "DELETE FROM calendar_item WHERE user_no=$request->user_no AND dav_name=".qpg($request->path).";";
449
521
  }
454
526
  COMMIT;
455
527
EOSQL;
456
528
 
457
 
  $qry = new PgQuery( $sql, $request->user_no, $request->path, $etag, $ic->Get('UID'), $dtstamp,
458
 
                            $ic->Get('DTSTART'), $ic->Get('SUMMARY'), $ic->Get('LOCATION'),
459
 
                            $class, $ic->Get('TRANSP'), $ic->Get('DESCRIPTION'), $ic->Get('RRULE'), $ic->Get('TZ_ID'),
460
 
                            $last_modified, $ic->Get('URL'), $ic->Get('PRIORITY'), $ic->Get('CREATED'),
461
 
                            $ic->Get('DUE'), $ic->Get('PERCENT-COMPLETE'), $ic->Get('STATUS'), $request->collection_id
 
529
  $qry = new PgQuery( $sql, $request->user_no, $request->path, $etag, $first->GetPValue('UID'), $dtstamp,
 
530
                            $first->GetPValue('DTSTART'), $first->GetPValue('SUMMARY'), $first->GetPValue('LOCATION'),
 
531
                            $class, $first->GetPValue('TRANSP'), $first->GetPValue('DESCRIPTION'), $first->GetPValue('RRULE'), $tzid,
 
532
                            $last_modified, $first->GetPValue('URL'), $first->GetPValue('PRIORITY'), $first->GetPValue('CREATED'),
 
533
                            $first->GetPValue('DUE'), $first->GetPValue('PERCENT-COMPLETE'), $first->GetPValue('STATUS'), $request->collection_id
462
534
                      );
463
535
  if ( !$qry->Exec("PUT") ) rollback_on_error( $caldav_context, $request->user_no, $request->path);
464
536
  dbg_error_log( "PUT", "User: %d, ETag: %s, Path: %s", $author, $etag, $request->path);