AvailabilityCalendarICalFeedsParser.inc in Availability Calendars 7.5
File
AvailabilityCalendarICalFeedsParser.incView source
<?php
/**
* @class ICalendar parser for availability calendars.
*/
class AvailabilityCalendarICalFeedsParser extends FeedsParser {
/**
* Implements FeedsParser::parse().
*
* @param \FeedsSource $source
* @param \FeedsFetcherResult $fetcher_result
*
* @return \FeedsParserResult
*
* @throws \Exception
*/
public function parse(FeedsSource $source, FeedsFetcherResult $fetcher_result) {
$state = $source
->state(FEEDS_PARSE);
// Read the iCal feed into memory.
$ical_feed_contents = $fetcher_result
->getRaw();
$vevents = $this
->parseVcalendar($ical_feed_contents);
// Report progress.
// We need to add 1 to the index of the last parsed component so that
// the subsequent batch starts on the first unparsed component.
$state
->progress($state->total, $state->pointer);
// All the vevents are to be handled as just 1 item with 1 field.
return new FeedsParserResult(array(
array(
'vcalendar' => $vevents,
),
));
}
/**
* Parses a VCALENDAR feed into VEVENTS.
*
* @param string $feed
* The full iCal feed.
*
* @return \stdClass[]
* A list of VEVENT objects.
*
* @throws \Exception
*/
protected function parseVcalendar($feed) {
$vevents = array();
// https://icalendar.org/iCalendar-RFC-5545/3-1-content-lines.html:
// - Content lines are delimited by a line break, which is a CRLF sequence.
// - Long content lines SHOULD be split into a multiple line representations
// {...} by inserting a CRLF immediately followed by a {...} SPACE or
// HTAB. Any {such} sequence {...} is {to be} ignored.
// We accept all line endings:
$feed = str_replace("\r\n", "\n", $feed);
$feed = str_replace("\r", "\n", $feed);
// Join lines that were split.
$feed = str_replace(array(
"\n ",
"\n\t",
), '', $feed);
$lines = explode("\n", $feed);
$i = 0;
$c = count($lines);
while ($i < $c) {
if (strtoupper(trim($lines[$i++])) === 'BEGIN:VEVENT') {
$vevent = $this
->parseVevent($lines, $i);
if (is_object($vevent)) {
$vevents[] = $vevent;
}
}
}
return $vevents;
}
/**
* Parses a VEVENT in a VCALENDAR feed.
*
* @param array $lines
* @param int $i
*
* @return bool|\stdClass
*
* @throws \Exception
*/
protected function parseVevent(array $lines, &$i) {
$vevent = new stdClass();
$c = count($lines);
while ($i < $c) {
$parts = explode(':', $lines[$i++]);
if (count($parts) !== 2) {
return FALSE;
}
$property = strtoupper(trim($parts[0]));
$value = $parts[1];
$propertyParts = explode(';', $property);
if (count($propertyParts) > 1) {
$property = $propertyParts[0];
$propertyType = strtoupper(trim($propertyParts[1]));
}
else {
$propertyType = '';
}
switch ($property) {
case 'UID':
if ($propertyType !== '') {
return FALSE;
}
$vevent->uid = $value;
break;
case 'SUMMARY':
if ($propertyType !== '') {
return FALSE;
}
$vevent->summary = $value;
break;
case 'DTSTART':
if ($propertyType !== 'VALUE=DATE') {
return FALSE;
}
$vevent->start = DateTime::createFromFormat('Ymd', $value)
->setTime(0, 0, 0, 0);
break;
case 'DTEND':
if ($propertyType !== 'VALUE=DATE') {
return FALSE;
}
// A DTEND indicates the departure day, we store a not-available block
// from the arrival date just to the last full day, ie. the day before
// the departure.
$vevent->end = DateTime::createFromFormat('Ymd', $value)
->setTime(0, 0, 0, 0)
->sub(new DateInterval('P1D'));
break;
case 'END':
if ($propertyType !== '') {
return FALSE;
}
if (strtoupper(trim($value)) !== 'VEVENT') {
return FALSE;
}
return !empty($vevent->start) && !empty($vevent->end) ? $vevent : FALSE;
break;
default:
// We ignore other properties.
break;
}
}
return FALSE;
}
}
Classes
Name | Description |
---|---|
AvailabilityCalendarICalFeedsParser | @class ICalendar parser for availability calendars. |