<?php
/* $Id: ical.inc,v 1.3.2.3 2006/05/22 13:35:07 killes Exp $ */

/**
 * @file
 * API for event import/export in iCalendar format as outlined in Internet Calendaring and Scheduling Core Object Specification
 * http://www.ietf.org/rfc/rfc2445.txt
 * 
 * This module is IN DEVELOPMENT and not a finished product
 */

/**
 * Turn an array of events into a valid iCalendar file
 *
 * @param $events
 *   An array of associative arrays where
 *      'start'         => Unix timestamp (GMT) of start time (Required, if no allday_start)
 *      'end'           => Unix timestamp (GMT) of end time (Optional)
 *      'allday_start'  => Start date of all-day event in YYYYMMDD format (Required, if no start)
 *      'allday_end'    => End date of all-day event in YYYYMMDD format (Optional)
 *      'summary'       => Title of event (Text)
 *      'description'   => Description of event (Text)
 *      'location'      => Location of event (Text)
 *      'uid'           => ID of the event for use by calendaring program.  Recommend the url of the node
 *      'url'           => URL of event information
 *
 * @param $calname
 *   Name of the calendar.  Will use site name if none is specified.
 *
 * @return
 *   Text of a iCalendar file
 */
function ical_export($events, $calname = NULL) {
  $output .= "BEGIN:VCALENDAR\nVERSION:2.0\n";
  $output .= 'X-WR-CALNAME:'. variable_get('site_name', '') .' | '. $calname ."\n";
  $output .= "PRODID:-//strange bird labs//Drupal iCal API//EN\n";
  foreach ($events as $uid => $event) {
    $output .= "BEGIN:VEVENT\n";
    if ($event['allday_start'] && $event['allday_end']) {
      $output .= "DTSTART;VALUE=DATE:" . $event['allday_start'] . "\n";
      $output .= "DTEND;VALUE=DATE:" . $event['allday_end'] . "\n";
    }
    else if ($event['allday_start'] && empty($event['allday_end'])) {
      $output .= "DTSTART;VALUE=DATE:" . $event['allday_start'] . "\n";
      $output .= "DTEND;VALUE=DATE:" . date('Ymd', strtotime($event['allday_start']) + 86400) . "\n"; //If no allday end date, set to day after allday start
    }
    else if ($event['start'] && $event['end']) {
      $output .= "DTSTART;VALUE=DATE-TIME:" . gmdate("Ymd\THis\Z", $event['start']) . "\n";
      $output .= "DTEND;VALUE=DATE-TIME:" . gmdate("Ymd\THis\Z", $event['end']) . "\n";
    }
    else if ($event['start']) {
      $output .= "DTSTART;VALUE=DATE-TIME:" . gmdate("Ymd\THis\Z", $event['start']) . "\n";
    }
    $output .= "SUMMARY:" . ical_escape_text($event['summary']) . "\n";
    if ($event['description']) $output .= "DESCRIPTION:" . ical_escape_text($event['description']) . "\n";
    if ($event['url']) $output .= "URL;VALUE=URI:" . $event['url'] . "\n";
    if ($event['location']) $output .= "LOCATION:" . $event['location'] . "\n";
    $output .= "UID:" . ($event['uid'] ? $event['uid'] : $uid) . "\n";
    $output .= "END:VEVENT\n";
  }
  $output .= "END:VCALENDAR\n";
  return $output;
}

/**
 * Escape #text elements for safe iCal use
 *
 * @param $text
 *   Text to escape
 *
 * @return
 *   Escaped text
 *
 */
function ical_escape_text($text) {
  $text = strip_tags($text);
  $text = str_replace("\"", "DQUOTE", $text);
  $text = str_replace("\\", "\\\\", $text);
  $text = str_replace(",", "\,", $text);
  $text = str_replace(":", "\":\"", $text);
  $text = str_replace(";", "\;", $text);
  $text = str_replace("\n", "\n ", $text);
  return $text;
}

/**                                                                                                                                                                   
 * Given the location of a valide iCalendar file, will return an array of event information
 *
 * @param $filename
 *   Location (local or remote) of a valid iCalendar file
 *                                                                                                                                                                   
 * @return                                                                                                                                                     
 *   An array of associative arrays where                                                                                                                             
 *      'start'         => Unix timestamp (GMT) of start time
 *      'end'           => Unix timestamp (GMT) of end time
 *      'allday_start'  => Start date of all-day event in YYYYMMDD format
 *      'allday_end'    => End date of all-day event in YYYYMMDD format                                                                              
 *      'summary'       => Title of event                                                                                                                      
 *      'description'   => Description of event                                                                                                                
 *      'location'      => Location of event                                                                                                                   
 *      'uid'           => ID of the event in calendaring program
 *      'url'           => URL of event information                                                                                                                    * 
 */
