<?php

/**
 * 
 *
 * @version $Id$
 * @copyright 2003 
 **/

use \Sabre\VObject;

require_once RHC_PATH.'vendor/autoload.php';
 
if (!function_exists("quoted_printable_encode")) {
	function quoted_printable_encode($string) {
	      $string = str_replace(array('%20', '%0D%0A', '%'), array(' ', "\r\n", '='), rawurlencode($string));
	      $string = preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\r\n", $string);
	
	      return $string;
	}
}

class events_to_vcalendar {
	var $events = array();
	var $dtend_is_exclusive = true;//google calendar and ical seem to treat it that way.
	var $gmt_offset = false;
	var $gmt_offset_seconds = false;
	var $tzid='';
	var $vtimezone='';
	var $timezone_string='';
	var $skip_location=false;
	function __construct($events,$gmt_offset=false,$tzid='',$vtimezone='',$skip_location=false) {
		$this->events = $events;
		$this->tzid = $tzid;
		$this->vtimezone = $vtimezone;
		$this->skip_location = $skip_location;
		if( !empty($gmt_offset) || '0' == $gmt_offset ){
			$this->gmt_offset = $gmt_offset;		
			$this->gmt_offset_seconds = $gmt_offset * 60 * 60 ;
		}
	}
	
	function get_vcalendar(){	
		ob_start();
		$output  = "BEGIN:VCALENDAR\r\n";
		$output .= "VERSION:2.0\r\n";
		$output .= "PRODID:-//RIGHTHERE//CALENDARIZE IT V3.2//EN\r\n";		
		$output .= $this->get_vtimezone();
		$output .= $this->get_vcalendar_body() ;

		$output .= "END:VCALENDAR\r\n";



		$output .= ob_get_contents();
		ob_end_clean();
				
		$output = $this->apply_line_folding( $output );		
				
		return $output;
	}
	
	function apply_line_folding( $output ){
		$lines = explode("\n", $output );
	
		$folded_output = array();
		
		foreach( $lines as $line ){
			if( $_SERVER['HTTP_USER_AGENT']=='Google-Calendar-Importer' ){
				//do not fold the RDATE line
				if( 0 === strpos( $line, 'RDATE' ) ){
					$folded_output[] = $line ."\r\n";
					continue;
				}
			}
					
			$count = 0;	
			$prefix = "";
			while( strlen($line) > 0 && $count++ < 9999 ){
				$line = $prefix.trim($line);
				$folded_output[] = substr( $line, 0, 75 ) ."\r\n";
				$line = substr( $line, 75 );
			
				$prefix.=" ";			
			}

			
		}
	
		return implode("",$folded_output);
	}
	
	function get_vtimezone(){
		$vtimezone = $this->generate_vtimezone( $this->timezone_string );
		if( false!==$vtimezone ){
			$this->vtimezone = $vtimezone->serialize();
		}
		return $this->vtimezone;
	}
	
	function get_vcalendar_body(){
		$properties = array(
			'UID'			=> 'id',
			'DTSTART'		=> 'start',
			'DTEND'			=> 'end',
			'DTSTAMP'		=> 'dtstamp',
			'SUMMARY'		=> 'title',
			'DESCRIPTION' 	=> 'description',
			'RRULE'			=> 'fc_rrule',
			'EXDATE'		=> 'fc_exdate',
			'RDATE'			=> 'fc_rdate',
			'URL'			=> 'url'
		);
		$str = "";
		
		if(!empty($this->events)){
			foreach($this->events as $event){		
				if(!isset($event['start']))continue;
				$str .= "BEGIN:VEVENT\r\n";
				foreach( $properties as $property => $field ){
					$method = "_".strtolower($property);
					if(method_exists($this,$method)){
						$str.=$this->$method( $field, $property, $event );
					}
				}
				$str.= $this->get_location($event);
				$str.= "END:VEVENT\r\n";
				//$str.= "\r\n";//empty lines not supported.			
			}
		}
		
		return $str;
	}
	
	function get_location($event){
		$out = '';
		if(isset($event['terms']) && is_array($event['terms']) && !empty($event['terms'])){
			$done_venue = false;
			$done_organizer = false;
			foreach($event['terms'] as $term){
				if( !$done_venue && $term->taxonomy==RHC_VENUE){
					$done_venue = true;
					if( ''!=trim($term->glat) && ''!=trim($term->glon) ){
						//$out.=$this->vevent_row('GEO', sprintf('%s;%s',$term->glat,$term->glon) );
						//google is no longer ignoring the geo field. instead it fails to load
					}
	
					$value = empty( $term->gaddress )?$term->name:$term->gaddress;//googl cal seems to ignore GEO coordinates. so prefer gaddress here.				
					$value = html_entity_decode(  $this->text_escaped_chars( $value ), ENT_NOQUOTES, 'UTF-8'); 
					$out .= $this->vevent_row('LOCATION', $value);
				}else if( !$done_organizer && $term->taxonomy==RHC_ORGANIZER ){
					$done_organizer=true;
					$email = get_term_meta( $term->term_id , 'email', true);
					$value = empty($email) ? '' : sprintf("MAILTO:%s",html_entity_decode( $email, ENT_NOQUOTES, 'UTF-8'));
					$field = sprintf('ORGANIZER;CN=%s', $this->text_escaped_chars( html_entity_decode(  $term->name , ENT_NOQUOTES, 'UTF-8') ) );
					$out .= $this->vevent_row( $field, $value);			
				}
			}
		}
		return $out;
	}
	
