3
* Class representing vFreebusy components.
5
* Copyright 2003-2013 Horde LLC (http://www.horde.org/)
7
* See the enclosed file COPYING for license information (LGPL). If you
8
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
10
* @todo Don't use timestamps
12
* @author Mike Cochrane <mike@graftonhall.co.nz>
14
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
17
class Horde_Icalendar_Vfreebusy extends Horde_Icalendar
20
* The component type of this class.
24
public $type = 'vFreebusy';
31
protected $_busyPeriods = array();
38
protected $_extraParams = array();
41
* Parses a string containing vFreebusy data.
43
* @param string $data The data to parse.
45
* @param $charset TODO
47
public function parsevCalendar($data, $type = null, $charset = null)
49
parent::parsevCalendar($data, 'VFREEBUSY', $charset);
51
// Do something with all the busy periods.
52
foreach ($this->_attributes as $key => $attribute) {
53
if ($attribute['name'] != 'FREEBUSY') {
56
foreach ($attribute['values'] as $value) {
57
$params = isset($attribute['params'])
58
? $attribute['params']
60
if (isset($value['duration'])) {
61
$this->addBusyPeriod('BUSY', $value['start'], null,
62
$value['duration'], $params);
64
$this->addBusyPeriod('BUSY', $value['start'],
65
$value['end'], null, $params);
68
unset($this->_attributes[$key]);
73
* Returns the component exported as string.
75
* @return string The exported vFreeBusy information according to the
76
* iCalendar format specification.
78
public function exportvCalendar()
80
foreach ($this->_busyPeriods as $start => $end) {
81
$periods = array(array('start' => $start, 'end' => $end));
82
$this->setAttribute('FREEBUSY', $periods,
83
isset($this->_extraParams[$start])
84
? $this->_extraParams[$start] : array());
87
$res = $this->_exportvData('VFREEBUSY');
89
foreach ($this->_attributes as $key => $attribute) {
90
if ($attribute['name'] == 'FREEBUSY') {
91
unset($this->_attributes[$key]);
99
* Returns a display name for this object.
101
* @return string A clear text name for displaying this object.
103
public function getName()
108
$method = !empty($this->_container)
109
? $this->_container->getAttribute('METHOD')
111
if ($method == 'PUBLISH') {
113
} elseif ($method == 'REPLY') {
116
} catch (Horde_Icalendar_Exception $e) {
121
$name = $this->getAttribute($attr, true);
122
if (isset($name[0]['CN'])) {
123
return $name[0]['CN'];
125
} catch (Horde_Icalendar_Exception $e) {}
128
$name = parse_url($this->getAttribute($attr));
129
return $name['path'];
130
} catch (Horde_Icalendar_Exception $e) {
136
* Returns the email address for this object.
138
* @return string The email address of this object's owner.
140
public function getEmail()
145
$method = !empty($this->_container)
146
? $this->_container->getAttribute('METHOD')
148
if ($method == 'PUBLISH') {
150
} elseif ($method == 'REPLY') {
153
} catch (Horde_Icalendar_Exception $e) {
158
$name = parse_url($this->getAttribute($attr));
159
return $name['path'];
160
} catch (Horde_Icalendar_Exception $e) {
166
* Returns the busy periods.
168
* @return array All busy periods.
170
public function getBusyPeriods()
172
return $this->_busyPeriods;
176
* Returns any additional freebusy parameters.
178
* @return array Additional parameters of the freebusy periods.
180
public function getExtraParams()
182
return $this->_extraParams;
186
* Returns all the free periods of time in a given period.
188
* @param integer $startStamp The start timestamp.
189
* @param integer $endStamp The end timestamp.
191
* @return array A hash with free time periods, the start times as the
192
* keys and the end times as the values.
194
public function getFreePeriods($startStamp, $endStamp)
199
// Check that we have data for some part of this period.
200
if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) {
204
// Locate the first time in the requested period we have data for.
205
$nextstart = max($startStamp, $this->getStart());
207
// Check each busy period and add free periods in between.
208
foreach ($this->_busyPeriods as $start => $end) {
209
if ($start <= $endStamp && $end >= $nextstart) {
210
if ($nextstart <= $start) {
211
$periods[$nextstart] = min($start, $endStamp);
213
$nextstart = min($end, $endStamp);
217
// If we didn't read the end of the requested period but still have
218
// data then mark as free to the end of the period or available data.
219
if ($nextstart < $endStamp && $nextstart < $this->getEnd()) {
220
$periods[$nextstart] = min($this->getEnd(), $endStamp);
227
* Adds a busy period to the info.
229
* This function may throw away data in case you add a period with a start
230
* date that already exists. The longer of the two periods will be chosen
231
* (and all information associated with the shorter one will be removed).
233
* @param string $type The type of the period. Either 'FREE' or
234
* 'BUSY'; only 'BUSY' supported at the moment.
235
* @param integer $start The start timestamp of the period.
236
* @param integer $end The end timestamp of the period.
237
* @param integer $duration The duration of the period. If specified, the
238
* $end parameter will be ignored.
239
* @param array $extra Additional parameters for this busy period.
241
public function addBusyPeriod($type, $start, $end = null, $duration = null,
244
if ($type == 'FREE') {
245
// Make sure this period is not marked as busy.
249
// Calculate the end time if duration was specified.
250
$tempEnd = is_null($duration) ? $end : $start + $duration;
252
// Make sure the period length is always positive.
253
$end = max($start, $tempEnd);
254
$start = min($start, $tempEnd);
256
if (isset($this->_busyPeriods[$start])) {
257
// Already a period starting at this time. Change the current
258
// period only if the new one is longer. This might be a problem
259
// if the callee assumes that there is no simplification going
260
// on. But since the periods are stored using the start time of
261
// the busy periods we have to throw away data here.
262
if ($end > $this->_busyPeriods[$start]) {
263
$this->_busyPeriods[$start] = $end;
264
$this->_extraParams[$start] = $extra;
267
// Add a new busy period.
268
$this->_busyPeriods[$start] = $end;
269
$this->_extraParams[$start] = $extra;
276
* Returns the timestamp of the start of the time period this free busy
277
* information covers.
279
* @return integer A timestamp.
281
public function getStart()
284
return $this->getAttribute('DTSTART');
285
} catch (Horde_Icalendar_Exception $e) {
286
return count($this->_busyPeriods)
287
? min(array_keys($this->_busyPeriods))
293
* Returns the timestamp of the end of the time period this free busy
294
* information covers.
296
* @return integer A timestamp.
298
public function getEnd()
301
return $this->getAttribute('DTEND');
302
} catch (Horde_Icalendar_Exception $e) {
303
return count($this->_busyPeriods)
304
? max(array_values($this->_busyPeriods))
310
* Merges the busy periods of another Horde_Icalendar_Vfreebusy object
313
* This might lead to simplification no matter what you specify for the
314
* "simplify" flag since periods with the same start date will lead to the
315
* shorter period being removed (see addBusyPeriod).
317
* @param Horde_Icalendar_Vfreebusy $freebusy A freebusy object.
318
* @param boolean $simplify If true, simplify() will
319
* called after the merge.
321
public function merge(Horde_Icalendar_Vfreebusy $freebusy,
324
$extra = $freebusy->getExtraParams();
325
foreach ($freebusy->getBusyPeriods() as $start => $end) {
326
// This might simplify the busy periods without taking the
327
// "simplify" flag into account.
328
$this->addBusyPeriod('BUSY', $start, $end, null,
329
isset($extra[$start])
330
? $extra[$start] : array());
333
foreach (array('DTSTART', 'DTEND') as $val) {
335
$thisattr = $this->getAttribute($val);
336
} catch (Horde_Icalendar_Exception $e) {
341
$thatattr = $freebusy->getAttribute($val);
342
} catch (Horde_Icalendar_Exception $e) {
346
if (is_null($thisattr) && !is_null($thatattr)) {
347
$this->setAttribute($val, $thatattr, array(), false);
348
} elseif (!is_null($thatattr)) {
351
$set = ($thatattr < $thisattr);
355
$set = ($thatattr > $thisattr);
360
$this->setAttribute($val, $thatattr, array(), false);
373
* Removes all overlaps and simplifies the busy periods array as much as
376
public function simplify()
379
$busy = array($this->_busyPeriods, $this->_extraParams);
381
$result = $this->_simplify($busy[0], $busy[1]);
382
$clean = $result === $busy;
386
ksort($result[1], SORT_NUMERIC);
387
$this->_extraParams = $result[1];
389
ksort($result[0], SORT_NUMERIC);
390
$this->_busyPeriods = $result[0];
396
* @param $busyPeriods TODO
397
* @param array $extraParams TODO
401
protected function _simplify($busyPeriods, $extraParams = array())
403
$checked = $checkedExtra = array();
404
$checkedEmpty = true;
406
foreach ($busyPeriods as $start => $end) {
408
$checked[$start] = $end;
409
$checkedExtra[$start] = isset($extraParams[$start])
410
? $extraParams[$start]
412
$checkedEmpty = false;
415
foreach ($checked as $testStart => $testEnd) {
416
// Replace old period if the new period lies around the
418
if ($start <= $testStart && $end >= $testEnd) {
419
// Remove old period entry.
420
unset($checked[$testStart]);
421
unset($checkedExtra[$testStart]);
422
// Add replacing entry.
423
$checked[$start] = $end;
424
$checkedExtra[$start] = isset($extraParams[$start])
425
? $extraParams[$start]
428
} elseif ($start >= $testStart && $end <= $testEnd) {
429
// The new period lies fully within the old
430
// period. Just forget about it.
432
} elseif (($end <= $testEnd && $end >= $testStart) ||
433
($start >= $testStart && $start <= $testEnd)) {
434
// Now we are in trouble: Overlapping time periods. If
435
// we allow for additional parameters we cannot simply
436
// choose one of the two parameter sets. It's better
437
// to leave two separated time periods.
438
$extra = isset($extraParams[$start])
439
? $extraParams[$start]
441
$testExtra = isset($checkedExtra[$testStart])
442
? $checkedExtra[$testStart]
444
// Remove old period entry.
445
unset($checked[$testStart]);
446
unset($checkedExtra[$testStart]);
447
// We have two periods overlapping. Are their
448
// additional parameters the same or different?
449
$newStart = min($start, $testStart);
450
$newEnd = max($end, $testEnd);
451
if ($extra === $testExtra) {
452
// Both periods have the same information. So we
454
$checked[$newStart] = $newEnd;
455
$checkedExtra[$newStart] = $extra;
457
// Extra parameters are different. Create one
458
// period at the beginning with the params of the
459
// first period and create a trailing period with
460
// the params of the second period. The break
461
// point will be the end of the first period.
462
$break = min($end, $testEnd);
463
$checked[$newStart] = $break;
464
$checkedExtra[$newStart] =
465
isset($extraParams[$newStart])
466
? $extraParams[$newStart]
468
$checked[$break] = $newEnd;
469
$highStart = max($start, $testStart);
470
$checkedExtra[$break] =
471
isset($extraParams[$highStart])
472
? $extraParams[$highStart]
475
// Ensure we also have the extra data in the
477
$extraParams[$break] =
478
isset($extraParams[$highStart])
479
? $extraParams[$highStart]
491
$checked[$start] = $end;
492
$checkedExtra[$start] = isset($extraParams[$start])
493
? $extraParams[$start]
499
return array($checked, $checkedExtra);