kopia lustrzana https://github.com/friendica/SG-iCalendar
Renamed SG_iCalReader to SG_iCal. Moved the parsing to a separate class.
rodzic
f702b4a79b
commit
4a0982f9fd
|
@ -1,11 +1,9 @@
|
|||
<?php
|
||||
|
||||
define('SG_ICALREADER_VERSION', '0.3');
|
||||
define('SG_ICALREADER_VERSION', '0.5');
|
||||
|
||||
/**
|
||||
* A simple iCalReader. Won't handle all the different fancy-smancy
|
||||
* stuff. What it will do is to parse the END/BEGIN-structures into
|
||||
* a easy to parse/read php-array.
|
||||
* A simple iCal parser. Should take care of most stuff for ya
|
||||
*
|
||||
* Roadmap:
|
||||
* * Finish FREQUENCY-parsing.
|
||||
|
@ -13,7 +11,7 @@ define('SG_ICALREADER_VERSION', '0.3');
|
|||
*
|
||||
* A simple example:
|
||||
* <?php
|
||||
* $ical = new SG_iCalReader("http://example.com/calendar.ics");
|
||||
* $ical = new SG_iCal("http://example.com/calendar.ics");
|
||||
* foreach( $ical->getEvents() As $event ) {
|
||||
* // Do stuff with the event $event
|
||||
* }
|
||||
|
@ -23,59 +21,54 @@ define('SG_ICALREADER_VERSION', '0.3');
|
|||
* @author Morten Fangel (C) 2008
|
||||
* @license http://creativecommons.org/licenses/by-sa/2.5/dk/deed.en_GB CC-BY-SA-DK
|
||||
*/
|
||||
class SG_iCalReader {
|
||||
private $url;
|
||||
private $parsed;
|
||||
private $is_utf8 = true;
|
||||
|
||||
private $parsed_data = array();
|
||||
class SG_iCal {
|
||||
private $information;
|
||||
private $events;
|
||||
private $timezones;
|
||||
|
||||
/**
|
||||
* Constructs a new iCalReader. You can supply the url now, or later using setUrl
|
||||
* @param $url string
|
||||
*/
|
||||
public function __construct($url = '') {
|
||||
$this->url = $url;
|
||||
$this->parsed = false;
|
||||
|
||||
public function __construct($url = false) {
|
||||
require_once dirname(__FILE__) . '/helpers/SG_iCal_Factory.php'; // BUILD: Remove line
|
||||
require_once dirname(__FILE__) . '/helpers/SG_iCal_Line.php'; // BUILD: Remove line
|
||||
require_once dirname(__FILE__) . '/helpers/SG_iCal_Query.php'; // BUILD: Remove line
|
||||
require_once dirname(__FILE__) . '/helpers/SG_iCal_Parser.php'; // BUILD: Remove line
|
||||
|
||||
if( $url !== false ) {
|
||||
SG_iCal_Parser::Parse($url, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets (or resets) the url this reader reads from. If the given
|
||||
* url is empty or the same as the existing it will be ignored.
|
||||
* Otherwise the url will change and the status of the parser will
|
||||
* be reset.
|
||||
*
|
||||
* If you need to time a few passes over parse(), a quick way to
|
||||
* make sure _parse() will get called is to set the url again
|
||||
* and setting $force to true to reset the url even though it even
|
||||
* though the url hasn't changed.
|
||||
*
|
||||
* Sets (or resets) the url this reader reads from.
|
||||
* @param $url string
|
||||
* @param $force bool
|
||||
*/
|
||||
public function setUrl( $url = '', $force = false ) {
|
||||
if( trim($url) != '' && ($url != $this->url || $force) ) {
|
||||
$this->url = $url;
|
||||
$this->parsed = false;
|
||||
$this->is_utf8 = true;
|
||||
public function setUrl( $url = false ) {
|
||||
if( $url !== false ) {
|
||||
SG_iCal_Parser::Parse($url, $this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main calendar info. You can then query the returned
|
||||
* object with ie getTitle().
|
||||
*
|
||||
* @return SG_iCal_VCalendar
|
||||
*/
|
||||
public function getCalendarInfo() {
|
||||
$this->ensureParsed();
|
||||
return $this->parsed_data['vcalendar'];
|
||||
return $this->information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the calendar info for this calendar
|
||||
* @param SG_iCal_VCalendar $info
|
||||
*/
|
||||
public function setCalendarInfo( SG_iCal_VCalendar $info ) {
|
||||
$this->information = $info;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a given timezone for the calendar. This is mainly used
|
||||
* by VEvents to adjust their date-times if they have specified a
|
||||
|
@ -88,11 +81,10 @@ class SG_iCalReader {
|
|||
* @return SG_iCal_VTimeZone
|
||||
*/
|
||||
public function getTimeZoneInfo( $tzid = null ) {
|
||||
$this->ensureParsed();
|
||||
if( $tzid == null ) {
|
||||
return $this->parsed_data['vtimezone'];
|
||||
return $this->timezones;
|
||||
} else {
|
||||
foreach( $this->parsed_data['vtimezone'] AS $tz ) {
|
||||
foreach( $this->timezones AS $tz ) {
|
||||
if( $tz->getTimeZoneId() == $tzid ) {
|
||||
return $tz;
|
||||
}
|
||||
|
@ -101,186 +93,32 @@ class SG_iCalReader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new timezone to this calendar
|
||||
* @param SG_iCal_VTimeZone $tz
|
||||
*/
|
||||
public function addTimeZone( SG_iCal_VTimeZone $tz ) {
|
||||
$this->timezones[] = $tz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the events found
|
||||
* @return array
|
||||
*/
|
||||
public function getEvents() {
|
||||
$this->ensureParsed();
|
||||
return $this->parsed_data['vevent'];
|
||||
return $this->events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the iCal file gets fetched, decoded, unfolded and
|
||||
* parsed.
|
||||
*
|
||||
* Note: It's not neccesary to call parse yourself. If you query
|
||||
* for some of the data, the file will be parsed.
|
||||
* It can't hurt, as a already parsed feed ain't parsed again..
|
||||
*
|
||||
*/
|
||||
public function parse() {
|
||||
if( !$this->parsed ) {
|
||||
$content = $this->fetch();
|
||||
if( !$this->is_utf8 ) {
|
||||
$content = utf8_encode($content);
|
||||
}
|
||||
$content = $this->unfold_lines($content);
|
||||
|
||||
$this->_parse( $content );
|
||||
$this->parsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// The rest are private function
|
||||
|
||||
/**
|
||||
* Ensures that the current url is parsed in the parsed_data member
|
||||
* @throws Exception
|
||||
* @ensure The feed located at $url is parsed and stored in $parsed_data
|
||||
*/
|
||||
private function ensureParsed() {
|
||||
if( !$this->parsed ) {
|
||||
if( $this->url == '' ) {
|
||||
throw new Exception('No url given to iCalReader to parse');
|
||||
}
|
||||
$this->parse();
|
||||
}
|
||||
|
||||
if( empty($this->parsed_data) ) {
|
||||
throw new Exception('Invalid iCal file or parse failure!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches url and stores it in the content member
|
||||
* @return string
|
||||
*/
|
||||
protected function fetch() {
|
||||
$c = curl_init();
|
||||
curl_setopt($c, CURLOPT_URL, $this->url);
|
||||
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
||||
if( !ini_get('safe_mode') ){
|
||||
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
|
||||
}
|
||||
$content = curl_exec($c);
|
||||
|
||||
$ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);
|
||||
$enc = preg_replace('/^.*charset=([-a-zA-Z0-9]+).*$/', '$1', $ct);
|
||||
if( $ct != '' && strtolower(str_replace('-','', $enc)) != 'utf8' ) {
|
||||
// Well, the encoding says it ain't utf-8
|
||||
$this->is_utf8 = false;
|
||||
} elseif( !$this->_valid_utf8( $content ) ) {
|
||||
// The data isn't utf-8
|
||||
$this->is_utf8 = false;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the string $content, and creates a array of iCal lines.
|
||||
* This includes unfolding multi-line entries into a single line.
|
||||
* @param $content string
|
||||
*/
|
||||
private function unfold_lines($content) {
|
||||
$data = array();
|
||||
$content = explode("\n", $content);
|
||||
for( $i=0; $i < count($content); $i++) {
|
||||
$line = rtrim($content[$i]);
|
||||
while( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == ' ' || $content[$i+1]{0} == "\t" )) {
|
||||
$line .= rtrim(substr($content[++$i],1));
|
||||
}
|
||||
$data[] = $line;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the feed found in content and calls storeSection to store
|
||||
* parsed data
|
||||
*/
|
||||
private function _parse( $content ) {
|
||||
$main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
|
||||
$sections = array();
|
||||
$section = '';
|
||||
$current_data = array();
|
||||
|
||||
foreach( $content AS $line ) {
|
||||
$line = new SG_iCal_Line($line);
|
||||
if( $line->isBegin() ) {
|
||||
// New block of data, $section = new block
|
||||
$section = strtolower($line->getData());
|
||||
$sections[] = strtolower($line->getData());
|
||||
} elseif( $line->isEnd() ) {
|
||||
// End of block of data ($removed = just ended block, $section = new top-block)
|
||||
$removed = array_pop($sections);
|
||||
$section = end($sections);
|
||||
|
||||
if( array_search($removed, $main_sections) !== false ) {
|
||||
$this->storeSection( $removed, $current_data[$removed]);
|
||||
$current_data[$removed] = array();
|
||||
}
|
||||
} else {
|
||||
// Data line
|
||||
foreach( $main_sections AS $s ) {
|
||||
// Loops though the main sections
|
||||
|
||||
if( array_search($s, $sections) !== false ) {
|
||||
// This section is in the main section
|
||||
if( $section == $s ) {
|
||||
// It _is_ the main section
|
||||
$current_data[$s][$line->getIdent()] = $line;
|
||||
} else {
|
||||
// Sub section
|
||||
$current_data[$s][$section][$line->getIdent()] = $line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$current_data = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data in the parsed_data member
|
||||
* Adds a event to this calendar
|
||||
* @param SG_iCal_VEvent $event
|
||||
*/
|
||||
private function storeSection( $section, $data ) {
|
||||
$data = SG_iCal_Factory::factory($this, $section, $data);
|
||||
if( $section == 'vcalendar' ) {
|
||||
// We don't want to do parsed_data['vcalendar'][0] to get the data, so it's special..
|
||||
$this->parsed_data['vcalendar'] = $data;
|
||||
} else {
|
||||
$this->parsed_data[$section][] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions does some regexp checking to see if the value is
|
||||
* valid UTF-8.
|
||||
*
|
||||
* The function is from the book "Building Scalable Web Sites" by
|
||||
* Cal Henderson.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function _valid_utf8( $data ) {
|
||||
$rx = '[\xC0-\xDF]([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xE0-\xEF].{0,1}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xF0-\xF7].{0,2}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xF8-\xFB].{0,3}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xFC-\xFD].{0,4}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xFE-\xFE].{0,5}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\x00-\x7F][\x80-\xBF]';
|
||||
$rx .= '|[\xC0-\xDF].[\x80-\xBF]';
|
||||
$rx .= '|[\xE0-\xEF]..[\x80-\xBF]';
|
||||
$rx .= '|[\xF0-\xF7]...[\x80-\xBF]';
|
||||
$rx .= '|[\xF8-\xFB]....[\x80-\xBF]';
|
||||
$rx .= '|[\xFC-\xFD].....[\x80-\xBF]';
|
||||
$rx .= '|[\xFE-\xFE]......[\x80-\xBF]';
|
||||
$rx .= '|^[\x80-\xBF]';
|
||||
|
||||
return ( ! (bool) preg_match('!'.$rx.'!', $data) );
|
||||
public function addEvent( SG_iCal_VEvent $event ) {
|
||||
$this->events[] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For legacy reasons, we keep the name SG_iCalReader..
|
||||
*/
|
||||
class SG_iCalReader extends SG_iCal {}
|
||||
|
|
|
@ -29,7 +29,7 @@ class SG_iCal_VEvent {
|
|||
* @param SG_iCal_Line[] $data
|
||||
* @param SG_iCalReader $ical
|
||||
*/
|
||||
public function __construct($data, SG_iCalReader $ical ) {
|
||||
public function __construct($data, SG_iCal $ical ) {
|
||||
$this->uid = $data['uid']->getData();
|
||||
unset($data['uid']);
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ class SG_iCal_VTimeZone {
|
|||
}
|
||||
|
||||
$daylight_freq = new SG_iCal_Freq($this->daylight['rrule'], strtotime($this->daylight['dtstart']));
|
||||
$last_dst = $daylight_freq->lastOccurance($ts);
|
||||
$last_dst = $daylight_freq->lastOccurrence($ts);
|
||||
if( date('Y') == date('Y', $last_dst) ) {
|
||||
$this->cache[$ts] = 'daylight';
|
||||
} else {
|
||||
|
|
|
@ -23,7 +23,7 @@ class SG_iCal_Factory {
|
|||
* @param $section string
|
||||
* @param SG_iCal_Line[]
|
||||
*/
|
||||
public static function factory( SG_iCalReader $ical, $section, $data ) {
|
||||
public static function factory( SG_iCal $ical, $section, $data ) {
|
||||
switch( $section ) {
|
||||
case "vcalendar":
|
||||
require_once dirname(__FILE__).'/../blocks/SG_iCal_VCalendar.php'; // BUILD: Remove line
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
<?php
|
||||
|
||||
class SG_iCal_Parser {
|
||||
public static function Parse( $url, SG_iCal $ical ) {
|
||||
$content = self::Fetch( $url );
|
||||
$content = self::UnfoldLines($content);
|
||||
self::_Parse( $content, $ical );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches url and stores it in the content member
|
||||
* @return string
|
||||
*/
|
||||
protected static function Fetch( $url ) {
|
||||
$c = curl_init();
|
||||
$is_utf8 = true;
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
||||
if( !ini_get('safe_mode') ){
|
||||
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
|
||||
}
|
||||
$content = curl_exec($c);
|
||||
|
||||
$ct = curl_getinfo($c, CURLINFO_CONTENT_TYPE);
|
||||
$enc = preg_replace('/^.*charset=([-a-zA-Z0-9]+).*$/', '$1', $ct);
|
||||
if( $ct != '' && strtolower(str_replace('-','', $enc)) != 'utf8' ) {
|
||||
// Well, the encoding says it ain't utf-8
|
||||
$is_utf8 = false;
|
||||
} elseif( ! self::_ValidUtf8( $content ) ) {
|
||||
// The data isn't utf-8
|
||||
$is_utf8 = false;
|
||||
}
|
||||
|
||||
if( !$is_utf8 ) {
|
||||
$content = utf8_encode($content);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the string $content, and creates a array of iCal lines.
|
||||
* This includes unfolding multi-line entries into a single line.
|
||||
* @param $content string
|
||||
*/
|
||||
protected static function UnfoldLines($content) {
|
||||
$data = array();
|
||||
$content = explode("\n", $content);
|
||||
for( $i=0; $i < count($content); $i++) {
|
||||
$line = rtrim($content[$i]);
|
||||
while( isset($content[$i+1]) && strlen($content[$i+1]) > 0 && ($content[$i+1]{0} == ' ' || $content[$i+1]{0} == "\t" )) {
|
||||
$line .= rtrim(substr($content[++$i],1));
|
||||
}
|
||||
$data[] = $line;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the feed found in content and calls storeSection to store
|
||||
* parsed data
|
||||
*/
|
||||
private static function _Parse( $content, SG_iCal $ical ) {
|
||||
$main_sections = array('vevent', 'vjournal', 'vtodo', 'vtimezone', 'vcalendar');
|
||||
$sections = array();
|
||||
$section = '';
|
||||
$current_data = array();
|
||||
|
||||
foreach( $content AS $line ) {
|
||||
$line = new SG_iCal_Line($line);
|
||||
if( $line->isBegin() ) {
|
||||
// New block of data, $section = new block
|
||||
$section = strtolower($line->getData());
|
||||
$sections[] = strtolower($line->getData());
|
||||
} elseif( $line->isEnd() ) {
|
||||
// End of block of data ($removed = just ended block, $section = new top-block)
|
||||
$removed = array_pop($sections);
|
||||
$section = end($sections);
|
||||
|
||||
if( array_search($removed, $main_sections) !== false ) {
|
||||
self::StoreSection( $removed, $current_data[$removed], $ical);
|
||||
$current_data[$removed] = array();
|
||||
}
|
||||
} else {
|
||||
// Data line
|
||||
foreach( $main_sections AS $s ) {
|
||||
// Loops though the main sections
|
||||
if( array_search($s, $sections) !== false ) {
|
||||
// This section is in the main section
|
||||
if( $section == $s ) {
|
||||
// It _is_ the main section
|
||||
$current_data[$s][$line->getIdent()] = $line;
|
||||
} else {
|
||||
// Sub section
|
||||
$current_data[$s][$section][$line->getIdent()] = $line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$current_data = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the data in the parsed_data member
|
||||
*/
|
||||
protected static function storeSection( $section, $data, SG_iCal $ical ) {
|
||||
$data = SG_iCal_Factory::Factory($ical, $section, $data);
|
||||
switch( $section ) {
|
||||
case 'vcalendar':
|
||||
return $ical->setCalendarInfo( $data );
|
||||
case 'vevent':
|
||||
return $ical->addEvent( $data );
|
||||
case 'vjournal':
|
||||
case 'vtodo':
|
||||
return true; // TODO: Implement
|
||||
case 'vtimezone':
|
||||
return $ical->addTimeZone( $data );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This functions does some regexp checking to see if the value is
|
||||
* valid UTF-8.
|
||||
*
|
||||
* The function is from the book "Building Scalable Web Sites" by
|
||||
* Cal Henderson.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function _ValidUtf8( $data ) {
|
||||
$rx = '[\xC0-\xDF]([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xE0-\xEF].{0,1}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xF0-\xF7].{0,2}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xF8-\xFB].{0,3}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xFC-\xFD].{0,4}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\xFE-\xFE].{0,5}([^\x80-\xBF]|$)';
|
||||
$rx .= '|[\x00-\x7F][\x80-\xBF]';
|
||||
$rx .= '|[\xC0-\xDF].[\x80-\xBF]';
|
||||
$rx .= '|[\xE0-\xEF]..[\x80-\xBF]';
|
||||
$rx .= '|[\xF0-\xF7]...[\x80-\xBF]';
|
||||
$rx .= '|[\xF8-\xFB]....[\x80-\xBF]';
|
||||
$rx .= '|[\xFC-\xFD].....[\x80-\xBF]';
|
||||
$rx .= '|[\xFE-\xFE]......[\x80-\xBF]';
|
||||
$rx .= '|^[\x80-\xBF]';
|
||||
|
||||
return ( ! (bool) preg_match('!'.$rx.'!', $data) );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +10,7 @@ require_once dirname(__FILE__) . "/../helpers/SG_iCal_Duration.php";
|
|||
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Factory.php";
|
||||
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Freq.php";
|
||||
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Line.php";
|
||||
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Parser.php";
|
||||
require_once dirname(__FILE__) . "/../helpers/SG_iCal_Query.php";
|
||||
|
||||
?>
|
Ładowanie…
Reference in New Issue