4 * Creates the iCal output for a single user's calendar so
5 * that remote users can "subscribe" to a WebCalendar calendar.
6 * Both Apple iCal and Mozilla's Calendar support subscribing
9 * Note that unlink the export to iCal, this page does not include
10 * attendee info. This improves the performance considerably, BTW.
13 * Does anyone know when a client (iCal, for example) refreshes its
14 * data, does it delete all old data and reload? Just wondering
15 * if we need to somehow send a delete notification on updates...
18 * URL should be the form of /xxx/publish.php/username.ics
19 * or /xxx/publish.php?user=username
22 * If $PUBLISH_ENABLED is not 'Y' (set in Admin System Settings),
24 * If $USER_PUBLISH_ENABLED is not 'Y' (set in each user's
25 * Preferences), do not allow.
27 include "includes/config.php";
28 include "includes/php-dbi.php";
29 include "includes/functions.php";
30 include "includes/$user_inc";
31 include "includes/connect.php";
33 // Calculate username.
34 if ( empty ( $user ) ) {
35 $arr = explode ( "/", $PHP_SELF );
36 $user = $arr[count($arr)-1];
37 # remove any trailing ".ics" in user name
38 $user = preg_replace ( "/\.[iI][cC][sS]$/", '', $user );
41 if ( $user == 'public' )
44 load_global_settings ();
46 include "includes/translate.php";
48 if ( empty ( $PUBLISH_ENABLED ) || $PUBLISH_ENABLED != 'Y' ) {
49 header ( "Content-Type: text/plain" );
50 etranslate("You are not authorized");
54 // Make sure they specified a username
55 if ( empty ( $user ) ) {
56 echo "<?xml version=\"1.0\" encoding=\"utf8\"?>\n<!DOCTYPE html
57 PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
58 \"DTD/xhtml1-transitional.dtd\">
59 <html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n
60 <head>\n<title>" . translate("Error") . "</title>\n</head>\n" .
61 "<body>\n<h2>" . translate("Error") . "</h2>\n" .
62 "No user specified.\n</body>\n</html>";
65 // Load user preferences (to get the USER_PUBLISH_ENABLED and
66 // DISPLAY_UNAPPROVED setting for this user).
68 load_user_preferences ();
70 if ( empty ( $USER_PUBLISH_ENABLED ) || $USER_PUBLISH_ENABLED != 'Y' ) {
71 header ( "Content-Type: text/plain" );
72 etranslate("You are not authorized");
76 // Load user name, etc.
77 user_load_variables ( $user, "publish_" );
79 function get_events_for_publish ()
82 global $DISPLAY_UNAPPROVED;
84 // We exporting repeating events only with the pilot-datebook CSV format
85 $sql = "SELECT webcal_entry.cal_id, webcal_entry.cal_name " .
86 ", webcal_entry.cal_priority, webcal_entry.cal_date " .
87 ", webcal_entry.cal_time " .
88 ", webcal_entry_user.cal_status, webcal_entry.cal_create_by " .
89 ", webcal_entry.cal_access, webcal_entry.cal_duration " .
90 ", webcal_entry.cal_description " .
91 ", webcal_entry_user.cal_category " .
92 "FROM webcal_entry, webcal_entry_user ";
94 $sql .= "WHERE webcal_entry.cal_id = webcal_entry_user.cal_id AND " .
95 "webcal_entry_user.cal_login = '" . $user . "'";
97 // Include unapproved events if the user has asked to do so in
99 if ( $DISPLAY_UNAPPROVED == "N" || $user == "__public__" )
100 $sql .= " AND webcal_entry_user.cal_status = 'A'";
102 $sql .= " AND webcal_entry_user.cal_status IN ('W','A')";
104 $sql .= " ORDER BY webcal_entry.cal_date";
106 $res = dbi_query ( $sql );
111 function export_quoted_printable_encode($car) {
114 if ((ord($car) >= 33 && ord($car) <= 60) ||
115 (ord($car) >= 62 && ord($car) <= 126) ||
116 ord($car) == 9 || ord($car) == 32) {
119 $res = sprintf("=%02X", ord($car));
125 function export_fold_lines($string, $encoding="none", $limit=76) {
126 $len = strlen($string);
131 $lwsp = 0; // position of the last linear whitespace (where to fold)
132 $res_ind = 0; // row index
133 $start_encode = 0; // we start encoding only after the ":" caracter is encountered
135 if (strcmp($encoding,"quotedprintable") == 0)
136 $fold--; // must take into account the soft line break
138 for ($i = 0; $i < $len; $i++) {
142 if (strcmp($encoding,"quotedprintable") == 0)
143 $enc = export_quoted_printable_encode($string[$i]);
144 else if (strcmp($encoding,"utf8") == 0)
145 $enc = utf8_encode($string[$i]);
148 if ($string[$i] == ":")
151 if ((strlen($row) + strlen($enc)) > $fold) {
155 $lwsp = $fold - 1; // the folding will occur in the middle of a word
157 if ($row[$lwsp] == " " || $row[$lwsp] == "\t")
160 $res[$res_ind] = substr($row, 0, $lwsp+1+$delta);
162 if (strcmp($encoding,"quotedprintable") == 0)
163 $res[$res_ind] .= "="; // soft line break;
165 $row = substr($row, $lwsp+1);
169 if ($delta == -1 && strcmp($encoding,"utf8") == 0)
172 // reduce row length of 1 to take into account the whitespace
173 // at the beginning of lines
177 $res_ind++; // next line
184 if ($string[$i] == " " || $string[$i] == "\t" ||
185 $string[$i] == ";" || $string[$i] == ",")
186 $lwsp = strlen($row) - 1;
188 if ($string[$i] == ":" && (strcmp($encoding,"quotedprintable") == 0))
189 $lwsp = strlen($row) - 1; // we cut at ':' only for quoted printable
192 $res[$res_ind] = $row; // Add last row (or first if no folding is necessary)
197 function export_time($date, $duration, $time, $texport) {
198 $allday = ( $time == -1 || $duration == 24*60 );
199 $year = (int) substr($date,0,-4);
200 $month = (int) substr($date,-4,2);
201 $day = (int) substr($date,-2,2);
203 //No time, or an "All day" event"
209 $start_tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
210 $start_date = date("Ymd", $start_tmstamp);
211 echo "DTSTART;VALUE=DATE:$start_date\r\n";
213 // normal/timed event
214 $hour = (int) substr($time,0,-4);
215 $min = (int) substr($time,-4,2);
216 $sec = (int) substr($time,-2,2);
217 $duration = $duration * 60;
219 $start_tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
221 $utc_start = export_get_utc_date($date, $time);
222 echo "DTSTART:$utc_start\r\n";
225 $utc_dtstamp = export_get_utc_date(date("Ymd", mktime()),
226 date("His", mktime()));
227 echo "DTSTAMP:$utc_dtstamp\r\n";
229 // Only include and end time on all-day events and timed events
230 // (and not for untimed events)
232 $end_tmstamp = $start_tmstamp + $duration;
233 $utc_end = export_get_utc_date(date("Ymd", $end_tmstamp),
234 date("His", $end_tmstamp));
235 echo "DTEND:$utc_end\r\n";
239 function export_recurrence_ical($id, $date) {
240 global $days_per_month, $ldays_per_month;
241 $str_day = array( 'SU','MO','TU','WE','TH','FR','SA' );
243 $sql = "SELECT cal_date FROM webcal_entry_repeats_not WHERE cal_id = '$id'";
245 $res = dbi_query($sql);
250 while ($row = dbi_fetch_row($res)) {
251 $exdate[$i] = $row[0];
254 dbi_free_result($res);
257 $sql = "SELECT webcal_entry_repeats.cal_type, " .
258 "webcal_entry_repeats.cal_end, webcal_entry_repeats.cal_frequency, " .
259 "webcal_entry_repeats.cal_days, webcal_entry.cal_time " .
260 "FROM webcal_entry, webcal_entry_repeats " .
261 "WHERE webcal_entry_repeats.cal_id = '$id' " .
262 "AND webcal_entry.cal_id = '$id'";
264 $res = dbi_query($sql);
267 if ( $row = dbi_fetch_row($res) ) {
277 /* recurrence frequency */
285 case 'monthlyByDayR':
287 case 'monthlyByDate' :
295 echo ";INTERVAL=$freq";
297 if ($type == "weekly") {
298 if ($day != "nnnnnnn") {
300 for ($i = 0; $i < strlen($day); $i++) {
301 if ($day[$i] == 'y') {
302 $byday .= $str_day[$i] .",";
305 $byday = substr($byday, 0, strlen($byday)-1); // suppress last ','
308 } elseif ($type == "monthlyByDate") {
309 $day = (int) substr($date,-2,2);
310 echo ";BYMONTHDAY=$day";
311 } elseif ($type == "monthlyByDay") {
312 $year = (int) substr($date,0,-4);
313 $month = (int) substr($date,-4,2);
314 $day = (int) substr($date,-2,2);
315 $stamp = mktime(0, 0, 0, $month, $day, $year);
316 $dow = date ( "w", $stamp );
317 $dow1 = date ( "w", mktime ( 3, 0, 0, $month, 1, $year ) );
318 $partWeek = ( 7 - $dow1 ) % 7;
319 $whichWeek = ceil ( ( $day - $partWeek ) / 7 );
320 if ( $partWeek && $dow >= $dow1 )
322 printf ( ";BYDAY=%d%s", $whichWeek, $str_day[$dow] );
323 } elseif ($type == "monthlyByDayR") {
324 $year = (int) substr($date,0,-4);
325 $month = (int) substr($date,-4,2);
326 $day = (int) substr($date,-2,2);
327 $stamp = mktime(0, 0, 0, $month, $day, $year);
328 $dow = date ( "w", $stamp );
329 // get number of days in this month
330 $daysthismonth = ( $year % 4 == 0 ) ? $ldays_per_month[$month] :
331 $days_per_month[$month];
332 // how many weekdays like this one remain in the month?
333 // 0=last one, 1=one more after this one, etc.
334 $whichWeek = floor ( ( $daysthismonth - $day ) / 7 );
335 printf ( ";BYDAY=%d%s", -1 - $whichWeek, $str_day[$dow] );
340 $utc = export_get_utc_date($end, $time);
346 if (count($exdate) > 0) {
349 while ($i < count($exdate)) {
350 $date = export_get_utc_date($exdate[$i],$time);
355 $string = substr($string, 0, strlen($string)-1); // suppress last ','
356 $string = export_fold_lines($string);
357 while (list($key,$value) = each($string))
364 function export_alarm_ical($id, $description) {
365 $sql = "SELECT cal_data FROM webcal_site_extras " .
366 "WHERE cal_id = $id AND cal_type = 7 AND cal_remind = 1";
367 $res = dbi_query ( $sql );
368 $row = dbi_fetch_row ( $res );
369 dbi_free_result ( $res );
372 echo "BEGIN:VALARM\r\n";
373 echo "TRIGGER:-PT".$row[0]."M\r\n";
374 echo "ACTION:DISPLAY\r\n";
376 $array = export_fold_lines($description,"utf8");
377 while (list($key,$value) = each($array)) {
381 echo "END:VALARM\r\n";
385 function export_get_utc_date($date, $time=0) {
386 $year = (int) substr($date,0,-4);
387 $month = (int) substr($date,-4,2);
388 $day = (int) substr($date,-2,2);
395 $hour = (int) substr($time,0,-4);
396 $min = (int) substr($time,-4,2);
397 $sec = (int) substr($time,-2,2);
400 $tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
402 $utc_date = gmdate("Ymd", $tmstamp);
403 $utc_hour = gmdate("His", $tmstamp);
405 $utc = sprintf ("%sT%sZ", $utc_date, $utc_hour);
410 function generate_uid($id) {
411 global $user, $server_url;
414 if ( empty ( $uid ) )
415 $uid = "UNCONFIGURED-WEBCALENDAR";
416 $uid = str_replace ( "http://", "", $uid );
417 $uid .= sprintf ( "-%s-%010d", $user, $id );
418 $uid = preg_replace ( "/[\s\/\.-]+/", "-", $uid );
419 $uid = strtoupper ( $uid );
423 function export_ical () {
424 global $publish_fullname, $user, $PROGRAM_NAME;
425 $res = get_events_for_publish ();
427 $entry_array = array();
430 while ( $entry = dbi_fetch_row($res) ) {
431 $entry_array[$count++] = $entry;
435 echo "BEGIN:VCALENDAR\r\n";
436 $title = "X-WR-CALNAME;VALUE=TEXT:" .
437 ( empty ( $publish_fullname ) ? $user : translate($publish_fullname) );
438 $title = str_replace ( ",", "\\,", $title );
440 if ( preg_match ( "/WebCalendar v(\S+)/", $PROGRAM_NAME, $match ) ) {
441 echo "PRODID:-//WebCalendar-$match[1]\r\n";
443 echo "PRODID:-//WebCalendar-UnknownVersion\r\n";
445 echo "VERSION:2.0\r\n";
446 echo "METHOD:PUBLISH\r\n";
449 while (list($key,$row) = each($entry_array)) {
451 $export_uid = generate_uid($uid);
457 $create_by = $row[6];
460 $description = $row[9];
463 echo "BEGIN:VEVENT\r\n";
465 /* UID of the event (folded to 76 char) */
466 $array = export_fold_lines("UID:$export_uid");
467 while (list($key,$value) = each($array))
470 $name = preg_replace("/\r/", "", $name);
471 $name = preg_replace("/\n/", "\\n", $name);
472 $name = preg_replace("/\\\\/", "\\\\\\", $name); // ??
473 $description = preg_replace("/\r/", "", $description);
474 $description = preg_replace("/\n/", "\\n", $description);
475 $description = preg_replace("/\\\\/", "\\\\\\", $description); // ??
477 /* SUMMARY of the event (folded to 76 char) */
478 $name = "SUMMARY:" . $name;
479 $array = export_fold_lines($name,"utf8");
481 while (list($key,$value) = each($array))
484 /* DESCRIPTION if any (folded to 76 char) */
485 if ($description != "") {
486 $description = "DESCRIPTION:" . $description;
487 $array = export_fold_lines($description,"utf8");
488 while (list($key,$value) = each($array))
492 /* CLASS either "PRIVATE" or "PUBLIC" (the default) */
493 if ($access == "R") {
494 echo "CLASS:PRIVATE\r\n";
496 echo "CLASS:PUBLIC\r\n";
499 /* Time - all times are utc */
500 export_time($date, $duration, $time, "ical");
503 export_recurrence_ical($uid, $date);
506 export_alarm_ical($uid,$description);
509 echo "END:VEVENT\r\n";
513 echo "END:VCALENDAR\r\n";
516 //header ( "Content-Type: text/plain" );
517 header ( "Content-Type: text/calendar" );