4 * This class helps with generating FREEBUSY reports based on existing sets of
7 * It only looks at VEVENT and VFREEBUSY objects from the sourcedata, and
8 * generates a single VFREEBUSY object.
10 * VFREEBUSY components are described in RFC5545, The rules for what should
11 * go in a single freebusy report is taken from RFC4791, section 7.10.
15 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
16 * @author Evert Pot (http://www.rooftopsolutions.nl/)
17 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
19 class Sabre_VObject_FreeBusyGenerator {
45 * @var Sabre_VObject_Component
47 protected $baseObject;
50 * Sets the VCALENDAR object.
52 * If this is set, it will not be generated for you. You are responsible
53 * for setting things like the METHOD, CALSCALE, VERSION, etc..
55 * The VFREEBUSY object will be automatically added though.
57 * @param Sabre_VObject_Component $vcalendar
60 public function setBaseObject(Sabre_VObject_Component $vcalendar) {
62 $this->baseObject = $vcalendar;
67 * Sets the input objects
69 * Every object must either be a string or a Sabre_VObject_Component.
71 * @param array $objects
74 public function setObjects(array $objects) {
76 $this->objects = array();
77 foreach($objects as $object) {
79 if (is_string($object)) {
80 $this->objects[] = Sabre_VObject_Reader::read($object);
81 } elseif ($object instanceof Sabre_VObject_Component) {
82 $this->objects[] = $object;
84 throw new InvalidArgumentException('You can only pass strings or Sabre_VObject_Component arguments to setObjects');
94 * Any freebusy object falling outside of this time range will be ignored.
96 * @param DateTime $start
97 * @param DateTime $end
100 public function setTimeRange(DateTime $start = null, DateTime $end = null) {
102 $this->start = $start;
108 * Parses the input data and returns a correct VFREEBUSY object, wrapped in
111 * @return Sabre_VObject_Component
113 public function getResult() {
115 $busyTimes = array();
117 foreach($this->objects as $object) {
119 foreach($object->getBaseComponents() as $component) {
121 switch($component->name) {
126 if (isset($component->TRANSP) && (strtoupper($component->TRANSP) === 'TRANSPARENT')) {
129 if (isset($component->STATUS)) {
130 $status = strtoupper($component->STATUS);
131 if ($status==='CANCELLED') {
134 if ($status==='TENTATIVE') {
135 $FBTYPE = 'BUSY-TENTATIVE';
141 if ($component->RRULE) {
143 $iterator = new Sabre_VObject_RecurrenceIterator($object, (string)$component->uid);
145 $iterator->fastForward($this->start);
148 $maxRecurrences = 200;
150 while($iterator->valid() && --$maxRecurrences) {
152 $startTime = $iterator->getDTStart();
153 if ($this->end && $startTime > $this->end) {
157 $iterator->getDTStart(),
158 $iterator->getDTEnd(),
167 $startTime = $component->DTSTART->getDateTime();
168 if ($this->end && $startTime > $this->end) {
172 if (isset($component->DTEND)) {
173 $endTime = $component->DTEND->getDateTime();
174 } elseif (isset($component->DURATION)) {
175 $duration = Sabre_VObject_DateTimeParser::parseDuration((string)$component->DURATION);
176 $endTime = clone $startTime;
177 $endTime->add($duration);
178 } elseif ($component->DTSTART->getDateType() === Sabre_VObject_Property_DateTime::DATE) {
179 $endTime = clone $startTime;
180 $endTime->modify('+1 day');
182 // The event had no duration (0 seconds)
186 $times[] = array($startTime, $endTime);
190 foreach($times as $time) {
192 if ($this->end && $time[0] > $this->end) break;
193 if ($this->start && $time[1] < $this->start) break;
195 $busyTimes[] = array(
204 foreach($component->FREEBUSY as $freebusy) {
206 $fbType = isset($freebusy['FBTYPE'])?strtoupper($freebusy['FBTYPE']):'BUSY';
208 // Skipping intervals marked as 'free'
209 if ($fbType==='FREE')
212 $values = explode(',', $freebusy);
213 foreach($values as $value) {
214 list($startTime, $endTime) = explode('/', $value);
215 $startTime = Sabre_VObject_DateTimeParser::parseDateTime($startTime);
217 if (substr($endTime,0,1)==='P' || substr($endTime,0,2)==='-P') {
218 $duration = Sabre_VObject_DateTimeParser::parseDuration($endTime);
219 $endTime = clone $startTime;
220 $endTime->add($duration);
222 $endTime = Sabre_VObject_DateTimeParser::parseDateTime($endTime);
225 if($this->start && $this->start > $endTime) continue;
226 if($this->end && $this->end < $startTime) continue;
227 $busyTimes[] = array(
248 if ($this->baseObject) {
249 $calendar = $this->baseObject;
251 $calendar = new Sabre_VObject_Component('VCALENDAR');
252 $calendar->version = '2.0';
253 if (Sabre_DAV_Server::$exposeVersion) {
254 $calendar->prodid = '-//SabreDAV//Sabre VObject ' . Sabre_VObject_Version::VERSION . '//EN';
256 $calendar->prodid = '-//SabreDAV//Sabre VObject//EN';
258 $calendar->calscale = 'GREGORIAN';
261 $vfreebusy = new Sabre_VObject_Component('VFREEBUSY');
262 $calendar->add($vfreebusy);
265 $dtstart = new Sabre_VObject_Property_DateTime('DTSTART');
266 $dtstart->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC);
267 $vfreebusy->add($dtstart);
270 $dtend = new Sabre_VObject_Property_DateTime('DTEND');
271 $dtend->setDateTime($this->start,Sabre_VObject_Property_DateTime::UTC);
272 $vfreebusy->add($dtend);
274 $dtstamp = new Sabre_VObject_Property_DateTime('DTSTAMP');
275 $dtstamp->setDateTime(new DateTime('now'), Sabre_VObject_Property_DateTime::UTC);
276 $vfreebusy->add($dtstamp);
278 foreach($busyTimes as $busyTime) {
280 $busyTime[0]->setTimeZone(new DateTimeZone('UTC'));
281 $busyTime[1]->setTimeZone(new DateTimeZone('UTC'));
283 $prop = new Sabre_VObject_Property(
285 $busyTime[0]->format('Ymd\\THis\\Z') . '/' . $busyTime[1]->format('Ymd\\THis\\Z')
287 $prop['FBTYPE'] = $busyTime[2];
288 $vfreebusy->add($prop);