function ical_import($filename) {
  $items = array();
  $ifile = @fopen($filename, "r");
  if ($ifile == FALSE) exit('Invalid input file');
  $nextline = fgets($ifile, 1024);
  if (trim($nextline) != 'BEGIN:VCALENDAR') exit('Invalid calendar file:' . $filename);
  while (!feof($ifile)) {
    $line = $nextline;
    $nextline = fgets($ifile, 1024);
    $nextline = ereg_replace("[\r\n]", "", $nextline);
    while (substr($nextline, 0, 1) == " ") {
      $line = $line . substr($nextline, 1);
      $nextline = fgets($ifile, 1024);
      $nextline = ereg_replace("[\r\n]", "", $nextline);
    }
    $line = trim($line);
    switch ($line) {
    case 'BEGIN:VEVENT':
      unset($start_unixtime, $start_date, $start_time,
	    $end_unixtime, $end_date, $end_time,
	    $allday_start, $allday_end, $the_duration,
	    $uid,
	    $summary,
	    $description,
	    $url,
	    $location
	    );
      break;
    case 'END:VEVENT':
      if (empty($uid)) {
	$uid = $uid_counter;
	$uid_counter++;
      }
      if (empty($end_unixtime) && isset($the_duration)) {
	$end_unixtime = $start_unixtime + $the_duration;
	$end_time = date ('Hi', $end_unixtime);
      }

      $items[$uid] = array('start' => $start_unixtime,
			   'end' => $end_unixtime,
			   'allday_start' => $allday_start,
			   'allday_end' => $allday_end,
			   'summary' => $summary,
			   'description' => $description,
			   'location' => $location,
			   'url' => $url,
			   'uid' => $uid);
      break;
    default:

      unset ($field, $data, $prop_pos, $property);
      ereg ("([^:]+):(.*)", $line, $line);
      $field = $line[1];
      $data = $line[2];

      $property = $field;
      $prop_pos = strpos($property,';');
      if ($prop_pos !== false) $property = substr($property,0,$prop_pos);
      $property = strtoupper($property);

      switch ($property) {

      case 'DTSTART':
	$zulu_time = false;
	if (substr($data,-1) == 'Z') $zulu_time = true;
	$data = str_replace('T', '', $data);
	$data = str_replace('Z', '', $data);
	$field = str_replace(';VALUE=DATE-TIME', '', $field);
	if ((preg_match("/^DTSTART;VALUE=DATE/i", $field)) || (ereg ('^([0-9]{4})([0-9]{2})([0-9]{2})$', $data))) {
	  ereg ('([0-9]{4})([0-9]{2})([0-9]{2})', $data, $dtstart_check);
	  $allday_start = $data;
	  $start_date = $allday_start;
	  $start_unixtime = strtotime($data);
	}
	else {
	  if (preg_match("/^DTSTART;TZID=/i", $field)) {
	    $tz_tmp = explode('=', $field);
	    $tz_dtstart = $tz_tmp[1];
	    unset($tz_tmp);
	  } 
	  elseif ($zulu_time) {
	    $tz_dtstart = 'GMT';
	  }
	  preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
	  $start_date = $regs[1] . $regs[2] . $regs[3];
	  $start_time = $regs[4] . $regs[5];
	  $start_unixtime = mktime($regs[4], $regs[5], 0, $regs[2], $regs[3], $regs[1]);

	  $dlst = date('I', $start_unixtime);
	  $server_offset_tmp = _ical_chooseOffset($start_unixtime, 'Same as Server');
	  if (isset($tz_dtstart)) {
	    if ($tz = _ical_tz($tz_dtstart)) {
	      $offset_tmp = date('I', $start_unixtime) ? $tz->offset_dst : $tz->offset;
	    } 
	    else {
	      $offset_tmp = '+0000';
	    }
	  } else if (isset($calendar_tz)) {
            if ($tz = _ical_tz($calendar_tz)) {
              $offset_tmp = date('I', $start_unixtime) ? $tz->offset_dst : $tz->offset;
	    } else {
	      $offset_tmp = '+0000';
	    }
	  } else {
	    $offset_tmp = $server_offset_tmp;
	  }
	  $start_unixtime = _ical_calcTime($offset_tmp, $server_offset_tmp, $start_unixtime);
	  $start_date = date('Ymd', $start_unixtime);
	  $start_time = date('Hi', $start_unixtime);
	  unset($server_offset_tmp, $offset_tmp, $tz_dtstart);
	}
	break;
      case 'DTEND':
	$zulu_time = false;
	if (substr($data,-1) == 'Z') $zulu_time = true;
	$data = str_replace('T', '', $data);
	$data = str_replace('Z', '', $data);
	$field = str_replace(';VALUE=DATE-TIME', '', $field);
        if ((preg_match("/^DTEND;VALUE=DATE/i", $field)) || (ereg ('^([0-9]{4})([0-9]{2})([0-9]{2})$', $data))) {
          $allday_end = $data;
          $end_date = $allday_end;
          $end_unixtime = strtotime($data);
        }
	else {
	  if (preg_match("/^DTEND;TZID=/i", $field)) {
	    $tz_tmp = explode('=', $field);
	    $tz_dtend = $tz_tmp[1];
	    unset($tz_tmp);
	  } 
	  elseif ($zulu_time) {
	    $tz_dtend = 'GMT';
	  }
	  preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
	  $end_date = $regs[1] . $regs[2] . $regs[3];
	  $end_time = $regs[4] . $regs[5];
	  $end_unixtime = mktime($regs[4], $regs[5], 0, $regs[2], $regs[3], $regs[1]);

	  $server_offset_tmp = _ical_chooseOffset($end_unixtime, 'Same as Server');
	  if (isset($tz_dtend)) {
	    if ($tz = _ical_tz($tz_dtend)) {
	      $offset_tmp = date('I', $end_unixtime) ? $tz->offset_dst : $tz->offset;
	    } 
	    else {
	      $offset_tmp = '+0000';
	    }
	  } else if (isset($calendar_tz)) {
            if ($tz = _ical_tz($calendar_tz)) {
              $offset_tmp = date('I', $end_unixtime) ? $tz->offset_dst : $tz->offset;
	    } else {
	      $offset_tmp = '+0000';
	    }
	  } else {
	    $offset_tmp = $server_offset_tmp;
	  }
	  $end_unixtime = _ical_calcTime($offset_tmp, $server_offset_tmp, $end_unixtime);
	  $end_date = date('Ymd', $end_unixtime);
	  $end_time = date('Hi', $end_unixtime);
	  unset($server_offset_tmp, $offset_tmp, $tz_dtend);
	}
	break;
      case 'DURATION':
	if (!stristr($field, '=DURATION')) {
	  ereg ('^P([0-9]{1,2}[W])?([0-9]{1,2}[D])?([T]{0,1})?([0-9]{1,2}[H])?([0-9]{1,2}[M])?([0-9]{1,2}[S])?', $data, $duration); 
	  $weeks = str_replace('W', '', $duration[1]); 
	  $days = str_replace('D', '', $duration[2]); 
	  $hours = str_replace('H', '', $duration[4]); 
	  $minutes = str_replace('M', '', $duration[5]); 
	  $seconds = str_replace('S', '', $duration[6]); 
	  $the_duration = ($weeks * 60 * 60 * 24 * 7) + ($days * 60 * 60 * 24) + ($hours * 60 * 60) + ($minutes * 60) + ($seconds);
	}
	break;
      case 'SUMMARY':
	$summary = $data;
	break;
      case 'DESCRIPTION':
	$description = $data;
	break;
      case 'UID':
	$uid = $data;
	break;
      case 'X-WR-CALNAME':
	$actual_calname = $data;
	break;
      case 'X-WR-TIMEZONE':
	$calendar_tz = $data;
	break;
      case 'LOCATION':
	$location = $data;
	break;
      case 'URL':
	$url = $data;
	break;
      }
    }
  }
  return $items;
}