	function text_encode($text){
		return quoted_printable_encode($text);
	}
	
	function unencoded_text( $field, $property, $e ){
		if(!isset($e[$field]) || ''==trim( $e[$field] ) )return '';
		$value = html_entity_decode( $e[$field], ENT_NOQUOTES, 'UTF-8'); 
	//	$value = utf8_encode($value);
		return $this->vevent_row($property, $value );	
	}
	
	function datetime(  $field, $property, $e ){
		if( !isset($e['tzid']) && isset($e['local_id']) && $e['local_id'] > 0 && !empty($this->timezone_string) ){
			$e['tzid'] = $this->timezone_string;
		}

		if(!isset($e[$field]))return '';
		$ts = strtotime($e[$field]);
		if(false===$ts||-1==$ts)return '';
		$tzid = isset($e['tzid']) && ''!=trim($e['tzid'])?trim($e['tzid']):$this->tzid;
		if(''!=$tzid){
			$property = $property.';TZID='.$tzid;
			$format = 'Ymd\THis';
		}else if(false!==$this->gmt_offset_seconds( $field, $property, $e ) ){
			$ts = $ts - $this->gmt_offset_seconds( $field, $property, $e );
			$format = 'Ymd\THis\Z';
		}else{
			$format = 'Ymd\THis';
		}

		return $this->vevent_row($property, date( $format, $ts ) );
	}
	
	function gmt_offset_seconds(  $field, $property, $e ){
		if( !empty($this->timezone_string) && isset( $e[$field] ) ){
			$ds = new DateTime( $e[$field] , new DateTimeZone( $this->timezone_string ));		
			return $ds->getOffset();			
		}
		return $this->gmt_offset_seconds;
	}
	
	function allday_date( $field, $property, $e ){
		if(!isset($e[$field]))return '';
		$ts = strtotime($e[$field]);
		if($field=='end' && $this->dtend_is_exclusive && $e['fc_start']!=$e['fc_end']){
			$ts = $ts + 86400;//we use fc_end inclusive, whilst most ical implementation seem to do dtend exclusive.
		}
		if(false==$ts||-1==$ts)return '';
		return $this->vevent_row($property.';VALUE=DATE', date( 'Ymd', $ts ) );
	}
	
	function vevent_row($field,$value){
		return sprintf( "%s:%s\r\n", $field, $value );
	}
	
	function _uid( $field, $property, $e ){
		$arr = parse_url( site_url() );
		if( isset($e['network']) ){
			$id = $e['id'];
		}else{
			$id = $e['id'].'@'.$arr['host'];
		}
		return $this->vevent_row($property, $id );
	}
	
	function _dtstart( $field, $property, $e ){
		return isset($e['allDay'])&&$e['allDay']? $this->allday_date( $field, $property, $e ) : $this->datetime(  $field, $property, $e );
	}
	
	function _dtend( $field, $property, $e ){
		return isset($e['allDay'])&&$e['allDay']? $this->allday_date( $field, $property, $e ) : $this->datetime(  $field, $property, $e );
	}
	
	function _dtstamp( $field, $property, $e ){
		return $this->datetime(  $field, $property, $e );
	}
	
	function _rrule( $field, $property, $e ){
		if(!isset($e[$field]) || ''==trim( $e[$field] ) )return '';
		$e[$field]=rtrim($e[$field], ';');//remove ending semicolon
		return $this->vevent_row($property, $e[$field] );			
		//return $this->unencoded_text( $field, $property, $e );
	}
	
	function _exdate($field, $property, $e){
		if(!isset($e[$field]) || ''==trim( $e[$field] ) )return '';
		
		if( isset($e['allDay'])&&$e['allDay'] ){
			$dates = explode(',',$e[$field]);
			$new_dates = array();
			foreach($dates as $date){
				$ts = strtotime($date);
				$new_dates[]=date('Ymd',$ts);
			}	
			$e[$field] = implode(',',$new_dates);	
		}

		$tzid = isset($e['tzid']) && ''!=trim($e['tzid'])?trim($e['tzid']):$this->tzid;		
		if($tzid!=''){
			if( isset($e['allDay'])&&$e['allDay'] ){
			
			}else{
				$property = $property.';TZID='.$tzid;
			}
		}else if(false!==$this->gmt_offset_seconds){
			if( isset($e['allDay'])&&$e['allDay'] ){

			}else{
				$dates = explode(',',$e[$field]);
				$new_dates = array();
				foreach($dates as $date){
					$ts = strtotime($date) - $this->gmt_offset_seconds;
					$new_dates[]=date('Ymd\THis\Z',$ts);
				}	
				$e[$field] = implode(',',$new_dates);
			}
		}
		//----
		return $this->vevent_row($property, $e[$field] );
	}
	
