3 * $Id: publish.php,v 1.12.4.1 2005/11/10 21:01:23 umcesrjones Exp $
6 * Creates the iCal output for a single user's calendar so
7 * that remote users can "subscribe" to a WebCalendar calendar.
8 * Both Apple iCal and Mozilla's Calendar support subscribing
11 * Note that unlink the export to iCal, this page does not include
12 * attendee info. This improves the performance considerably, BTW.
15 * Does anyone know when a client (iCal, for example) refreshes its
16 * data, does it delete all old data and reload? Just wondering
17 * if we need to somehow send a delete notification on updates...
20 * URL should be the form of /xxx/publish.php/username.ics
21 * or /xxx/publish.php?user=username
24 * If $PUBLISH_ENABLED is not 'Y' (set in Admin System Settings),
26 * If $USER_PUBLISH_ENABLED is not 'Y' (set in each user's
27 * Preferences), do not allow.
29 include "includes/config.php";
30 include "includes/php-dbi.php";
31 include "includes/functions.php";
32 include "includes/$user_inc";
33 include "includes/connect.php";
35 // Calculate username.
36 if ( empty ( $user ) ) {
37 $arr = explode ( "/", $PHP_SELF );
38 $user = $arr[count($arr)-1];
39 # remove any trailing ".ics" in user name
40 $user = preg_replace ( "/\.[iI][cC][sS]$/", '', $user );
43 if ( $user == 'public' )
46 load_global_settings ();
48 include "includes/translate.php";
50 if ( empty ( $PUBLISH_ENABLED ) || $PUBLISH_ENABLED != 'Y' ) {
51 header ( "Content-Type: text/plain" );
52 etranslate("You are not authorized");
56 // Make sure they specified a username
57 if ( empty ( $user ) ) {
58 echo "<?xml version=\"1.0\" encoding=\"utf8\"?>\n<!DOCTYPE html
59 PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
60 \"DTD/xhtml1-transitional.dtd\">
61 <html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n
62 <head>\n<title>" . translate("Error") . "</title>\n</head>\n" .
63 "<body>\n<h2>" . translate("Error") . "</h2>\n" .
64 "No user specified.\n</body>\n</html>";
67 // Load user preferences (to get the USER_PUBLISH_ENABLED and
68 // DISPLAY_UNAPPROVED setting for this user).
70 load_user_preferences ();
72 if ( empty ( $USER_PUBLISH_ENABLED ) || $USER_PUBLISH_ENABLED != 'Y' ) {
73 header ( "Content-Type: text/plain" );
74 etranslate("You are not authorized");
78 // Load user name, etc.
79 user_load_variables ( $user, "publish_" );
81 function get_events_for_publish ()
84 global $DISPLAY_UNAPPROVED;
86 // We exporting repeating events only with the pilot-datebook CSV format
87 $sql = "SELECT webcal_entry.cal_id, webcal_entry.cal_name " .
88 ", webcal_entry.cal_priority, webcal_entry.cal_date " .
89 ", webcal_entry.cal_time " .
90 ", webcal_entry_user.cal_status, webcal_entry.cal_create_by " .
91 ", webcal_entry.cal_access, webcal_entry.cal_duration " .
92 ", webcal_entry.cal_description " .
93 ", webcal_entry_user.cal_category " .
94 "FROM webcal_entry, webcal_entry_user ";
96 $sql .= "WHERE webcal_entry.cal_id = webcal_entry_user.cal_id AND " .
97 "webcal_entry_user.cal_login = '" . $user . "'";
99 // Include unapproved events if the user has asked to do so in
100 // their preferences.
101 if ( $DISPLAY_UNAPPROVED == "N" || $user == "__public__" )
102 $sql .= " AND webcal_entry_user.cal_status = 'A'";
104 $sql .= " AND webcal_entry_user.cal_status IN ('W','A')";
106 $sql .= " ORDER BY webcal_entry.cal_date";
108 $res = dbi_query ( $sql );
113 function export_quoted_printable_encode($car) {
116 if ((ord($car) >= 33 && ord($car) <= 60) ||
117 (ord($car) >= 62 && ord($car) <= 126) ||
118 ord($car) == 9 || ord($car) == 32) {
121 $res = sprintf("=%02X", ord($car));
127 function export_fold_lines($string, $encoding="none", $limit=76) {
128 $len = strlen($string);
133 $lwsp = 0; // position of the last linear whitespace (where to fold)
134 $res_ind = 0; // row index
135 $start_encode = 0; // we start encoding only after the ":" caracter is encountered
137 if (strcmp($encoding,"quotedprintable") == 0)
138 $fold--; // must take into account the soft line break
140 for ($i = 0; $i < $len; $i++) {
144 if (strcmp($encoding,"quotedprintable") == 0)
145 $enc = export_quoted_printable_encode($string[$i]);
146 else if (strcmp($encoding,"utf8") == 0)
147 $enc = utf8_encode($string[$i]);
150 if ($string[$i] == ":")
153 if ((strlen($row) + strlen($enc)) > $fold) {
157 $lwsp = $fold - 1; // the folding will occur in the middle of a word
159 if ($row[$lwsp] == " " || $row[$lwsp] == "\t")
162 $res[$res_ind] = substr($row, 0, $lwsp+1+$delta);
164 if (strcmp($encoding,"quotedprintable") == 0)
165 $res[$res_ind] .= "="; // soft line break;
167 $row = substr($row, $lwsp+1);
171 if ($delta == -1 && strcmp($encoding,"utf8") == 0)
174 // reduce row length of 1 to take into account the whitespace
175 // at the beginning of lines
179 $res_ind++; // next line
186 if ($string[$i] == " " || $string[$i] == "\t" ||
187 $string[$i] == ";" || $string[$i] == ",")
188 $lwsp = strlen($row) - 1;
190 if ($string[$i] == ":" && (strcmp($encoding,"quotedprintable") == 0))
191 $lwsp = strlen($row) - 1; // we cut at ':' only for quoted printable
194 $res[$res_ind] = $row; // Add last row (or first if no folding is necessary)
199 function export_time($date, $duration, $time, $texport) {
200 $allday = ( $time == -1 || $duration == 24*60 );
201 $year = (int) substr($date,0,-4);
202 $month = (int) substr($date,-4,2);
203 $day = (int) substr($date,-2,2);
205 //No time, or an "All day" event"
211 $start_tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
212 $start_date = date("Ymd", $start_tmstamp);
213 echo "DTSTART;VALUE=DATE:$start_date\r\n";
215 // normal/timed event
216 $hour = (int) substr($time,0,-4);
217 $min = (int) substr($time,-4,2);
218 $sec = (int) substr($time,-2,2);
219 $duration = $duration * 60;
221 $start_tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
223 $utc_start = export_get_utc_date($date, $time);
224 echo "DTSTART:$utc_start\r\n";
227 $utc_dtstamp = export_get_utc_date(date("Ymd", mktime()),
228 date("His", mktime()));
229 echo "DTSTAMP:$utc_dtstamp\r\n";
231 // Only include and end time on all-day events and timed events
232 // (and not for untimed events)
234 $end_tmstamp = $start_tmstamp + $duration;
235 $utc_end = export_get_utc_date(date("Ymd", $end_tmstamp),
236 date("His", $end_tmstamp));
237 echo "DTEND:$utc_end\r\n";
241 function export_recurrence_ical($id, $date) {
242 global $days_per_month, $ldays_per_month;
243 $str_day = array( 'SU','MO','TU','WE','TH','FR','SA' );
245 $sql = "SELECT cal_date FROM webcal_entry_repeats_not WHERE cal_id = '$id'";
247 $res = dbi_query($sql);
252 while ($row = dbi_fetch_row($res)) {
253 $exdate[$i] = $row[0];
256 dbi_free_result($res);
259 $sql = "SELECT webcal_entry_repeats.cal_type, " .
260 "webcal_entry_repeats.cal_end, webcal_entry_repeats.cal_frequency, " .
261 "webcal_entry_repeats.cal_days, webcal_entry.cal_time " .
262 "FROM webcal_entry, webcal_entry_repeats " .
263 "WHERE webcal_entry_repeats.cal_id = '$id' " .
264 "AND webcal_entry.cal_id = '$id'";
266 $res = dbi_query($sql);
269 if ( $row = dbi_fetch_row($res) ) {
279 /* recurrence frequency */
287 case 'monthlyByDayR':
289 case 'monthlyByDate' :
297 echo ";INTERVAL=$freq";
299 if ($type == "weekly") {
300 if ($day != "nnnnnnn") {
302 for ($i = 0; $i < strlen($day); $i++) {
303 if ($day[$i] == 'y') {
304 $byday .= $str_day[$i] .",";
307 $byday = substr($byday, 0, strlen($byday)-1); // suppress last ','
310 } elseif ($type == "monthlyByDate") {
311 $day = (int) substr($date,-2,2);
312 echo ";BYMONTHDAY=$day";
313 } elseif ($type == "monthlyByDay") {
314 $year = (int) substr($date,0,-4);
315 $month = (int) substr($date,-4,2);
316 $day = (int) substr($date,-2,2);
317 $stamp = mktime(0, 0, 0, $month, $day, $year);
318 $dow = date ( "w", $stamp );
319 $dow1 = date ( "w", mktime ( 3, 0, 0, $month, 1, $year ) );
320 $partWeek = ( 7 - $dow1 ) % 7;
321 $whichWeek = ceil ( ( $day - $partWeek ) / 7 );
322 if ( $partWeek && $dow >= $dow1 )
324 printf ( ";BYDAY=%d%s", $whichWeek, $str_day[$dow] );
325 } elseif ($type == "monthlyByDayR") {
326 $year = (int) substr($date,0,-4);
327 $month = (int) substr($date,-4,2);
328 $day = (int) substr($date,-2,2);
329 $stamp = mktime(0, 0, 0, $month, $day, $year);
330 $dow = date ( "w", $stamp );
331 // get number of days in this month
332 $daysthismonth = ( $year % 4 == 0 ) ? $ldays_per_month[$month] :
333 $days_per_month[$month];
334 // how many weekdays like this one remain in the month?
335 // 0=last one, 1=one more after this one, etc.
336 $whichWeek = floor ( ( $daysthismonth - $day ) / 7 );
337 printf ( ";BYDAY=%d%s", -1 - $whichWeek, $str_day[$dow] );
342 $utc = export_get_utc_date($end, $time);
348 if (count($exdate) > 0) {
351 while ($i < count($exdate)) {
352 $date = export_get_utc_date($exdate[$i],$time);
357 $string = substr($string, 0, strlen($string)-1); // suppress last ','
358 $string = export_fold_lines($string);
359 while (list($key,$value) = each($string))
366 function export_alarm_ical($id, $description) {
367 $sql = "SELECT cal_data FROM webcal_site_extras " .
368 "WHERE cal_id = $id AND cal_type = 7 AND cal_remind = 1";
369 $res = dbi_query ( $sql );
370 $row = dbi_fetch_row ( $res );
371 dbi_free_result ( $res );
374 echo "BEGIN:VALARM\r\n";
375 echo "TRIGGER:-PT".$row[0]."M\r\n";
376 echo "ACTION:DISPLAY\r\n";
378 $array = export_fold_lines($description,"utf8");
379 while (list($key,$value) = each($array)) {
383 echo "END:VALARM\r\n";
387 function export_get_utc_date($date, $time=0) {
388 $year = (int) substr($date,0,-4);
389 $month = (int) substr($date,-4,2);
390 $day = (int) substr($date,-2,2);
397 $hour = (int) substr($time,0,-4);
398 $min = (int) substr($time,-4,2);
399 $sec = (int) substr($time,-2,2);
402 $tmstamp = mktime($hour, $min, $sec, $month, $day, $year);
404 $utc_date = gmdate("Ymd", $tmstamp);
405 $utc_hour = gmdate("His", $tmstamp);
407 $utc = sprintf ("%sT%sZ", $utc_date, $utc_hour);
412 function generate_uid($id) {
413 global $user, $server_url;
416 if ( empty ( $uid ) )
417 $uid = "UNCONFIGURED-WEBCALENDAR";
418 $uid = str_replace ( "http://", "", $uid );
419 $uid .= sprintf ( "-%s-%010d", $user, $id );
420 $uid = preg_replace ( "/[\s\/\.-]+/", "-", $uid );
421 $uid = strtoupper ( $uid );
425 function export_ical () {
426 global $publish_fullname, $user, $PROGRAM_NAME;
427 $res = get_events_for_publish ();
429 $entry_array = array();
432 while ( $entry = dbi_fetch_row($res) ) {
433 $entry_array[$count++] = $entry;
437 echo "BEGIN:VCALENDAR\r\n";
438 $title = "X-WR-CALNAME;VALUE=TEXT:" .
439 ( empty ( $publish_fullname ) ? $user : translate($publish_fullname) );
440 $title = str_replace ( ",", "\\,", $title );
442 if ( preg_match ( "/WebCalendar v(\S+)/", $PROGRAM_NAME, $match ) ) {
443 echo "PRODID:-//WebCalendar-$match[1]\r\n";
445 echo "PRODID:-//WebCalendar-UnknownVersion\r\n";
447 echo "VERSION:2.0\r\n";
448 echo "METHOD:PUBLISH\r\n";
451 while (list($key,$row) = each($entry_array)) {
453 $export_uid = generate_uid($uid);
459 $create_by = $row[6];
462 $description = $row[9];
465 echo "BEGIN:VEVENT\r\n";
467 /* UID of the event (folded to 76 char) */
468 $array = export_fold_lines("UID:$export_uid");
469 while (list($key,$value) = each($array))
472 $name = preg_replace("/\r/", "", $name);
473 $name = preg_replace("/\n/", "\\n", $name);
474 $name = preg_replace("/\\\\/", "\\\\\\", $name); // ??
475 $description = preg_replace("/\r/", "", $description);
476 $description = preg_replace("/\n/", "\\n", $description);
477 $description = preg_replace("/\\\\/", "\\\\\\", $description); // ??
479 /* SUMMARY of the event (folded to 76 char) */
480 $name = "SUMMARY:" . $name;
481 $array = export_fold_lines($name,"utf8");
483 while (list($key,$value) = each($array))
486 /* DESCRIPTION if any (folded to 76 char) */
487 if ($description != "") {
488 $description = "DESCRIPTION:" . $description;
489 $array = export_fold_lines($description,"utf8");
490 while (list($key,$value) = each($array))
494 /* CLASS either "PRIVATE" or "PUBLIC" (the default) */
495 if ($access == "R") {
496 echo "CLASS:PRIVATE\r\n";
498 echo "CLASS:PUBLIC\r\n";
501 /* Time - all times are utc */
502 export_time($date, $duration, $time, "ical");
505 export_recurrence_ical($uid, $date);
508 export_alarm_ical($uid,$description);
511 echo "END:VEVENT\r\n";
515 echo "END:VCALENDAR\r\n";
518 //header ( "Content-Type: text/plain" );
519 header ( "Content-Type: text/calendar" );