diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php index 16a42915..271f331e 100644 --- a/plugins/libcalendaring/libvcalendar.php +++ b/plugins/libcalendaring/libvcalendar.php @@ -42,6 +42,7 @@ class libvcalendar public $method; public $objects = array(); + public $freebusy = array(); /** * Default constructor @@ -147,7 +148,7 @@ class libvcalendar */ public function import_from_vobject($vobject) { - $this->objects = $seen = array(); + $this->objects = $this->freebusy = $seen = array(); if ($vobject->name == 'VCALENDAR') { $this->method = strval($vobject->METHOD); @@ -170,12 +171,31 @@ class libvcalendar $this->objects[] = $object; } } + else if ($ve->name == 'VFREEBUSY') { + $this->_parse_freebusy($ve); + break; + } } } return $this->objects; } + /** + * Getter for free-busy periods + */ + public function get_busy_periods() + { + $out = array(); + foreach ((array)$this->freebusy['periods'] as $period) { + if ($period[2] != 'FREE') { + $out[] = $period; + } + } + + return $out; + } + /** * Convert the given VEvent object to a libkolab compatible array representation * @@ -429,6 +449,60 @@ class libvcalendar return $event; } + /** + * Parse the given vfreebusy component into an array representation + */ + private function _parse_freebusy($ve) + { + $this->freebusy = array('periods' => array()); + + foreach ($ve->children as $prop) { + if (!($prop instanceof VObject\Property)) + continue; + + switch ($prop->name) { + case 'DTSTART': + case 'DTEND': + $propmap = array('DTSTART' => 'start', 'DTEND' => 'end'); + $this->freebusy[$propmap[$prop->name]] = self::convert_datetime($prop); + break; + + case 'ORGANIZER': + $this->freebusy['organizer'] = preg_replace('/^mailto:/i', '', $prop->value); + break; + + case 'FREEBUSY': + // The freebusy component can hold more than 1 value, separated by commas. + $periods = explode(',', $prop->value); + $fbtype = strval($prop['FBTYPE']) ?: 'BUSY'; + + foreach ($periods as $period) { + // Every period is formatted as [start]/[end]. The start is an + // absolute UTC time, the end may be an absolute UTC time, or + // duration (relative) value. + list($busyStart, $busyEnd) = explode('/', $period); + + $busyStart = VObject\DateTimeParser::parse($busyStart); + $busyEnd = VObject\DateTimeParser::parse($busyEnd); + if ($busyEnd instanceof \DateInterval) { + $tmp = clone $busyStart; + $tmp->add($busyEnd); + $busyEnd = $tmp; + } + + if ($busyEnd && $busyEnd > $busyStart) + $this->freebusy['periods'][] = array($busyStart, $busyEnd, $fbtype); + } + break; + + case 'COMMENT': + $this->freebusy['comment'] = $prop->value; + } + } + + return $this->freebusy; + } + /** * Helper method to correctly interpret an all-day date value */ diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php index 3a560b28..bc93bab9 100644 --- a/plugins/libcalendaring/tests/libvcalendar.php +++ b/plugins/libcalendaring/tests/libvcalendar.php @@ -127,6 +127,34 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase $this->assertEquals('confidential', $event['sensitivity'], "Class/sensitivity = confidential"); } + /** + * @depends test_import + */ + function test_freebusy() + { + $ical = new libvcalendar(); + $ical->import_from_file(__DIR__ . '/resources/freebusy.ifb', 'UTF-8'); + $freebusy = $ical->freebusy; + + $this->assertInstanceOf('DateTime', $freebusy['start'], "'start' property is DateTime object"); + $this->assertInstanceOf('DateTime', $freebusy['end'], "'end' property is DateTime object"); + $this->assertEquals(50, count($freebusy['periods']), "Number of freebusy periods defined"); + $this->assertEquals(48, count($ical->get_busy_periods()), "Number of busy periods found"); + } + + /** + * @depends test_import + */ + function test_freebusy_dummy() + { + $ical = new libvcalendar(); + $ical->import_from_file(__DIR__ . '/resources/dummy.ifb', 'UTF-8'); + $freebusy = $ical->freebusy; + + $this->assertEquals(0, count($freebusy['periods']), "Ignore 0-length freebudy periods"); + $this->assertContains('dummy', $freebusy['comment'], "Parse comment"); + } + /** * Test for iCal export from internal hash array representation * diff --git a/plugins/libcalendaring/tests/resources/dummy.ifb b/plugins/libcalendaring/tests/resources/dummy.ifb new file mode 100644 index 00000000..2d5f9ad9 --- /dev/null +++ b/plugins/libcalendaring/tests/resources/dummy.ifb @@ -0,0 +1,13 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//kolab.org//NONSGML Kolab Server 3//EN +METHOD:PUBLISH +BEGIN:VFREEBUSY +ORGANIZER:MAILTO:nobody@kolabsys.com.ifb +DTSTAMP:20130824T135913Z +DTSTART:20130629T135913Z +DTEND:20131214T135913Z +COMMENT:This is a dummy vfreebusy that indicates an empty calendar +FREEBUSY:19700101T000000Z/19700101T000000Z +END:VFREEBUSY +END:VCALENDAR diff --git a/plugins/libcalendaring/tests/resources/freebusy.ifb b/plugins/libcalendaring/tests/resources/freebusy.ifb new file mode 100644 index 00000000..07476b29 --- /dev/null +++ b/plugins/libcalendaring/tests/resources/freebusy.ifb @@ -0,0 +1,61 @@ +BEGIN:VCALENDAR +PRODID:Libkolab-0.4.2 +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VFREEBUSY +CREATED:20130824T140042Z +ORGANIZER:MAILTO:somebody@somedomain.com +DTSTART:20130824T123016Z +DTEND:20131122T140026Z +FREEBUSY;FBTYPE=FREE:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130826T110000Z/20130826T150000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130827T100000Z/20130827T160000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130828T100000Z/20130828T120000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T090000Z/20130830T093000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130830T093000Z/20130830T100000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20130930T070000Z/20130930T160000Z +FREEBUSY:20131104T113000Z/20131104T160000Z +FREEBUSY:20131104T113000Z/20131104T160000Z +FREEBUSY:20131104T113000Z/20131104T160000Z +FREEBUSY:20131104T113000Z/20131104T160000Z +FREEBUSY;FBTYPE=OOF:20131104T113000Z/20131104T160000Z +FREEBUSY;FBTYPE=BUSY-TENTATIVE:20131104T113000Z/20131104T160000Z +FREEBUSY;FBTYPE=FREE:20131104T113000Z/20131104T160000Z +FREEBUSY;FBTYPE=BUSY:20131104T113000Z/20131104T160000Z +END:VFREEBUSY +END:VCALENDAR