	function _rdate($field, $property, $e){
		if( !isset($e['tzid']) && isset($e['local_id']) && $e['local_id'] > 0 && !empty($this->timezone_string) ){
			$e['tzid'] = $this->timezone_string;
		}

		if(!isset($e[$field]))return '';
		
		$tzid = isset($e['tzid']) && ''!=trim($e['tzid'])?trim($e['tzid']):$this->tzid;
		if(''!=$tzid){
			$property = $property.';TZID='.$tzid;
		}
	
		if( false!==strpos( $e[$field] , '/' ) ){
			$property = $property.';VALUE=PERIOD';
		}
	
		return $this->vevent_row($property, $e[$field] );
	}
	
	function _summary( $field, $property, $e ){
		$summary = isset($e['title']) ? $e['title'] : '' ;
		$e['title'] = $this->text_escaped_chars( $summary );
		return $this->unencoded_text( $field, $property, $e );
		//return $this->vevent_row('SUMMARY', $this->text_encode( $e['title'] ) );
		//return sprintf( "SUMMARY;ENCODING=QUOTED-PRINTABLE:%s\r\n", $this->text_encode( $e['title'] ) );
	}
	
	function _description( $field, $property, $e ){	
		$str = isset($e['description']) ? $e['description'] : '' ;
		$e['description'] = $this->text_escaped_chars( $str );
		return $this->unencoded_text( $field, $property, $e );

		return '';
	}
	
	function _url( $field, $property, $e ){
		return $this->unencoded_text( $field, $property, $e );
	}
	
	function text_escaped_chars( $str ){
/*
  ESCAPED-CHAR = "\\" / "\;" / "\," / "\N" / "\n")
     ; \\ encodes \, \N or \n encodes newline
     ; \; encodes ;, \, encodes ,
*/
		foreach(array(
			"\r\n" 	=> '\n',
			"\n" 	=> '\n',
			";"	 	=> "\;",
			","	 	=> "\,",
			'"'		=> "^'"
		) as $char => $replacement){
			$str = str_replace($char,$replacement,$str);
		}
			
		return trim($str);
	}
	
	function generate_vtimezone($tzid, $from = 0, $to = 0)
	{
		if (!$from) $from = time();
		if (!$to)   $to = $from;
		try {
			$tz = new \DateTimeZone($tzid);
		}
		catch (\Exception $e) {
			return false;
		}
		// get all transitions for one year back/ahead
		$year = 86400 * 360;
		$transitions = $tz->getTransitions($from - $year, $to + $year);
		$vcalendar = new VObject\Component\VCalendar();
		$vt = $vcalendar->createComponent('VTIMEZONE');
		$vt->TZID = $tz->getName();
		$std = null; $dst = null;
		foreach ($transitions as $i => $trans) {
			$cmp = null;
			// skip the first entry...
			if ($i == 0) {
				// ... but remember the offset for the next TZOFFSETFROM value
				$tzfrom = $trans['offset'] / 3600;
				continue;
			}
			// daylight saving time definition
			if ($trans['isdst']) {
				$t_dst = $trans['ts'];
				$dst = $vcalendar->createComponent('DAYLIGHT');
				$cmp = $dst;
			}
			// standard time definition
			else {
				$t_std = $trans['ts'];
				$std = $vcalendar->createComponent('STANDARD');
				$cmp = $std;
			}
			if ($cmp) {		
				$dt = new DateTime($trans['time']);
				$offset = $trans['offset'] / 3600;
				$cmp->DTSTART = $dt->format('Ymd\THis');
				$cmp->TZOFFSETFROM = $this->get_offset_for_vt( $tzfrom );
				$cmp->TZOFFSETTO   = $this->get_offset_for_vt( $offset );
				// add abbreviated timezone name if available
				if (!empty($trans['abbr'])) {
					$cmp->TZNAME = $trans['abbr'];
				}
				$tzfrom = $offset;
				$vt->add($cmp);
			}
			// we covered the entire date range
			if ($std && $dst && min($t_std, $t_dst) < $from && max($t_std, $t_dst) > $to) {
				break;
			}
		}
		// add X-MICROSOFT-CDO-TZID if available
		$microsoftExchangeMap = array_flip(VObject\TimeZoneUtil::$microsoftExchangeMap);
		if (array_key_exists($tz->getName(), $microsoftExchangeMap)) {
			$vt->add('X-MICROSOFT-CDO-TZID', $microsoftExchangeMap[$tz->getName()]);
		}
		return $vt;
	}	
	
	function get_offset_for_vt( $tzfrom ){
		if( $tzfrom < 0 ){
			return sprintf('%s%03d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom), ($tzfrom - floor($tzfrom)) * 60);
		}else{
			return sprintf('%s%02d%02d', $tzfrom >= 0 ? '+' : '', floor($tzfrom), ($tzfrom - floor($tzfrom)) * 60);
		}
		
	}
}

?>