1620 lines
44 KiB
PHP
1620 lines
44 KiB
PHP
<?php
|
|
|
|
/**
|
|
* <pre>
|
|
* Invision Power Services
|
|
* IP.Board v3.4.5
|
|
* Library to facilitate iCalendar input and output
|
|
* Last Updated: $Date: 2013-02-14 18:02:12 -0500 (Thu, 14 Feb 2013) $
|
|
* </pre>
|
|
*
|
|
* @author $Author: bfarber $
|
|
* @copyright (c) 2001 - 2009 Invision Power Services, Inc.
|
|
* @license http://www.invisionpower.com/company/standards.php#license
|
|
* @package IP.Board
|
|
* @subpackage Calendar
|
|
* @link http://www.invisionpower.com
|
|
* @since 5th January 2005
|
|
* @version $Revision: 11991 $
|
|
*/
|
|
|
|
if ( ! defined( 'IN_IPB' ) )
|
|
{
|
|
print "<h1>Incorrect access</h1>You cannot access this file directly.";
|
|
exit();
|
|
}
|
|
|
|
class app_calendar_classes_icalendarOutput
|
|
{
|
|
/**#@+
|
|
* Registry objects
|
|
*
|
|
* @access protected
|
|
* @var object
|
|
*/
|
|
protected $registry;
|
|
protected $DB;
|
|
protected $settings;
|
|
protected $request;
|
|
protected $lang;
|
|
protected $member;
|
|
protected $memberData;
|
|
protected $cache;
|
|
protected $caches;
|
|
/**#@-*/
|
|
|
|
/**
|
|
* Calendar data
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $calendar = array();
|
|
|
|
/**
|
|
* Calendar events to insert in feed
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_events = array();
|
|
|
|
/**
|
|
* Calendar functions
|
|
*
|
|
* @var object
|
|
*/
|
|
protected $functions;
|
|
|
|
/**
|
|
* Error message from import
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_error = '';
|
|
|
|
/**
|
|
* Type of begin block we are currently parsing
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_begin = '';
|
|
|
|
/**
|
|
* Raw iCalendar feed data after parsing
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_parsedIcsData = array();
|
|
|
|
/**
|
|
* Raw iCalendar data before parsing
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_rawIcsData = array();
|
|
|
|
/**
|
|
* Temp: Current timezone ID we are parsing
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_tzId = '';
|
|
|
|
/**
|
|
* Temp: Current timezone ID we are beginning to parse
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_tzBegin = '';
|
|
|
|
/**
|
|
* Earliest timestamp from feed
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $_feedEarliest = 0;
|
|
|
|
/**
|
|
* Latest timestamp from feed
|
|
*
|
|
* @var int
|
|
*/
|
|
protected $_feedLatest = 0;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param object Registry object
|
|
* @param int Calendar we want to output
|
|
* @return @e void
|
|
*/
|
|
public function __construct( ipsRegistry $registry, $calendarId )
|
|
{
|
|
/* Make object */
|
|
$this->registry = $registry;
|
|
$this->DB = $this->registry->DB();
|
|
$this->settings =& $this->registry->fetchSettings();
|
|
$this->request =& $this->registry->fetchRequest();
|
|
$this->lang = $this->registry->getClass('class_localization');
|
|
$this->member = $this->registry->member();
|
|
$this->memberData =& $this->registry->member()->fetchMemberData();
|
|
$this->cache = $this->registry->cache();
|
|
$this->caches =& $this->registry->cache()->fetchCaches();
|
|
|
|
//-----------------------------------------
|
|
// Language files
|
|
//-----------------------------------------
|
|
|
|
$this->registry->class_localization->loadLanguageFile( array( 'public_calendar' ), 'calendar' );
|
|
|
|
//-----------------------------------------
|
|
// Functions class
|
|
//-----------------------------------------
|
|
|
|
$classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( 'calendar' ) . "/sources/functions.php", 'app_calendar_classes_functions', 'calendar' );
|
|
$this->functions = new $classToLoad( $this->registry, true );
|
|
|
|
//-----------------------------------------
|
|
// Get our calendar
|
|
//-----------------------------------------
|
|
|
|
$this->calendar = $this->functions->getCalendar( $calendarId );
|
|
}
|
|
|
|
/**
|
|
* Add an event
|
|
*
|
|
* @param array Event data
|
|
* @return @e void
|
|
*/
|
|
public function addEvent( $event )
|
|
{
|
|
if( $event['event']['event_id'] )
|
|
{
|
|
$this->_events[ $event['event']['event_id'] ] = $event;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove an event
|
|
*
|
|
* @param int Event id
|
|
* @return @e void
|
|
*/
|
|
public function removeEvent( $eventId )
|
|
{
|
|
if( $eventId )
|
|
{
|
|
unset($this->_events[ $eventId ]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return events to be output
|
|
*
|
|
* @return @e void
|
|
*/
|
|
public function getEvents()
|
|
{
|
|
return $this->_events;
|
|
}
|
|
|
|
/**
|
|
* Build iCalendar feed and return
|
|
*
|
|
* @return string iCalendar feed (can be downloaded or sent as webcal subscription)
|
|
*/
|
|
public function buildICalendarFeed()
|
|
{
|
|
//-----------------------------------------
|
|
// Start of output
|
|
//-----------------------------------------
|
|
|
|
$output = "BEGIN:VCALENDAR\r\n";
|
|
$output .= "VERSION:2.0\r\n";
|
|
$output .= "PRODID:-//IP.Board Calendar " . ipsRegistry::$version . "//EN\r\n";
|
|
$output .= "METHOD:PUBLISH\r\n";
|
|
$output .= "CALSCALE:GREGORIAN\r\n";
|
|
$output .= "X-WR-CALNAME:" . $this->_encodeSpecialCharacters( $this->calendar['cal_title'] ) . "\r\n";
|
|
|
|
//-----------------------------------------
|
|
// Format the time zones (GMT only)
|
|
//-----------------------------------------
|
|
|
|
$output .= $this->_addTimezones();
|
|
|
|
//-----------------------------------------
|
|
// Format the events
|
|
//-----------------------------------------
|
|
|
|
$output .= $this->_addEvents();
|
|
|
|
//-----------------------------------------
|
|
// End of output
|
|
//-----------------------------------------
|
|
|
|
$output .= "END:VCALENDAR\r\n";
|
|
|
|
//-----------------------------------------
|
|
// Return the output
|
|
//-----------------------------------------
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Build the VEVENT parts of the iCalendar feed
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _addEvents()
|
|
{
|
|
//-----------------------------------------
|
|
// Init
|
|
//-----------------------------------------
|
|
|
|
$output = '';
|
|
|
|
//-----------------------------------------
|
|
// Loop over events
|
|
//-----------------------------------------
|
|
|
|
$events = $this->getEvents();
|
|
|
|
if( count($events) )
|
|
{
|
|
foreach( $events as $event )
|
|
{
|
|
//-----------------------------------------
|
|
// Basic output stuff
|
|
//-----------------------------------------
|
|
|
|
$output .= "BEGIN:VEVENT\r\n";
|
|
$output .= "SUMMARY:" . $this->_encodeSpecialCharacters( $event['event']['event_title'] ) . "\r\n";
|
|
$output .= "DTSTAMP:" . $this->_buildIcalDate( $event['event']['event_saved'] ) . "\r\n";
|
|
$output .= "SEQUENCE:" . $event['event']['event_sequence'] . "\r\n";
|
|
$output .= "UID:" . $this->_buildUid( array( $event['event']['event_saved'], $event['event']['event_id'] ) ) . "\r\n";
|
|
$output .= $this->_foldLines( "ORGANIZER;CN=\"" . $this->_encodeSpecialCharacters( $event['member']['members_display_name'], false ) . '":' . $this->settings['email_out'] ) . "\r\n";
|
|
|
|
//-----------------------------------------
|
|
// Attachments...
|
|
//-----------------------------------------
|
|
|
|
$attachments = array();
|
|
|
|
if( $event['event']['event_attach_content'] )
|
|
{
|
|
preg_match_all( "/(http.+?attach_id=(\d+))/i", $event['event']['event_attach_content'], $matches );
|
|
|
|
if( is_array($matches) AND count($matches) )
|
|
{
|
|
foreach( $matches[2] as $k => $v )
|
|
{
|
|
$attachments[ $v ] = $v;
|
|
}
|
|
}
|
|
}
|
|
|
|
preg_match_all( "/(http.+?attach_id=(\d+))/i", $event['event']['event_content'], $matches );
|
|
|
|
if( is_array($matches) AND count($matches) )
|
|
{
|
|
foreach( $matches[2] as $k => $v )
|
|
{
|
|
$attachments[ $v ] = $v;
|
|
}
|
|
}
|
|
|
|
if( count($attachments) )
|
|
{
|
|
$this->DB->build( array( 'select' => '*', 'from' => 'attachments', 'where' => 'attach_id IN(' . implode( ',', $attachments ) . ')' ) );
|
|
$this->DB->execute();
|
|
|
|
while( $r = $this->DB->fetch() )
|
|
{
|
|
$output .= "ATTACH;FMTTYPE=" . $this->caches['attachtypes'][ $r['attach_ext'] ]['atype_mimetype'] . ":" . $this->registry->output->buildSEOUrl( "app=core&module=attach§ion=attach&attach_id=" . $r['attach_id'], 'public' ) . "\r\n";
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Output description/content
|
|
//-----------------------------------------
|
|
|
|
$output .= "DESCRIPTION:" . $this->_encodeSpecialCharacters( $event['event']['event_content'] ) . "\r\n";
|
|
|
|
//-----------------------------------------
|
|
// Figure out times (the hard part...)
|
|
//-----------------------------------------
|
|
|
|
$_startTime = @strtotime( $event['event']['event_start_date'] );
|
|
$_endTime = @strtotime( $event['event']['event_end_date'] );
|
|
$_allDay = $event['event']['event_all_day'];
|
|
|
|
if( $event['event']['event_recurring'] )
|
|
{
|
|
if( $event['event_all_day'] )
|
|
{
|
|
$output .= "DTSTART;VALUE=DATE:" . $this->_buildIcalDate( $_startTime, true ) . "\r\n";
|
|
$output .= "DTEND;VALUE=DATE:" . $this->_buildIcalDate( $_startTime + 86400, true ) . "\r\n";
|
|
}
|
|
else
|
|
{
|
|
$output .= "DTSTART;TZID=Europe/London:" . $this->_buildIcalDate( $_startTime ) . "\r\n";
|
|
$output .= "DTEND;TZID=Europe/London:" . $this->_buildIcalDate( $_startTime ) . "\r\n";
|
|
}
|
|
}
|
|
else if( $_endTime )
|
|
{
|
|
if( $_allDay )
|
|
{
|
|
$output .= "DTSTART;VALUE=DATE:" . $this->_buildIcalDate( $_startTime, true ) . "\r\n";
|
|
$output .= "DTEND;VALUE=DATE:" . $this->_buildIcalDate( $_endTime + 86400, true ) . "\r\n";
|
|
}
|
|
else
|
|
{
|
|
$output .= "DTSTART;TZID=Europe/London:" . $this->_buildIcalDate( $_startTime ) . "\r\n";
|
|
$output .= "DTEND;TZID=Europe/London:" . $this->_buildIcalDate( $_endTime ) . "\r\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( $_allDay )
|
|
{
|
|
$output .= "DTSTART;VALUE=DATE:" . $this->_buildIcalDate( $_startTime, true ) . "\r\n";
|
|
}
|
|
else
|
|
{
|
|
$output .= "DTSTART;TZID=Europe/London:" . $this->_buildIcalDate( $_startTime ) . "\r\n";
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Recurring
|
|
//-----------------------------------------
|
|
|
|
if ( $event['event']['event_recurring'] )
|
|
{
|
|
$_freq = $event['event']['event_recurring'] == 3 ? 'YEARLY' : ( $event['event']['event_recurring'] == 2 ? 'MONTHLY' : 'WEEKLY' );
|
|
|
|
$output .= "RRULE:FREQ=" . $_freq . ";INTERVAL=1;UNTIL=" . $this->_buildIcalDate( $_endTime ) . "\r\n";
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Attendees?
|
|
//-----------------------------------------
|
|
|
|
if( is_array($event['event']['_rsvp_attendees']) AND count($event['event']['_rsvp_attendees']) )
|
|
{
|
|
foreach( $event['event']['_rsvp_attendees'] as $mid => $member )
|
|
{
|
|
$output .= $this->_foldLines( "ATTENDEE;CN=\"" . $this->_encodeSpecialCharacters( $member['members_display_name'], false ) . '";CUTYPE=INDIVIDUAL;PARTSTAT=ACCEPTED:' . $this->settings['email_out'] ) . "\r\n";
|
|
}
|
|
}
|
|
|
|
$output .= "END:VEVENT\r\n";
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Return the output
|
|
//-----------------------------------------
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Build the VTIMEZONE parts of the iCalendar feed
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function _addTimezones()
|
|
{
|
|
//-----------------------------------------
|
|
// Init
|
|
//-----------------------------------------
|
|
|
|
$output = '';
|
|
|
|
//-----------------------------------------
|
|
// Get the needed data
|
|
//-----------------------------------------
|
|
|
|
$events = $this->getEvents();
|
|
$years = array();
|
|
|
|
if( count($events) )
|
|
{
|
|
foreach( $events as $event )
|
|
{
|
|
$_startTime = @strtotime( $event['event']['event_start_date'] );
|
|
$_endTime = @strtotime( $event['event']['event_end_date'] );
|
|
|
|
$_year = gmdate( 'Y', $_startTime );
|
|
$years[ $_year ] = $_year;
|
|
|
|
if( $_endTime )
|
|
{
|
|
while( $_startTime < $_endTime )
|
|
{
|
|
$_year = gmdate( 'Y', $_startTime );
|
|
$years[ $_year ] = $_year;
|
|
|
|
$_year = gmdate( 'Y', $_endTime );
|
|
$years[ $_year ] = $_year;
|
|
|
|
$_startTime += 2592000; // add one month
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Add the timezones to the object
|
|
//-----------------------------------------
|
|
|
|
foreach( $years as $year )
|
|
{
|
|
$_daylight_start = strtotime( 'last Sunday of March ' . $year );
|
|
$_standard_start = strtotime( 'last Sunday of October ' . $year );
|
|
$_daylight = gmmktime( 2, 0, 0, 3 , gmdate( 'j', $_daylight_start ), $year );
|
|
$_standard = gmmktime( 2, 0, 0, 10, gmdate( 'j', $_standard_start ), $year );
|
|
|
|
$output .= "BEGIN:VTIMEZONE\r\n";
|
|
$output .= "TZID:Europe/London\r\n";
|
|
$output .= "TZURL:http://tzurl.org/zoneinfo/Europe/London\r\n";
|
|
$output .= "X-LIC-LOCATION:Europe/London\r\n";
|
|
|
|
$output .= "BEGIN:DAYLIGHT\r\n";
|
|
$output .= "TZOFFSETFROM:+0000\r\n";
|
|
$output .= "TZOFFSETTO:+0100\r\n";
|
|
$output .= "TZNAME:BST\r\n";
|
|
$output .= "DTSTART:" . $this->_buildIcalDate( $_daylight ) . "\r\n";
|
|
$output .= "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\n";
|
|
$output .= "END:DAYLIGHT\r\n";
|
|
|
|
$output .= "BEGIN:STANDARD\r\n";
|
|
$output .= "TZOFFSETFROM:+0100\r\n";
|
|
$output .= "TZOFFSETTO:+0000\r\n";
|
|
$output .= "TZNAME:GMT\r\n";
|
|
$output .= "DTSTART:" . $this->_buildIcalDate( $_standard ) . "\r\n";
|
|
$output .= "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\n";
|
|
$output .= "END:STANDARD\r\n";
|
|
|
|
$output .= "END:VTIMEZONE\r\n";
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Return the output
|
|
//-----------------------------------------
|
|
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Return a UID for iCalendar
|
|
*
|
|
* @param array Parameters to use to generate UID
|
|
* @param string UID
|
|
*/
|
|
protected function _buildUid( $params )
|
|
{
|
|
return md5( implode( '.', $params ) ) . '@' . $_SERVER['SERVER_ADDR'];
|
|
}
|
|
|
|
/**
|
|
* Return a date in iCalendar format
|
|
*
|
|
* @param int Timestamp
|
|
* @param bool Build date only format
|
|
* @param string Date in format of YYYYMMDDTHHMMSSZ OR YYYYMMDD
|
|
*/
|
|
protected function _buildIcalDate( $time, $dateOnly=false )
|
|
{
|
|
if( $dateOnly )
|
|
{
|
|
return gmdate( 'Ymd', $time );
|
|
}
|
|
else
|
|
{
|
|
return gmdate( 'Ymd\THis\Z', $time );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encode special characters in a string for iCalendar
|
|
*
|
|
* @param string String to encode
|
|
* @param bool Line-fold
|
|
* @param string Encoded string
|
|
*/
|
|
protected function _encodeSpecialCharacters( $text, $lineFold=true )
|
|
{
|
|
$text = strip_tags( IPSText::br2nl( $text ) );
|
|
$text = str_replace( "\\", "\\\\", $text );
|
|
$text = str_replace( "\n" , '\\n', $text );
|
|
$text = str_replace( "\r" , '\\n', $text );
|
|
$text = str_replace( ',' , '\,', $text );
|
|
$text = str_replace( ';' , '\;', $text );
|
|
//$text = str_replace( ':' , '\:', $text );
|
|
$text = str_replace( '"', '\"', $text );
|
|
|
|
if( $lineFold )
|
|
{
|
|
$text = $this->_foldLines( $text );
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Un-encode special characters in a string coming from iCalendar feed
|
|
*
|
|
* @param string String to unencode
|
|
* @param string Encoded string
|
|
* @link http://community.invisionpower.com/tracker/issue-33787-formatting-problem-with-imported-ics-file-calendar-app/
|
|
*/
|
|
protected function _unencodeSpecialCharacters( $text )
|
|
{
|
|
/* Reverse encoding */
|
|
if( stripos( $text, 'encoding=' ) === 0 )
|
|
{
|
|
preg_match( "#encoding=(.+?):(.+?)$#i", $text, $matches );
|
|
|
|
if( $matches[1] )
|
|
{
|
|
switch( strtolower($matches[1]) )
|
|
{
|
|
case 'base64':
|
|
$text = base64_decode( $matches[2] );
|
|
break;
|
|
|
|
case 'quoted-printable':
|
|
$text = quoted_printable_decode( $matches[2] );
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$text = substr( $text, strpos( $text, ':' ) );
|
|
}
|
|
}
|
|
|
|
$text = str_replace( '\\n', "\n", $text );
|
|
$text = str_replace( '\,', "," , $text );
|
|
$text = str_replace( '\;', ";" , $text );
|
|
$text = str_replace( '\:', ":" , $text );
|
|
$text = str_replace( 'DQUOTE', '"' , $text );
|
|
|
|
return $text;
|
|
}
|
|
|
|
/**
|
|
* Fold lines per RFC2445
|
|
*
|
|
* @param string $text String to fold
|
|
* @return string
|
|
* @link https://gist.github.com/81747
|
|
*/
|
|
protected function _foldLines( $text )
|
|
{
|
|
/* Do we have mb functions? */
|
|
if ( function_exists('mb_list_encodings') AND function_exists('mb_substr') AND function_exists('mb_strlen') )
|
|
{
|
|
$valid_encodings = mb_list_encodings();
|
|
|
|
if ( count($valid_encodings) )
|
|
{
|
|
/* Does mb functions cover our charset? */
|
|
if ( in_array( strtoupper(IPS_DOC_CHAR_SET), $valid_encodings ) )
|
|
{
|
|
mb_internal_encoding( strtoupper(IPS_DOC_CHAR_SET) );
|
|
|
|
$return = array();
|
|
$_extra = 15; /* Takes into account line beginning, i.e. "DESCRIPTION:" */
|
|
|
|
while( strlen($text) > 60 )
|
|
{
|
|
$space = 75 - $_extra; /* Remove line beginning - subsequent loops this will be tab character */
|
|
$mbcc = $space;
|
|
|
|
while( $mbcc )
|
|
{
|
|
$line = mb_substr( $text, 0, $mbcc ); /* Get first chunk of chars */
|
|
$octet = strlen( $line ); /* Determine how long this really is (3-byte letters could triple the size) */
|
|
|
|
/* Too long ? */
|
|
if( $octet > $space )
|
|
{
|
|
$mbcc -= $octet - $space;
|
|
}
|
|
else
|
|
{
|
|
$return[] = $line;
|
|
$_extra = 1;
|
|
$text = mb_substr( $text, $mbcc );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Anything left? */
|
|
if( !empty($text) )
|
|
{
|
|
$return[] = $text;
|
|
}
|
|
|
|
/* Return now */
|
|
return implode( "\r\n\t", $return );
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Use regular string functions - can break mb characters however, but if mb charset is used, mb functions should be enabled anyways */
|
|
return implode( "\r\n\t", str_split( $text, 60 ) );
|
|
}
|
|
|
|
/**
|
|
* Unfold lines per RFC2445 4.1
|
|
*
|
|
* @param string $string Starting string
|
|
* @param int $line Starting line number
|
|
* @param string
|
|
*/
|
|
protected function _unfoldLines( $string, $line )
|
|
{
|
|
//-----------------------------------------
|
|
// Recursively unfold as needed
|
|
//-----------------------------------------
|
|
|
|
if( isset( $this->_rawIcsData[ $line + 1 ] ) AND ( substr( $this->_rawIcsData[ $line + 1 ], 0, 1 ) == ' ' OR substr( $this->_rawIcsData[ $line + 1 ], 0, 1 ) == "\t" ) )
|
|
{
|
|
$string .= ltrim( $this->_rawIcsData[ $line + 1 ] );
|
|
$string = $this->_unfoldLines( $string, $line + 1 );
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Unparse time information from iCalendar datetime info
|
|
*
|
|
* @param string $string iCalendar line
|
|
* @return array Time information
|
|
*/
|
|
protected function _unparseTimeInfo( $string )
|
|
{
|
|
if( strpos( $string, '=' ) !== false )
|
|
{
|
|
$_tmp = explode( '=', $string );
|
|
$_key = explode( ';', $_tmp[0] );
|
|
$tmp = explode( ':', $_tmp[1] );
|
|
}
|
|
else
|
|
{
|
|
$_tmp = array();
|
|
$_key = array();
|
|
$tmp = explode( ':', $string );
|
|
$tmp[0] = 'DATETIME';
|
|
}
|
|
|
|
$tzid = '';
|
|
|
|
//-----------------------------------------
|
|
// Got a TZID?
|
|
//-----------------------------------------
|
|
|
|
if ( $_key[1] == 'TZID' )
|
|
{
|
|
$tzid = $tmp[0];
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Is it a date?
|
|
//-----------------------------------------
|
|
|
|
if ( $tmp[0] == 'DATE' )
|
|
{
|
|
$timestamp = strtotime( $tmp[1] );
|
|
}
|
|
else
|
|
{
|
|
$timestamp = '?';
|
|
}
|
|
|
|
$return = array(
|
|
'type' => $tmp[0],
|
|
'raw' => $tmp[1],
|
|
'raw_ts' => strtotime( $tmp[1] ),
|
|
'ts' => $timestamp,
|
|
'tzid' => str_replace( '"', '', $tzid ),
|
|
);
|
|
|
|
//-----------------------------------------
|
|
// Earliest? Latest?
|
|
//-----------------------------------------
|
|
|
|
if ( ( $this->_feedEarliest == 0 ) OR ( $return['raw_ts'] < $this->_feedEarliest ) )
|
|
{
|
|
$this->_feedEarliest = $return['raw_ts'];
|
|
}
|
|
|
|
if ( ( $this->_feedLatest == 0 ) OR ( $return['raw_ts'] > $this->_feedLatest ) )
|
|
{
|
|
$this->_feedLatest = $return['raw_ts'];
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Return
|
|
//-----------------------------------------
|
|
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Unformat content from incoming ical feed
|
|
*
|
|
* @param string $string String content to unparse
|
|
* @param int $line Line number
|
|
* @return mixed Array of data, or false
|
|
*/
|
|
protected function _unparseContent( $string, $line )
|
|
{
|
|
//-----------------------------------------
|
|
// If line starts with a space, it was folded (skip)
|
|
//-----------------------------------------
|
|
|
|
if( substr( $this->_rawIcsData[ $line ], 0, 1 ) == ' ' )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Process
|
|
//-----------------------------------------
|
|
|
|
$_temp = preg_split( "/(:|;)/", $string );
|
|
$_type = array_shift( $_temp );
|
|
$_data = implode( ':', $_temp );
|
|
|
|
//-----------------------------------------
|
|
// Unfold folded lines if necessary
|
|
//-----------------------------------------
|
|
|
|
$_data = $this->_unfoldLines( $_data, $line );
|
|
|
|
//-----------------------------------------
|
|
// Return data
|
|
//-----------------------------------------
|
|
|
|
return array( 'type' => $_type, 'data' => $_data );
|
|
}
|
|
|
|
/**
|
|
* Get event recurrence data
|
|
*
|
|
* @param string Data
|
|
* @return array
|
|
*/
|
|
protected function _getRecurrenceData( $data )
|
|
{
|
|
$tmp = explode( ':', $data );
|
|
|
|
foreach( $tmp as $t )
|
|
{
|
|
$recurr = explode( '=', $t );
|
|
$rrule_array[ $recurr[0] ] = $recurr[1];
|
|
|
|
//-----------------------------------------
|
|
// Format a timestamp for the 'UNTIL' date
|
|
//-----------------------------------------
|
|
|
|
if ( $recurr[0] == 'UNTIL' )
|
|
{
|
|
$rrule_array['until_ts'] = strtotime( $recurr[1] );
|
|
}
|
|
}
|
|
|
|
return $rrule_array;
|
|
}
|
|
|
|
/**
|
|
* Retrieve stored error message
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getError()
|
|
{
|
|
return $this->_error;
|
|
}
|
|
|
|
/**
|
|
* Import and parse an iCalendar feed
|
|
*
|
|
* @param string $content Feed content
|
|
* @param int $member_id Member to save events under
|
|
* @param int $feed_id Feed id
|
|
* @return mixed False on failure, otherwise an array with keys 'skipped' and 'imported'
|
|
*/
|
|
public function import( $content, $member_id=0, $feed_id=0 )
|
|
{
|
|
//-----------------------------------------
|
|
// Init
|
|
//-----------------------------------------
|
|
|
|
$this->lang->loadLanguageFile( array( 'admin_calendar' ), 'calendar' );
|
|
|
|
//-----------------------------------------
|
|
// Error checking
|
|
//-----------------------------------------
|
|
|
|
if( !$content )
|
|
{
|
|
$this->_error = $this->lang->words['icali_nocontent'];
|
|
return;
|
|
}
|
|
|
|
$_raw = preg_replace( "#(\n\r|\r|\n){1,}#", "\n", $content );
|
|
$_raw = explode( "\n", $_raw );
|
|
|
|
if( !count($_raw) )
|
|
{
|
|
$this->_error = $this->lang->words['icali_nocontent'];
|
|
return;
|
|
}
|
|
|
|
if( $_raw[0] != 'BEGIN:VCALENDAR' )
|
|
{
|
|
$this->_error = $this->lang->words['icali_badcontent'];
|
|
return;
|
|
}
|
|
|
|
$this->_rawIcsData = $_raw;
|
|
|
|
//-----------------------------------------
|
|
// Loop and start parsing
|
|
//-----------------------------------------
|
|
|
|
foreach( $this->_rawIcsData as $k => $v )
|
|
{
|
|
$line = explode( ':', $v );
|
|
|
|
switch( $line[0] )
|
|
{
|
|
case 'BEGIN':
|
|
$this->_parseBeginBlock( $line[1], $k );
|
|
break;
|
|
|
|
/* Unsupported at this time */
|
|
case 'CALSCALE':
|
|
case 'METHOD':
|
|
case 'X-WR-TIMEZONE':
|
|
case 'X-WR-RELCALID':
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Process raw ICS data now
|
|
//-----------------------------------------
|
|
|
|
if( count($this->_parsedIcsData) )
|
|
{
|
|
$this->_parsedIcsData = $this->_convertToGmt( $this->_parsedIcsData );
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// And loop over results to insert
|
|
//-----------------------------------------
|
|
|
|
$_imported = 0;
|
|
$_skipped = 0;
|
|
// Leave this here - useful for debugging
|
|
//print_r($this->_parsedIcsData);exit;
|
|
if ( count( $this->_parsedIcsData ) )
|
|
{
|
|
//-----------------------------------------
|
|
// Get member data
|
|
//-----------------------------------------
|
|
|
|
$_member = IPSMember::load( $member_id );
|
|
|
|
if( !$_member )
|
|
{
|
|
$this->_error = $this->lang->words['icali_nomember'];
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Get like class for notifications
|
|
//-----------------------------------------
|
|
|
|
require_once( IPS_ROOT_PATH . 'sources/classes/like/composite.php' );/*noLibHook*/
|
|
$_like = classes_like::bootstrap( 'calendar', 'calendars' );
|
|
|
|
//-----------------------------------------
|
|
// Loop over the events
|
|
//-----------------------------------------
|
|
|
|
foreach( $this->_parsedIcsData['events'] as $event )
|
|
{
|
|
$event['uid'] = $event['uid'] ? $event['uid'] : md5( implode( ',', $event ) );
|
|
|
|
$event_unix_from = $event['start']['gmt_ts'];
|
|
$event_unix_to = $event['end']['gmt_ts'];
|
|
$event_all_day = ( $event['start']['type'] == 'DATE' ) ? 1 : 0;
|
|
|
|
//-----------------------------------------
|
|
// End dates are "exclusive" in iCalendar format, meaning
|
|
// they are actually the day ahead.
|
|
// @link http://microformats.org/wiki/dtend-issue
|
|
//-----------------------------------------
|
|
|
|
if( $event_unix_to AND $event['end']['type'] == 'DATE' )
|
|
{
|
|
$event_unix_to -= 86400;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// If end date is same as start date, it's a single day event
|
|
//-----------------------------------------
|
|
|
|
if( $event_unix_from == $event_unix_to )
|
|
{
|
|
$event_unix_to = 0;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// If no end date, but we have a duration, calculate end date
|
|
//-----------------------------------------
|
|
|
|
if ( ! $event_unix_to AND $event['duration'] )
|
|
{
|
|
preg_match( "#(\d+?)H#is", $event['duration'], $match );
|
|
$hour = $match[1] ? $match[1] : 0;
|
|
|
|
preg_match( "#(\d+?)M#is", $event['duration'], $match );
|
|
$minute = $match[1] ? $match[1] : 0;
|
|
|
|
preg_match( "#(\d+?)S#is", $event['duration'], $match );
|
|
$second = $match[1] ? $match[1] : 0;
|
|
|
|
$event_unix_to = $event_unix_from + ( $hour * 3600 ) + ( $minute * 60 ) + $second;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Recurring...
|
|
//-----------------------------------------
|
|
|
|
$recurring = 0;
|
|
|
|
if ( isset( $event['recurr']['FREQ'] ) )
|
|
{
|
|
if ( $event['recurr']['FREQ'] == 'MONTHLY' AND $event['recurr']['INTERVAL'] == 12 )
|
|
{
|
|
$event['recurr']['FREQ'] = 'YEARLY';
|
|
}
|
|
|
|
switch( $event['recurr']['FREQ'] )
|
|
{
|
|
case 'WEEKLY':
|
|
$recurring = 1;
|
|
break;
|
|
case 'MONTHLY':
|
|
$recurring = 2;
|
|
break;
|
|
case 'YEARLY':
|
|
$recurring = 3;
|
|
break;
|
|
}
|
|
|
|
$event_unix_to = ( $event['recurr']['until_ts'] ) ? $event['recurr']['until_ts'] : time() + 86400 * 365 * 10;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Adjust timestamps if all day event
|
|
//-----------------------------------------
|
|
|
|
if( $event_all_day )
|
|
{
|
|
$event_unix_from = gmmktime( 0, 0, 0, gmstrftime( '%m', $event_unix_from ), gmstrftime( '%d', $event_unix_from ), gmstrftime( '%Y', $event_unix_from ) );
|
|
$event_unix_to = $event_unix_to ? gmmktime( 0, 0, 0, gmstrftime( '%m', $event_unix_to ), gmstrftime( '%d', $event_unix_to ), gmstrftime( '%Y', $event_unix_to ) ) : 0;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// If we are missing crucial data, skip
|
|
//-----------------------------------------
|
|
|
|
if( !($event['description'] OR $event['summary']) OR !$event_unix_from )
|
|
{
|
|
$_skipped++;
|
|
continue;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Skip previously imported events
|
|
//-----------------------------------------
|
|
|
|
if( $event['uid'] )
|
|
{
|
|
$_check = $this->DB->buildAndFetch( array( 'select' => 'import_id', 'from' => 'cal_import_map', 'where' => "import_guid='" . $this->DB->addSlashes( $event['uid'] ) . "'" ) );
|
|
|
|
if( $_check['import_id'] )
|
|
{
|
|
$_skipped++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Format array for storage
|
|
//-----------------------------------------
|
|
|
|
$_eventData = array(
|
|
'event_calendar_id' => $this->calendar['cal_id'],
|
|
'event_member_id' => $_member['member_id'],
|
|
'event_content' => $event['description'] ? nl2br($event['description']) : $event['summary'],
|
|
'event_title' => $event['summary'] ? $event['summary'] : IPSText::truncate( $event['description'], 100 ),
|
|
'event_title_seo' => IPSText::makeSeoTitle( $event['summary'] ),
|
|
'event_smilies' => 1,
|
|
'event_comments' => 0,
|
|
'event_rsvp' => count($event['attendee']) ? 1 : 0,
|
|
'event_perms' => '*',
|
|
'event_private' => 0,
|
|
'event_approved' => 1,
|
|
'event_saved' => $event['created'] ? $event['created'] : time(),
|
|
'event_lastupdated' => $event['last_modified'] ? $event['last_modified'] : ( $event['created'] ? $event['created'] : time() ),
|
|
'event_recurring' => $recurring,
|
|
'event_start_date' => strftime( "%Y-%m-%d %H:%M:00", $event_unix_from ),
|
|
'event_end_date' => $event_unix_to ? strftime( "%Y-%m-%d %H:%M:00", $event_unix_to ) : 0,
|
|
'event_post_key' => md5( uniqid( microtime(), true ) ),
|
|
'event_sequence' => intval($event['sequence']),
|
|
'event_all_day' => $event_all_day,
|
|
);
|
|
|
|
//-----------------------------------------
|
|
// Data hooks
|
|
//-----------------------------------------
|
|
|
|
IPSLib::doDataHooks( $_eventData, 'calendarAddEvent' );
|
|
|
|
//-----------------------------------------
|
|
// Insert
|
|
//-----------------------------------------
|
|
|
|
$this->DB->insert( 'cal_events', $_eventData );
|
|
|
|
$event_id = $this->DB->getInsertId();
|
|
|
|
$_imported++;
|
|
|
|
//-----------------------------------------
|
|
// Insert mapping
|
|
//-----------------------------------------
|
|
|
|
$this->DB->insert( 'cal_import_map', array(
|
|
'import_feed_id' => $feed_id,
|
|
'import_event_id' => $event_id,
|
|
'import_guid' => $event['uid'],
|
|
) );
|
|
|
|
//-----------------------------------------
|
|
// If we have attendees that are members, insert them
|
|
//-----------------------------------------
|
|
|
|
if( isset($event['attendee']) AND count($event['attendee']) )
|
|
{
|
|
foreach( $event['attendee'] as $attendee )
|
|
{
|
|
if( $attendee['email'] )
|
|
{
|
|
$_loadedMember = IPSMember::load( $attendee['email'] );
|
|
|
|
if( $_loadedMember['member_id'] )
|
|
{
|
|
$this->DB->insert( 'cal_event_rsvp', array(
|
|
'rsvp_member_id' => $_loadedMember['member_id'],
|
|
'rsvp_event_id' => $event_id,
|
|
'rsvp_date' => time(),
|
|
) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Send notifications
|
|
//-----------------------------------------
|
|
|
|
$_url = $this->registry->output->buildSEOUrl( 'app=calendar&module=calendar&section=view&do=showevent&event_id=' . $event_id, 'public', $_eventData['event_title_seo'], 'cal_event' );
|
|
|
|
$_like->sendNotifications( $_eventData['event_calendar_id'], array( 'immediate', 'offline' ),
|
|
array(
|
|
'notification_key' => 'new_event',
|
|
'notification_url' => $_url,
|
|
'email_template' => 'add_event_follow',
|
|
'email_subject' => sprintf( $this->lang->words[ 'add_event_follow_subject'], $_url, $_eventData['event_title'] ),
|
|
'build_message_array' => array(
|
|
'NAME' => '-member:members_display_name-',
|
|
'AUTHOR' => $_member['members_display_name'],
|
|
'TITLE' => $_eventData['event_title'],
|
|
'URL' => $_url,
|
|
)
|
|
) );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Rebuild cache
|
|
//-----------------------------------------
|
|
|
|
$this->cache->rebuildCache( 'calendar_events', 'calendar' );
|
|
|
|
//-----------------------------------------
|
|
// Return
|
|
//-----------------------------------------
|
|
|
|
return array( 'skipped' => $_skipped, 'imported' => $_imported );
|
|
}
|
|
|
|
/**
|
|
* Convert times to GMT based on timezones
|
|
*
|
|
* @param array $data Parsed data
|
|
* @return array
|
|
*/
|
|
protected function _convertToGmt( $data )
|
|
{
|
|
//-----------------------------------------
|
|
// INIT
|
|
//-----------------------------------------
|
|
|
|
$timezones = array();
|
|
|
|
//-----------------------------------------
|
|
// Got any timezones?
|
|
//-----------------------------------------
|
|
|
|
if ( is_array( $data['timezones'] ) AND count( $data['timezones'] ) )
|
|
{
|
|
foreach( $data['timezones'] as $type => $_data )
|
|
{
|
|
$timezones[ $type ] = $_data;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// Fix up events
|
|
//-----------------------------------------
|
|
|
|
if ( is_array( $data['events'] ) AND count( $data['events'] ) )
|
|
{
|
|
foreach( $data['events'] as $id => $event )
|
|
{
|
|
foreach( array( 'start', 'end' ) as $method )
|
|
{
|
|
//-----------------------------------------
|
|
// Set up constraints
|
|
//-----------------------------------------
|
|
|
|
if ( isset( $event[ $method ]['tzid'] ) AND isset( $timezones[ $event[ $method ]['tzid'] ] ) AND is_array( $timezones[ $event[ $method ]['tzid'] ] ) )
|
|
{
|
|
$_standard = ( isset($timezones[ $event[ $method ]['tzid'] ]['standard']['start'] ) ) ? intval( $timezones[ $event[ $method ]['tzid'] ]['standard']['start'] ) : 0;
|
|
$_daylight = ( isset($timezones[ $event[ $method ]['tzid'] ]['daylight']['start'] ) ) ? intval( $timezones[ $event[ $method ]['tzid'] ]['daylight']['start'] ) : 0;
|
|
$_offset = 0;
|
|
|
|
if ( isset( $event[ $method ]['tzid'] ) AND $event[ $method ]['raw_ts'] )
|
|
{
|
|
//if ( $event[ $method ]['raw_ts'] < $_daylight )
|
|
//{
|
|
// $_offset = $timezones[ $event[ $method ]['tzid'] ]['standard']['tz_offset_to'];
|
|
//}
|
|
//else if ( $event[ $method ]['raw_ts'] > $_standard )
|
|
//{
|
|
// $_offset = $timezones[ $event[ $method ]['tzid'] ]['standard']['tz_offset_to'];
|
|
//}
|
|
//else
|
|
//{
|
|
// $_offset = $timezones[ $event[ $method ]['tzid'] ]['daylight']['tz_offset_to'];
|
|
//}
|
|
|
|
$_offset = $timezones[ $event[ $method ]['tzid'] ]['standard']['tz_offset_to'];
|
|
|
|
$event[ $method ]['gmt_ts'] = $event[ $method ]['raw_ts'] - ( $_offset / 100 * 3600 );
|
|
$event[ $method ]['offset'] = $_offset;
|
|
$event[ $method ]['gmt_rfc'] = gmdate( 'r', $event[ $method ]['gmt_ts'] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( isset( $event[ $method ]['raw_ts'] ) )
|
|
{
|
|
$event[ $method ]['gmt_ts'] = $event[ $method ]['raw_ts'];
|
|
$event[ $method ]['offset'] = 0;
|
|
$event[ $method ]['gmt_rfc'] = gmdate( 'r', $event[ $method ]['gmt_ts'] );
|
|
}
|
|
}
|
|
}
|
|
|
|
$data['events'][ $id ] = $event;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Parse a 'BEGIN:' block in an iCalendar feed
|
|
*
|
|
* @param string $type Type of 'BEGIN' object
|
|
* @param int $start Line number
|
|
* @return @e void
|
|
*/
|
|
protected function _parseBeginBlock( $type, $start )
|
|
{
|
|
switch( $type )
|
|
{
|
|
case 'VCALENDAR':
|
|
$this->_begin = 'VCALENDAR';
|
|
$this->_processVcalendarObject( $start + 1 );
|
|
break;
|
|
|
|
case 'VTIMEZONE':
|
|
$this->_begin = 'VTIMEZONE';
|
|
$this->_processTimezoneObject( $start + 1 );
|
|
break;
|
|
|
|
case 'STANDARD':
|
|
if ( $this->_begin == 'VTIMEZONE' )
|
|
{
|
|
$this->_processTimezoneTypeObject( $start + 1, 'STANDARD' );
|
|
}
|
|
break;
|
|
|
|
case 'DAYLIGHT':
|
|
if ( $this->_begin == 'VTIMEZONE' )
|
|
{
|
|
$this->_processTimezoneTypeObject( $start + 1, 'DAYLIGHT' );
|
|
}
|
|
break;
|
|
|
|
case 'VEVENT':
|
|
$this->_begin = 'VEVENT';
|
|
$this->_processEventObject( $start + 1 );
|
|
break;
|
|
|
|
/* Anything else is unsupported at this time */
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse event object in an ical feed
|
|
*
|
|
* @param int $start Line number
|
|
* @return @e void
|
|
* @link http://community.invisionpower.com/resources/bugs.html/_/ip-calendar/recurring-events-can-sometimes-be-skipped-in-ics-r41033
|
|
*/
|
|
protected function _processEventObject( $start )
|
|
{
|
|
//-----------------------------------------
|
|
// Init
|
|
//-----------------------------------------
|
|
|
|
$_break = false;
|
|
$_event = array();
|
|
|
|
//-----------------------------------------
|
|
// Loop over lines
|
|
//-----------------------------------------
|
|
|
|
$_recid = null;
|
|
|
|
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
|
|
{
|
|
//-----------------------------------------
|
|
// Unparse and get the data
|
|
//-----------------------------------------
|
|
|
|
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
|
|
|
|
if ( !$tmp )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$_type = $tmp['type'];
|
|
$_data = $tmp['data'];
|
|
|
|
switch( $_type )
|
|
{
|
|
case 'CLASS':
|
|
$_event['access_class'] = $_data;
|
|
break;
|
|
|
|
case 'CREATED':
|
|
if( !$_event['created'] )
|
|
{
|
|
$_event['created'] = strtotime( $_data );
|
|
}
|
|
break;
|
|
|
|
case 'SUMMARY':
|
|
/* @link http://community.invisionpower.com/tracker/issue-32941-ical-summary/ */
|
|
if( strpos( $_data, 'LANGUAGE=' ) === 0 )
|
|
{
|
|
$_data = preg_replace( "/^LANGUAGE=(.+?):(.+?)$/i", "\\2", $_data );
|
|
}
|
|
|
|
$_event['summary'] = $this->_unencodeSpecialCharacters( $_data );
|
|
break;
|
|
|
|
case 'DESCRIPTION':
|
|
$_event['description'] = $this->_unencodeSpecialCharacters( $_data );
|
|
break;
|
|
|
|
case 'DURATION':
|
|
$_event['duration'] = $_data;
|
|
break;
|
|
|
|
case 'DTSTART':
|
|
$_event['start'] = $this->_unparseTimeInfo( $this->_rawIcsData[$i] );
|
|
break;
|
|
|
|
case 'DTEND':
|
|
$_event['end'] = $this->_unparseTimeInfo( $this->_rawIcsData[$i] );
|
|
break;
|
|
|
|
case 'DTSTAMP':
|
|
$_event['created'] = strtotime( $_data );
|
|
break;
|
|
|
|
case 'LAST-MODIFIED':
|
|
$_event['last_modified'] = strtotime( $_data );
|
|
break;
|
|
|
|
case 'TRANSP':
|
|
$_event['time_transparent'] = $_data;
|
|
break;
|
|
|
|
case 'GEO':
|
|
$_event['geo'] = $_data;
|
|
break;
|
|
|
|
case 'ORGANIZER':
|
|
$line = explode( ':', $_data );
|
|
$_event['organizer'] = array( 'name' => str_replace( 'CN=', '', $line[0] ), 'email' => $line[2] );
|
|
break;
|
|
|
|
case 'ATTENDEE':
|
|
$line = explode( ':', $_data );
|
|
$_email = '';
|
|
|
|
foreach( $line as $_line )
|
|
{
|
|
$_line = str_replace( 'cn=', '', strtolower($_line) );
|
|
|
|
if( IPSText::checkEmailAddress( $_line ) )
|
|
{
|
|
$_email = $_line;
|
|
}
|
|
}
|
|
|
|
$_event['attendee'][] = array( 'name' => str_replace( 'CN=', '', $line[0] ), 'email' => $_email );
|
|
break;
|
|
|
|
case 'UID':
|
|
$_event['uid'] = $_data;
|
|
break;
|
|
|
|
case 'STATUS':
|
|
$_event['status'] = $_data;
|
|
break;
|
|
|
|
case 'LOCATION':
|
|
$_event['location'] = $_data;
|
|
break;
|
|
|
|
case 'SEQUENCE':
|
|
$_event['sequence'] = intval($_data);
|
|
break;
|
|
|
|
case 'RRULE':
|
|
$_event['recurr'] = $_event['recurr'] ? $_event['recurr'] : array();
|
|
$_event['recurr'] = array_merge( $_event['recurr'], $this->_getRecurrenceData( $_data ) );
|
|
break;
|
|
|
|
case 'BEGIN':
|
|
$this->_parseBeginBlock( $_data, $i );
|
|
break;
|
|
|
|
case 'RECURRENCE-ID':
|
|
$_recid = $_data;
|
|
break;
|
|
|
|
case 'END':
|
|
$_break = true;
|
|
break;
|
|
}
|
|
|
|
if( $_break )
|
|
{
|
|
if( $_recid )
|
|
{
|
|
$event['uid'] = md5( $event['uid'] . $_recid );
|
|
}
|
|
|
|
$this->_parsedIcsData['events'][] = $_event;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse core vcalendar object in an ical feed
|
|
*
|
|
* @param int $start Line number
|
|
* @return @e void
|
|
*/
|
|
protected function _processVcalendarObject( $start )
|
|
{
|
|
//-----------------------------------------
|
|
// Loop over lines
|
|
//-----------------------------------------
|
|
|
|
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
|
|
{
|
|
//-----------------------------------------
|
|
// Unparse and get the data
|
|
//-----------------------------------------
|
|
|
|
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
|
|
|
|
if ( !$tmp )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$_type = $tmp['type'];
|
|
$_data = $tmp['data'];
|
|
|
|
switch( $_type )
|
|
{
|
|
case 'PRODID':
|
|
$this->_parsedIcsData['core']['product'] = $_data;
|
|
break;
|
|
|
|
case 'VERSION':
|
|
$this->_parsedIcsData['core']['version'] = $_data;
|
|
break;
|
|
|
|
case 'BEGIN':
|
|
$this->_parseBeginBlock( $_data, $i );
|
|
break;
|
|
|
|
case 'X-WR-CALNAME':
|
|
$this->_parsedIcsData['core']['calendar_name'] = $_data;
|
|
break;
|
|
|
|
case 'END':
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a timezone object in an ical feed
|
|
*
|
|
* @param int $start Line number
|
|
* @param string $type Type of time zone object to parse
|
|
* @return @e void
|
|
*/
|
|
protected function _processTimezoneTypeObject( $start, $type )
|
|
{
|
|
//-----------------------------------------
|
|
// INIT
|
|
//-----------------------------------------
|
|
|
|
$type = strtolower( $type );
|
|
$break = FALSE;
|
|
|
|
//-----------------------------------------
|
|
// Loop over line numbers
|
|
//-----------------------------------------
|
|
|
|
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
|
|
{
|
|
//-----------------------------------------
|
|
// Unparse and get the data
|
|
//-----------------------------------------
|
|
|
|
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
|
|
|
|
if ( !$tmp )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$_type = $tmp['type'];
|
|
$_data = $tmp['data'];
|
|
|
|
switch( $_type )
|
|
{
|
|
case 'DTSTART':
|
|
$this->_parsedIcsData['timezones'][ $this->_tzId ][ $type ]['start'] = strtotime( $_data );
|
|
break;
|
|
|
|
case 'TZOFFSETTO':
|
|
$this->_parsedIcsData['timezones'][ $this->_tzId ][ $type ]['tz_offset_to'] = $_data;
|
|
break;
|
|
|
|
case 'TZOFFSETFROM':
|
|
$this->_parsedIcsData['timezones'][ $this->_tzId ][ $type ]['tz_offset_from'] = $_data;
|
|
break;
|
|
|
|
case 'TZNAME':
|
|
$this->_parsedIcsData['timezones'][ $this->_tzId ][ $type ]['tz_name'] = $_data;
|
|
break;
|
|
|
|
case 'END':
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse a timezone object in an ical feed
|
|
*
|
|
* @param int $start Line number
|
|
* @return @e void
|
|
*/
|
|
protected function _processTimezoneObject( $start )
|
|
{
|
|
//-----------------------------------------
|
|
// Loop over lines
|
|
//-----------------------------------------
|
|
|
|
for( $i = $start, $j = count( $this->_rawIcsData ); $i < $j; $i++ )
|
|
{
|
|
//-----------------------------------------
|
|
// Unparse and get the data
|
|
//-----------------------------------------
|
|
|
|
$tmp = $this->_unparseContent( $this->_rawIcsData[$i], $i );
|
|
|
|
if( !$tmp )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$_type = $tmp['type'];
|
|
$_data = $tmp['data'];
|
|
|
|
switch( $_type )
|
|
{
|
|
case 'TZID':
|
|
$this->_tzId = $_data;
|
|
$this->_parsedIcsData['timezones'][ $_data ] = array();
|
|
break;
|
|
|
|
case 'LAST-MODIFIED':
|
|
$this->_parsedIcsData['timezones'][ $this->_tzId ]['last_modified'] = $_data;
|
|
break;
|
|
|
|
case 'BEGIN':
|
|
$this->_tzBegin = $_data;
|
|
$this->_parseBeginBlock( $_data, $i );
|
|
break;
|
|
|
|
case 'END':
|
|
if ( $this->_tzBegin == $_data )
|
|
{
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} |