function ical_help($section) {
  switch ($section) {
  case 'admin/modules#description':
    return t('iCalendar API for Events Modules');
    break;
  }
} 

function _ical_tz($tz) {
  return db_fetch_object(db_query("SELECT * FROM {ical_timezones} WHERE timezone = '%s'", $tz));
}

function _ical_chooseOffset($time, $timezone) {
  if (!isset($timezone)) $timezone = '';
  switch ($timezone) {
  case '':
    $offset = 'none';
    break;
  case 'Same as Server':
    $offset = date('O', $time);
    break;
  default:
    if ($tz = _ical_tz($timezone)) {
      $offset = date('I', $time) ? $tz->offset_dst : $tz->offset;
    } else {
      $offset = '+0000';
    }
  }
  return $offset;
}

function _ical_calcTime($have, $want, $time) {
  if ($have == 'none' || $want == 'none') return $time;
  $have_secs = _ical_calcOffset($have);
  $want_secs = _ical_calcOffset($want);
  $diff = $want_secs - $have_secs;
  $time += $diff;
  return $time;
}

function _ical_calcOffset($offset_str) {
  $sign = substr($offset_str, 0, 1);
  $hours = substr($offset_str, 1, 2);
  $mins = substr($offset_str, 3, 2);
  $secs = ((int)$hours * 3600) + ((int)$mins * 60);
  if ($sign == '-') $secs = 0 - $secs;
  return $secs;
}