$data
$data
";
// reflect the section where we are in the file:
// VEVENT, VTODO, VJORNAL, VFREEBUSY, VTIMEZONE
$state = "NONE";
$substate = "none"; // reflect the sub section
$subsubstate = ""; // reflect the sub-sub section
$error = false;
$line = 0;
$event = '';
$lines = explode ( "\n", $data );
for ( $n = 0; $n < count ( $lines ) && ! $error; $n++ ) {
$line++;
$buff = $lines[$n];
// parser debugging code...
//echo "line = $line
";
//echo "state = $state
";
//echo "substate = $substate
";
//echo "subsubstate = $subsubstate
";
//echo "buff = " . htmlspecialchars ( $buff ) . "
\n";
if ($state == "VEVENT") {
if ( ! empty ( $subsubstate ) ) {
if (preg_match("/^END:(.+)$/i", $buff, $match)) {
if ( $match[1] == $subsubstate ) {
$subsubstate = '';
}
} else if ( $subsubstate == "VALARM" &&
preg_match ( "/TRIGGER:(.+)$/i", $buff, $match ) ) {
// Example: TRIGGER;VALUE=DATE-TIME:19970317T133000Z
//echo "Set reminder to $match[1]
";
// reminder time is $match[1]
}
}
else if (preg_match("/^BEGIN:(.+)$/i", $buff, $match)) {
$subsubstate = $match[1];
}
// we suppose ":" is on the same line as property name, this can perhaps cause problems
else if (preg_match("/^SUMMARY.*:(.+)$/i", $buff, $match)) {
$substate = "summary";
$event[$substate] = $match[1];
} elseif (preg_match("/^DESCRIPTION:(.+)$/i", $buff, $match)) {
$substate = "description";
$event[$substate] = $match[1];
} elseif (preg_match("/^DESCRIPTION.*:(.+)$/i", $buff, $match)) {
$substate = "description";
$event[$substate] = $match[1];
} elseif (preg_match("/^CLASS.*:(.*)$/i", $buff, $match)) {
$substate = "class";
$event[$substate] = $match[1];
} elseif (preg_match("/^PRIORITY.*:(.*)$/i", $buff, $match)) {
$substate = "priority";
$event[$substate] = $match[1];
} elseif (preg_match("/^DTSTART.*:\s*(\d+T\d+Z?)\s*$/i", $buff, $match)) {
$substate = "dtstart";
$event[$substate] = $match[1];
} elseif (preg_match("/^DTSTART.*:\s*(\d+)\s*$/i", $buff, $match)) {
$substate = "dtstart";
$event[$substate] = $match[1];
} elseif (preg_match("/^DTEND.*:\s*(.*)\s*$/i", $buff, $match)) {
$substate = "dtend";
$event[$substate] = $match[1];
} elseif (preg_match("/^DURATION.*:(.+)\s*$/i", $buff, $match)) {
$substate = "duration";
$durH = $durM = 0;
if ( preg_match ( "/PT.*([0-9]+)H/", $match[1], $submatch ) )
$durH = $submatch[1];
if ( preg_match ( "/PT.*([0-9]+)M/", $match[1], $submatch ) )
$durM = $submatch[1];
$event[$substate] = $durH * 60 + $durM;
} elseif (preg_match("/^RRULE.*:(.+)$/i", $buff, $match)) {
$substate = "rrule";
$event[$substate] = $match[1];
} elseif (preg_match("/^EXDATE.*:(.+)$/i", $buff, $match)) {
$substate = "exdate";
$event[$substate] = $match[1];
} elseif (preg_match("/^CATEGORIES.*:(.+)$/i", $buff, $match)) {
$substate = "categories";
$event[$substate] = $match[1];
} elseif (preg_match("/^UID.*:(.+)$/i", $buff, $match)) {
$substate = "uid";
$event[$substate] = $match[1];
} elseif (preg_match("/^END:VEVENT$/i", $buff, $match)) {
$state = "VCALENDAR";
$substate = "none";
$subsubstate = '';
if ($tmp_data = format_ical($event)) $ical_data[] = $tmp_data;
// clear out data for new event
$event = '';
// TODO: QUOTED-PRINTABLE descriptions
// folded lines
// TODO: This is not the best way to handle folded lines.
// We should fix the folding before we parse...
} elseif (preg_match("/^\s(\S.*)$/", $buff, $match)) {
if ($substate != "none") {
$event[$substate] .= $match[1];
} else {
$errormsg .= "iCal parse error on line $line:
$buff\n";
$error = true;
}
// For unsupported properties
} else {
$substate = "none";
}
} elseif ($state == "VCALENDAR") {
if (preg_match("/^BEGIN:VEVENT/i", $buff)) {
$state = "VEVENT";
} elseif (preg_match("/^END:VCALENDAR/i", $buff)) {
$state = "NONE";
} else if (preg_match("/^BEGIN:VTIMEZONE/i", $buff)) {
$state = "VTIMEZONE";
} else if (preg_match("/^BEGIN:VALARM/i", $buff)) {
$state = "VALARM";
}
} elseif ($state == "VTIMEZONE") {
// We don't do much with timezone info yet...
if (preg_match("/^END:VTIMEZONE$/i", $buff)) {
$state = "VCALENDAR";
}
} elseif ($state == "NONE") {
if (preg_match("/^BEGIN:VCALENDAR$/i", $buff))
$state = "VCALENDAR";
}
} // End while
}
return $ical_data;
}
// Convert ical format (yyyymmddThhmmssZ) to epoch time
function icaldate_to_timestamp ($vdate, $plus_d = '0', $plus_m = '0',
$plus_y = '0') {
global $TZoffset;
$y = substr($vdate, 0, 4) + $plus_y;
$m = substr($vdate, 4, 2) + $plus_m;
$d = substr($vdate, 6, 2) + $plus_d;
$H = substr($vdate, 9, 2);
$M = substr($vdate, 11, 2);
$S = substr($vdate, 13, 2);
$Z = substr($vdate, 15, 1);
if ($Z == 'Z') {
$TS = gmmktime($H,$M,$S,$m,$d,$y);
} else {
// Problem here if server in different timezone
$TS = mktime($H,$M,$S,$m,$d,$y);
}
return $TS;
}
// Put all ical data into import hash structure
function format_ical($event) {
// Start and end time
$fevent['StartTime'] = icaldate_to_timestamp($event['dtstart']);
if ($fevent['StartTime'] == '-1') return false;
if ( isset ( $event['dtend'] ) ) {
$fevent['EndTime'] = icaldate_to_timestamp($event['dtend']);
} else {
if ( isset ( $event['duration'] ) ) {
$fevent['EndTime'] = $fevent['StartTime'] + $event['duration'] * 60;
} else {
$fevent['EndTime'] = $fevent['StartTime'];
}
}
// Calculate duration in minutes
if ( isset ( $event['duration'] ) ) {
$fevent['Duration'] = $event['duration'];
} else if ( empty ( $fevent['Duration'] ) ) {
$fevent['Duration'] = ($fevent['EndTime'] - $fevent['StartTime']) / 60;
}
if ( $fevent['Duration'] == '1440' ) {
// All day event... nothing to do here :-)
} else if ( preg_match ( "/\d\d\d\d\d\d\d\d$/",
$event['dtstart'], $pmatch ) ) {
// Untimed event
$fevent['Duration'] = 0;
$fevent['Untimed'] = 1;
}
if ( preg_match ( "/\d\d\d\d\d\d\d\d$/", $event['dtstart'],
$pmatch ) && preg_match ( "/\d\d\d\d\d\d\d\d$/", $event['dtend'],
$pmatch2 ) && $event['dtstart'] != $event['dtend'] ) {
$startTime = icaldate_to_timestamp($event['dtstart']);
$endTime = icaldate_to_timestamp($event['dtend']);
// Not sure... should this be untimed or allday?
if ( $endTime - $startTime == ( 3600 * 24 ) ) {
// They used a DTEND set to the next day to say this is an all day
// event. We will call this an untimed event.
$fevent['Duration'] = '0';
$fevent['Untimed'] = 1;
} else {
// Event spans multiple days. The EndTime actually represents
// the first day the event does _not_ take place. So,
// we need to back up one day since WebCalendar end date is the
// last day the event takes place.
$fevent['Repeat']['Interval'] = '1'; // 1 = daily
$fevent['Repeat']['Frequency'] = '1'; // 1 = every day
$fevent['Duration'] = '0';
$fevent['Untimed'] = 1;
$fevent['Repeat']['EndTime'] = $endTime - ( 24 * 3600 );
}
}
$fevent['Summary'] = $event['summary'];
if ( ! empty ( $event['description'] ) ) {
$fevent['Description'] = $event['description'];
} else {
$fevent['Description'] = $event['summary'];
}
if ( ! empty ( $event['class'] ) ) {
$fevent['Private'] = preg_match("/private|confidential/i",
$event['class']) ? '1' : '0';
}
$fevent['UID'] = $event['uid'];
// Repeats
//
// Handle RRULE
if ( ! empty ( $event['rrule'] ) ) {
// first remove and EndTime that may have been calculated above
unset ( $fevent['Repeat']['EndTime'] );
//split into pieces
//echo "RRULE line: $event[rrule]
\n";
$RR = explode ( ";", $event['rrule'] );
// create an associative array of key-value paris in $RR2[]
for ( $i = 0; $i < count ( $RR ); $i++ ) {
$ar = explode ( "=", $RR[$i] );
$RR2[$ar[0]] = $ar[1];
}
for ( $i = 0; $i < count ( $RR ); $i++ ) {
//echo "RR $i = $RR[$i]
";
if ( preg_match ( "/^FREQ=(.+)$/i", $RR[$i], $match ) ) {
if ( preg_match ( "/YEARLY/i", $match[1], $submatch ) ) {
$fevent['Repeat']['Interval'] = 5;
} else if ( preg_match ( "/MONTHLY/i", $match[1], $submatch ) ) {
$fevent['Repeat']['Interval'] = 2;
} else if ( preg_match ( "/WEEKLY/i", $match[1], $submatch ) ) {
$fevent['Repeat']['Interval'] = 2;
} else if ( preg_match ( "/DAILY/i", $match[1], $submatch ) ) {
$fevent['Repeat']['Interval'] = 1;
} else {
// not supported :-(
echo "Unsupported iCal FREQ value \"$match[1]\"
\n";
}
} else if ( preg_match ( "/^INTERVAL=(.+)$/i", $RR[$i], $match ) ) {
$fevent['Repeat']['Frequency'] = $match[1];
} else if ( preg_match ( "/^UNTIL=(.+)$/i", $RR[$i], $match ) ) {
// specifies an end date
$fevent['Repeat']['EndTime'] = icaldate_to_timestamp ( $match[1] );
} else if ( preg_match ( "/^COUNT=(.+)$/i", $RR[$i], $match ) ) {
// NOT YET SUPPORTED -- TODO
echo "Unsupported iCal COUNT value \"$RR[$i]\"
\n";
} else if ( preg_match ( "/^BYSECOND=(.+)$/i", $RR[$i], $match ) ) {
// NOT YET SUPPORTED -- TODO
echo "Unsupported iCal BYSECOND value \"$RR[$i]\"
\n";
} else if ( preg_match ( "/^BYMINUTE=(.+)$/i", $RR[$i], $match ) ) {
// NOT YET SUPPORTED -- TODO
echo "Unsupported iCal BYMINUTE value \"$RR[$i]\"
\n";
} else if ( preg_match ( "/^BYHOUR=(.+)$/i", $RR[$i], $match ) ) {
// NOT YET SUPPORTED -- TODO
echo "Unsupported iCal BYHOUR value \"$RR[$i]\"
\n";
} else if ( preg_match ( "/^BYMONTH=(.+)$/i", $RR[$i], $match ) ) {
// this event repeats during the specified months
$months = explode ( ",", $match[1] );
if ( count ( $months ) == 1 ) {
// Change this to a monthly event so we can support repeat by
// day of month (if needed)
// Frequency = 3 (by day), 4 (by date), 6 (by day reverse)
if ( ! empty ( $RR2['BYDAY'] ) ) {
if ( preg_match ( "/^-/", $RR2['BYDAY'], $junk ) )
$fevent['Repeat']['Interval'] = 6; // monthly by day reverse
else
$fevent['Repeat']['Interval'] = 3; // monthly by day
$fevent['Repeat']['Frequency'] = 12; // once every 12 months
} else {
// could convert this to monthly by date, but we will just
// leave it as yearly.
//$fevent['Repeat']['Interval'] = 4; // monthly by date
}
} else {
// WebCalendar does not support this
echo "Unsupported iCal BYMONTH value \"$match[1]\"
\n";
}
} else if ( preg_match ( "/^BYDAY=(.+)$/i", $RR[$i], $match ) ) {
$fevent['Repeat']['RepeatDays'] = rrule_repeat_days( $match[1] );
} else if ( preg_match ( "/^BYMONTHDAY=(.+)$/i", $RR[$i], $match ) ) {
$fevent['Repeat']['Frequency'] = 4; //monthlyByDate
//echo "Partially Supported iCal BYSETPOS value \"$RR[$i]\"
\n";
} else if ( preg_match ( "/^BYSETPOS=(.+)$/i", $RR[$i], $match ) ) {
// NOT YET SUPPORTED -- TODO
echo "Unsupported iCal BYSETPOS value \"$RR[$i]\"
\n";
}
}
// Repeating exceptions?
if ( ! empty ( $event['exdate'] ) && $event['exdate']) {
$fevent['Repeat']['Exceptions'] = array();
$EX = explode(",", $event['exdate']);
foreach ( $EX as $exdate ){
$fevent['Repeat']['Exceptions'][] = icaldate_to_timestamp($exdate);
}
}
} // end if rrule
return $fevent;
}
// Figure out days of week for weekly repeats
function rrule_repeat_days($RA) {
$RA = explode(",", $RA );
$T = count( $RA ) ;
$sun = $mon = $tue = $wed = $thu = $fri = $sat = 'n';
for ($i = 0; $i < $T; $i++) {
if ($RA[$i] == 'SU') {
$sun = 'y';
} elseif ($RA[$i] == 'MO') {
$mon = 'y';
} elseif ($RA[$i] == 'TU') {
$tue = 'y';
} elseif ($RA[$i] == 'WE') {
$wed = 'y';
} elseif ($RA[$i] == 'TH') {
$thu = 'y';
} elseif ($RA[$i] == 'FR') {
$fri = 'y';
} elseif ($RA[$i] == 'SA') {
$sat = 'y';
}
}
return $sun.$mon.$tue.$wed.$thu.$fri.$sat;
}
// Calculate repeating ending time
function rrule_endtime($int,$freq,$start,$end) {
// if # then we have to add the difference to the start time
if (preg_match("/^#(.+)$/i", $end, $M)) {
$T = $M[1] * $freq;
$plus_d = $plus_m = $plus_y = '0';
if ($int == '1') {
$plus_d = $T;
} elseif ($int == '2') {
$plus_d = $T * 7;
} elseif ($int == '3') {
$plus_m = $T;
} elseif ($int == '4') {
$plus_m = $T;
} elseif ($int == '5') {
$plus_y = $T;
} elseif ($int == '6') {
$plus_m = $T;
}
$endtime = icaldate_to_timestamp($start,$plus_d,$plus_m,$plus_y);
// if we have the enddate
} else {
$endtime = icaldate_to_timestamp($end);
}
return $endtime;
}
?>