Overview

Packages

  • Icalendar

Classes

  • Horde_Icalendar
  • Horde_Icalendar_Daylight
  • Horde_Icalendar_Exception
  • Horde_Icalendar_Standard
  • Horde_Icalendar_Translation
  • Horde_Icalendar_Valarm
  • Horde_Icalendar_Vcard
  • Horde_Icalendar_Vevent
  • Horde_Icalendar_Vfreebusy
  • Horde_Icalendar_Vjournal
  • Horde_Icalendar_Vnote
  • Horde_Icalendar_Vtimezone
  • Horde_Icalendar_Vtodo
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * Class representing vFreebusy components.
  4:  *
  5:  * Copyright 2003-2012 Horde LLC (http://www.horde.org/)
  6:  *
  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.
  9:  *
 10:  * @todo Don't use timestamps
 11:  *
 12:  * @author   Mike Cochrane <mike@graftonhall.co.nz>
 13:  * @category Horde
 14:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 15:  * @package  Icalendar
 16:  */
 17: class Horde_Icalendar_Vfreebusy extends Horde_Icalendar
 18: {
 19:     /**
 20:      * The component type of this class.
 21:      *
 22:      * @var string
 23:      */
 24:     public $type = 'vFreebusy';
 25: 
 26:     /**
 27:      * TODO
 28:      *
 29:      * @var array
 30:      */
 31:     protected $_busyPeriods = array();
 32: 
 33:     /**
 34:      * TODO
 35:      *
 36:      * @var array
 37:      */
 38:     protected $_extraParams = array();
 39: 
 40:     /**
 41:      * Parses a string containing vFreebusy data.
 42:      *
 43:      * @param string $data     The data to parse.
 44:      * @param $type TODO
 45:      * @param $charset TODO
 46:      */
 47:     public function parsevCalendar($data, $type = null, $charset = null)
 48:     {
 49:         parent::parsevCalendar($data, 'VFREEBUSY', $charset);
 50: 
 51:         // Do something with all the busy periods.
 52:         foreach ($this->_attributes as $key => $attribute) {
 53:             if ($attribute['name'] != 'FREEBUSY') {
 54:                 continue;
 55:             }
 56:             foreach ($attribute['values'] as $value) {
 57:                 $params = isset($attribute['params'])
 58:                     ? $attribute['params']
 59:                     : array();
 60:                 if (isset($value['duration'])) {
 61:                     $this->addBusyPeriod('BUSY', $value['start'], null,
 62:                                          $value['duration'], $params);
 63:                 } else {
 64:                     $this->addBusyPeriod('BUSY', $value['start'],
 65:                                          $value['end'], null, $params);
 66:                 }
 67:             }
 68:             unset($this->_attributes[$key]);
 69:         }
 70:     }
 71: 
 72:     /**
 73:      * Returns the component exported as string.
 74:      *
 75:      * @return string  The exported vFreeBusy information according to the
 76:      *                 iCalendar format specification.
 77:      */
 78:     public function exportvCalendar()
 79:     {
 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());
 85:         }
 86: 
 87:         $res = $this->_exportvData('VFREEBUSY');
 88: 
 89:         foreach ($this->_attributes as $key => $attribute) {
 90:             if ($attribute['name'] == 'FREEBUSY') {
 91:                 unset($this->_attributes[$key]);
 92:             }
 93:         }
 94: 
 95:         return $res;
 96:     }
 97: 
 98:     /**
 99:      * Returns a display name for this object.
100:      *
101:      * @return string  A clear text name for displaying this object.
102:      */
103:     public function getName()
104:     {
105:         $name = '';
106: 
107:         try {
108:             $method = !empty($this->_container)
109:                 ? $this->_container->getAttribute('METHOD')
110:                 : 'PUBLISH';
111:             if ($method == 'PUBLISH') {
112:                 $attr = 'ORGANIZER';
113:             } elseif ($method == 'REPLY') {
114:                 $attr = 'ATTENDEE';
115:             }
116:         } catch (Horde_Icalendar_Exception $e) {
117:             $attr = 'ORGANIZER';
118:         }
119: 
120:         try {
121:             $name = $this->getAttribute($attr, true);
122:             if (isset($name[0]['CN'])) {
123:                 return $name[0]['CN'];
124:             }
125:         } catch (Horde_Icalendar_Exception $e) {}
126: 
127:         try {
128:             $name = parse_url($this->getAttribute($attr));
129:             return $name['path'];
130:         } catch (Horde_Icalendar_Exception $e) {
131:             return '';
132:         }
133:     }
134: 
135:     /**
136:      * Returns the email address for this object.
137:      *
138:      * @return string  The email address of this object's owner.
139:      */
140:     public function getEmail()
141:     {
142:         $name = '';
143: 
144:         try {
145:             $method = !empty($this->_container)
146:                 ? $this->_container->getAttribute('METHOD')
147:                 : 'PUBLISH';
148:             if ($method == 'PUBLISH') {
149:                 $attr = 'ORGANIZER';
150:             } elseif ($method == 'REPLY') {
151:                 $attr = 'ATTENDEE';
152:             }
153:         } catch (Horde_Icalendar_Exception $e) {
154:             $attr = 'ORGANIZER';
155:         }
156: 
157:         try {
158:             $name = parse_url($this->getAttribute($attr));
159:             return $name['path'];
160:         } catch (Horde_Icalendar_Exception $e) {
161:             return '';
162:         }
163:     }
164: 
165:     /**
166:      * Returns the busy periods.
167:      *
168:      * @return array  All busy periods.
169:      */
170:     public function getBusyPeriods()
171:     {
172:         return $this->_busyPeriods;
173:     }
174: 
175:     /**
176:      * Returns any additional freebusy parameters.
177:      *
178:      * @return array  Additional parameters of the freebusy periods.
179:      */
180:     public function getExtraParams()
181:     {
182:         return $this->_extraParams;
183:     }
184: 
185:     /**
186:      * Returns all the free periods of time in a given period.
187:      *
188:      * @param integer $startStamp  The start timestamp.
189:      * @param integer $endStamp    The end timestamp.
190:      *
191:      * @return array  A hash with free time periods, the start times as the
192:      *                keys and the end times as the values.
193:      */
194:     public function getFreePeriods($startStamp, $endStamp)
195:     {
196:         $this->simplify();
197:         $periods = array();
198: 
199:         // Check that we have data for some part of this period.
200:         if ($this->getEnd() < $startStamp || $this->getStart() > $endStamp) {
201:             return $periods;
202:         }
203: 
204:         // Locate the first time in the requested period we have data for.
205:         $nextstart = max($startStamp, $this->getStart());
206: 
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);
212:                 }
213:                 $nextstart = min($end, $endStamp);
214:             }
215:         }
216: 
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);
221:         }
222: 
223:         return $periods;
224:     }
225: 
226:     /**
227:      * Adds a busy period to the info.
228:      *
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).
232:      *
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.
240:      */
241:     public function addBusyPeriod($type, $start, $end = null, $duration = null,
242:                                   $extra = array())
243:     {
244:         if ($type == 'FREE') {
245:             // Make sure this period is not marked as busy.
246:             return false;
247:         }
248: 
249:         // Calculate the end time if duration was specified.
250:         $tempEnd = is_null($duration) ? $end : $start + $duration;
251: 
252:         // Make sure the period length is always positive.
253:         $end = max($start, $tempEnd);
254:         $start = min($start, $tempEnd);
255: 
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;
265:             }
266:         } else {
267:             // Add a new busy period.
268:             $this->_busyPeriods[$start] = $end;
269:             $this->_extraParams[$start] = $extra;
270:         }
271: 
272:         return true;
273:     }
274: 
275:     /**
276:      * Returns the timestamp of the start of the time period this free busy
277:      * information covers.
278:      *
279:      * @return integer  A timestamp.
280:      */
281:     public function getStart()
282:     {
283:         try {
284:             return $this->getAttribute('DTSTART');
285:         } catch (Horde_Icalendar_Exception $e) {
286:             return count($this->_busyPeriods)
287:                 ? min(array_keys($this->_busyPeriods))
288:                 : false;
289:         }
290:     }
291: 
292:     /**
293:      * Returns the timestamp of the end of the time period this free busy
294:      * information covers.
295:      *
296:      * @return integer  A timestamp.
297:      */
298:     public function getEnd()
299:     {
300:         try {
301:             return $this->getAttribute('DTEND');
302:         } catch (Horde_Icalendar_Exception $e) {
303:             return count($this->_busyPeriods)
304:                 ? max(array_values($this->_busyPeriods))
305:                 : false;
306:         }
307:     }
308: 
309:     /**
310:      * Merges the busy periods of another Horde_Icalendar_Vfreebusy object
311:      * into this one.
312:      *
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).
316:      *
317:      * @param Horde_Icalendar_Vfreebusy $freebusy  A freebusy object.
318:      * @param boolean $simplify                    If true, simplify() will
319:      *                                             called after the merge.
320:      */
321:     public function merge(Horde_Icalendar_Vfreebusy $freebusy,
322:                           $simplify = true)
323:     {
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());
331:         }
332: 
333:         foreach (array('DTSTART', 'DTEND') as $val) {
334:             try {
335:                 $thisattr = $this->getAttribute($val);
336:             } catch (Horde_Icalendar_Exception $e) {
337:                 $thisattr = null;
338:             }
339: 
340:             try {
341:                 $thatattr = $freebusy->getAttribute($val);
342:             } catch (Horde_Icalendar_Exception $e) {
343:                 $thatattr = null;
344:             }
345: 
346:             if (is_null($thisattr) && !is_null($thatattr)) {
347:                 $this->setAttribute($val, $thatattr, array(), false);
348:             } elseif (!is_null($thatattr)) {
349:                 switch ($val) {
350:                 case 'DTSTART':
351:                     $set = ($thatattr < $thisattr);
352:                     break;
353: 
354:                 case 'DTEND':
355:                     $set = ($thatattr > $thisattr);
356:                     break;
357:                 }
358: 
359:                 if ($set) {
360:                     $this->setAttribute($val, $thatattr, array(), false);
361:                 }
362:             }
363:         }
364: 
365:         if ($simplify) {
366:             $this->simplify();
367:         }
368: 
369:         return true;
370:     }
371: 
372:     /**
373:      * Removes all overlaps and simplifies the busy periods array as much as
374:      * possible.
375:      */
376:     public function simplify()
377:     {
378:         $clean = false;
379:         $busy  = array($this->_busyPeriods, $this->_extraParams);
380:         while (!$clean) {
381:             $result = $this->_simplify($busy[0], $busy[1]);
382:             $clean = $result === $busy;
383:             $busy = $result;
384:         }
385: 
386:         ksort($result[1], SORT_NUMERIC);
387:         $this->_extraParams = $result[1];
388: 
389:         ksort($result[0], SORT_NUMERIC);
390:         $this->_busyPeriods = $result[0];
391:     }
392: 
393:     /**
394:      * TODO
395:      *
396:      * @param $busyPeriods TODO
397:      * @param array $extraParams TODO
398:      *
399:      * @return array TODO
400:      */
401:     protected function _simplify($busyPeriods, $extraParams = array())
402:     {
403:         $checked = $checkedExtra = array();
404:         $checkedEmpty = true;
405: 
406:         foreach ($busyPeriods as $start => $end) {
407:             if ($checkedEmpty) {
408:                 $checked[$start] = $end;
409:                 $checkedExtra[$start] = isset($extraParams[$start])
410:                     ? $extraParams[$start]
411:                     : array();
412:                 $checkedEmpty = false;
413:             } else {
414:                 $added = false;
415:                 foreach ($checked as $testStart => $testEnd) {
416:                     // Replace old period if the new period lies around the
417:                     // old period.
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]
426:                             : array();
427:                         $added = true;
428:                     } elseif ($start >= $testStart && $end <= $testEnd) {
429:                         // The new period lies fully within the old
430:                         // period. Just forget about it.
431:                         $added = true;
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]
440:                             : array();
441:                         $testExtra = isset($checkedExtra[$testStart])
442:                             ? $checkedExtra[$testStart]
443:                             : array();
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
453:                             // can just merge.
454:                             $checked[$newStart] = $newEnd;
455:                             $checkedExtra[$newStart] = $extra;
456:                         } else {
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]
467:                                 : array();
468:                             $checked[$break] = $newEnd;
469:                             $highStart = max($start, $testStart);
470:                             $checkedExtra[$break] =
471:                                 isset($extraParams[$highStart])
472:                                 ? $extraParams[$highStart]
473:                                 : array();
474: 
475:                             // Ensure we also have the extra data in the
476:                             // extraParams.
477:                             $extraParams[$break] =
478:                                 isset($extraParams[$highStart])
479:                                 ? $extraParams[$highStart]
480:                                 : array();
481:                         }
482:                         $added = true;
483:                     }
484: 
485:                     if ($added) {
486:                         break;
487:                     }
488:                 }
489: 
490:                 if (!$added) {
491:                     $checked[$start] = $end;
492:                     $checkedExtra[$start] = isset($extraParams[$start])
493:                         ? $extraParams[$start]
494:                         : array();
495:                 }
496:             }
497:         }
498: 
499:         return array($checked, $checkedExtra);
500:     }
501: 
502: }
503: 
API documentation generated by ApiGen