egroupware_official/phpgwapi/inc/horde/Horde/iCalendar/vfreebusy.php
2009-11-16 08:04:18 +00:00

460 lines
16 KiB
PHP

<?php
/**
* Class representing vFreebusy components.
*
* $Horde: framework/iCalendar/iCalendar/vfreebusy.php,v 1.16.10.17 2008/09/17 08:46:57 jan Exp $
*
* Copyright 2003-2008 The Horde Project (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
*
* @todo Don't use timestamps
*
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @since Horde 3.0
* @package Horde_iCalendar
*/
class Horde_iCalendar_vfreebusy extends Horde_iCalendar {
var $_busyPeriods = array();
var $_extraParams = array();
/**
* Returns the type of this calendar component.
*
* @return string The type of this component.
*/
function getType()
{
return 'vFreebusy';
}
/**
* Parses a string containing vFreebusy data.
*
* @param string $data The data to parse.
*/
function parsevCalendar($data, $type = null, $charset = null)
{
parent::parsevCalendar($data, 'VFREEBUSY', $charset);
// Do something with all the busy periods.
foreach ($this->_attributes as $key => $attribute) {
if ($attribute['name'] != 'FREEBUSY') {
continue;
}
foreach ($attribute['values'] as $value) {
$params = isset($attribute['params'])
? $attribute['params']
: array();
if (isset($value['duration'])) {
$this->addBusyPeriod('BUSY', $value['start'], null,
$value['duration'], $params);
} else {
$this->addBusyPeriod('BUSY', $value['start'],
$value['end'], null, $params);
}
}
unset($this->_attributes[$key]);
}
}
/**
* Returns the component exported as string.
*
* @return string The exported vFreeBusy information according to the
* iCalender format specification.
*/
function exportvCalendar()
{
foreach ($this->_busyPeriods as $start => $end) {
$periods = array(array('start' => $start, 'end' => $end));
$this->setAttribute('FREEBUSY', $periods,
isset($this->_extraParams[$start])
? $this->_extraParams[$start] : array());
}
$res = parent::_exportvData('VFREEBUSY');
foreach ($this->_attributes as $key => $attribute) {
if ($attribute['name'] == 'FREEBUSY') {
unset($this->_attributes[$key]);
}
}
return $res;
}
/**
* Returns a display name for this object.
*
* @return string A clear text name for displaying this object.
*/
function getName()
{
$name = '';
$method = !empty($this->_container) ?
$this->_container->getAttribute('METHOD') : 'PUBLISH';
if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
$attr = 'ORGANIZER';
} elseif ($method == 'REPLY') {
$attr = 'ATTENDEE';
}
$name = $this->getAttribute($attr, true);
if (!is_a($name, 'PEAR_Error') && isset($name[0]['CN'])) {
return $name[0]['CN'];
}
$name = $this->getAttribute($attr);
if (is_a($name, 'PEAR_Error')) {
return '';
} else {
$name = parse_url($name);
return $name['path'];
}
}
/**
* Returns the email address for this object.
*
* @return string The email address of this object's owner.
*/
function getEmail()
{
$name = '';
$method = !empty($this->_container)
? $this->_container->getAttribute('METHOD') : 'PUBLISH';
if (is_a($method, 'PEAR_Error') || $method == 'PUBLISH') {
$attr = 'ORGANIZER';
} elseif ($method == 'REPLY') {
$attr = 'ATTENDEE';
}
$name = $this->getAttribute($attr);
if (is_a($name, 'PEAR_Error')) {
return '';
} else {
$name = parse_url($name);
return $name['path'];
}
}
/**
* Returns the busy periods.
*
* @return array All busy periods.
*/
function getBusyPeriods()
{
return $this->_busyPeriods;
}
/**
* Returns any additional freebusy parameters.
*
* @return array Additional parameters of the freebusy periods.
*/
function getExtraParams()
{
return $this->_extraParams;
}
/**
* Returns all the free periods of time in a given period.
*
* @param integer $startStamp The start timestamp.
* @param integer $endStamp The end timestamp.
*
* @return array A hash with free time periods, the start times as the
* keys and the end times as the values.
*/
function getFreePeriods($startStamp, $endStamp)
{
$this->simplify();
$periods = array();
// Check that we have data for some part of this period.
if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) {
return $periods;
}
// Locate the first time in the requested period we have data for.
$nextstart = max($startStamp, $this->getStart());
// Check each busy period and add free periods in between.
foreach ($this->_busyPeriods as $start => $end) {
if ($start <= $endStamp && $end >= $nextstart) {
if ($nextstart <= $start) {
$periods[$nextstart] = min($start, $endStamp);
}
$nextstart = min($end, $endStamp);
}
}
// If we didn't read the end of the requested period but still have
// data then mark as free to the end of the period or available data.
if ($nextstart < $endStamp && $nextstart < $this->getEnd()) {
$periods[$nextstart] = min($this->getEnd(), $endStamp);
}
return $periods;
}
/**
* Adds a busy period to the info.
*
* This function may throw away data in case you add a period with a start
* date that already exists. The longer of the two periods will be chosen
* (and all information associated with the shorter one will be removed).
*
* @param string $type The type of the period. Either 'FREE' or
* 'BUSY'; only 'BUSY' supported at the moment.
* @param integer $start The start timestamp of the period.
* @param integer $end The end timestamp of the period.
* @param integer $duration The duration of the period. If specified, the
* $end parameter will be ignored.
* @param array $extra Additional parameters for this busy period.
*/
function addBusyPeriod($type, $start, $end = null, $duration = null,
$extra = array())
{
if ($type == 'FREE') {
// Make sure this period is not marked as busy.
return false;
}
// Calculate the end time if duration was specified.
$tempEnd = is_null($duration) ? $end : $start + $duration;
// Make sure the period length is always positive.
$end = max($start, $tempEnd);
$start = min($start, $tempEnd);
if (isset($this->_busyPeriods[$start])) {
// Already a period starting at this time. Change the current
// period only if the new one is longer. This might be a problem
// if the callee assumes that there is no simplification going
// on. But since the periods are stored using the start time of
// the busy periods we have to throw away data here.
if ($end > $this->_busyPeriods[$start]) {
$this->_busyPeriods[$start] = $end;
$this->_extraParams[$start] = $extra;
}
} else {
// Add a new busy period.
$this->_busyPeriods[$start] = $end;
$this->_extraParams[$start] = $extra;
}
return true;
}
/**
* Returns the timestamp of the start of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function getStart()
{
if (!is_a($this->getAttribute('DTSTART'), 'PEAR_Error')) {
return $this->getAttribute('DTSTART');
} elseif (count($this->_busyPeriods)) {
return min(array_keys($this->_busyPeriods));
} else {
return false;
}
}
/**
* Returns the timestamp of the end of the time period this free busy
* information covers.
*
* @return integer A timestamp.
*/
function getEnd()
{
if (!is_a($this->getAttribute('DTEND'), 'PEAR_Error')) {
return $this->getAttribute('DTEND');
} elseif (count($this->_busyPeriods)) {
return max(array_values($this->_busyPeriods));
} else {
return false;
}
}
/**
* Merges the busy periods of another Horde_iCalendar_vfreebusy object
* into this one.
*
* This might lead to simplification no matter what you specify for the
* "simplify" flag since periods with the same start date will lead to the
* shorter period being removed (see addBusyPeriod).
*
* @param Horde_iCalendar_vfreebusy $freebusy A freebusy object.
* @param boolean $simplify If true, simplify() will
* called after the merge.
*/
function merge($freebusy, $simplify = true)
{
if (!is_a($freebusy, 'Horde_iCalendar_vfreebusy')) {
return false;
}
$extra = $freebusy->getExtraParams();
foreach ($freebusy->getBusyPeriods() as $start => $end) {
// This might simplify the busy periods without taking the
// "simplify" flag into account.
$this->addBusyPeriod('BUSY', $start, $end, null,
isset($extra[$start])
? $extra[$start] : array());
}
$thisattr = $this->getAttribute('DTSTART');
$thatattr = $freebusy->getAttribute('DTSTART');
if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
$this->setAttribute('DTSTART', $thatattr, array(), false);
} elseif (!is_a($thatattr, 'PEAR_Error')) {
if ($thatattr < $thisattr) {
$this->setAttribute('DTSTART', $thatattr, array(), false);
}
}
$thisattr = $this->getAttribute('DTEND');
$thatattr = $freebusy->getAttribute('DTEND');
if (is_a($thisattr, 'PEAR_Error') && !is_a($thatattr, 'PEAR_Error')) {
$this->setAttribute('DTEND', $thatattr, array(), false);
} elseif (!is_a($thatattr, 'PEAR_Error')) {
if ($thatattr > $thisattr) {
$this->setAttribute('DTEND', $thatattr, array(), false);
}
}
if ($simplify) {
$this->simplify();
}
return true;
}
/**
* Removes all overlaps and simplifies the busy periods array as much as
* possible.
*/
function simplify()
{
$clean = false;
$busy = array($this->_busyPeriods, $this->_extraParams);
while (!$clean) {
$result = $this->_simplify($busy[0], $busy[1]);
$clean = $result === $busy;
$busy = $result;
}
ksort($result[1], SORT_NUMERIC);
$this->_extraParams = $result[1];
ksort($result[0], SORT_NUMERIC);
$this->_busyPeriods = $result[0];
}
function _simplify($busyPeriods, $extraParams = array())
{
$checked = array();
$checkedExtra = array();
$checkedEmpty = true;
foreach ($busyPeriods as $start => $end) {
if ($checkedEmpty) {
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
$checkedEmpty = false;
} else {
$added = false;
foreach ($checked as $testStart => $testEnd) {
// Replace old period if the new period lies around the
// old period.
if ($start <= $testStart && $end >= $testEnd) {
// Remove old period entry.
unset($checked[$testStart]);
unset($checkedExtra[$testStart]);
// Add replacing entry.
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
$added = true;
} elseif ($start >= $testStart && $end <= $testEnd) {
// The new period lies fully within the old
// period. Just forget about it.
$added = true;
} elseif (($end <= $testEnd && $end >= $testStart) ||
($start >= $testStart && $start <= $testEnd)) {
// Now we are in trouble: Overlapping time periods. If
// we allow for additional parameters we cannot simply
// choose one of the two parameter sets. It's better
// to leave two separated time periods.
$extra = isset($extraParams[$start])
? $extraParams[$start] : array();
$testExtra = isset($checkedExtra[$testStart])
? $checkedExtra[$testStart] : array();
// Remove old period entry.
unset($checked[$testStart]);
unset($checkedExtra[$testStart]);
// We have two periods overlapping. Are their
// additional parameters the same or different?
$newStart = min($start, $testStart);
$newEnd = max($end, $testEnd);
if ($extra === $testExtra) {
// Both periods have the same information. So we
// can just merge.
$checked[$newStart] = $newEnd;
$checkedExtra[$newStart] = $extra;
} else {
// Extra parameters are different. Create one
// period at the beginning with the params of the
// first period and create a trailing period with
// the params of the second period. The break
// point will be the end of the first period.
$break = min($end, $testEnd);
$checked[$newStart] = $break;
$checkedExtra[$newStart] =
isset($extraParams[$newStart])
? $extraParams[$newStart] : array();
$checked[$break] = $newEnd;
$highStart = max($start, $testStart);
$checkedExtra[$break] =
isset($extraParams[$highStart])
? $extraParams[$highStart] : array();
// Ensure we also have the extra data in the
// extraParams.
$extraParams[$break] =
isset($extraParams[$highStart])
? $extraParams[$highStart] : array();
}
$added = true;
}
if ($added) {
break;
}
}
if (!$added) {
$checked[$start] = $end;
$checkedExtra[$start] = isset($extraParams[$start])
? $extraParams[$start] : array();
}
}
}
return array($checked, $checkedExtra);
}
}