1<?php 2 3namespace Sabre\VObject; 4 5/** 6 * FreeBusyData is a helper class that manages freebusy information. 7 * 8 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 9 * @author Evert Pot (http://evertpot.com/) 10 * @license http://sabre.io/license/ Modified BSD License 11 */ 12class FreeBusyData 13{ 14 /** 15 * Start timestamp. 16 * 17 * @var int 18 */ 19 protected $start; 20 21 /** 22 * End timestamp. 23 * 24 * @var int 25 */ 26 protected $end; 27 28 /** 29 * A list of free-busy times. 30 * 31 * @var array 32 */ 33 protected $data; 34 35 public function __construct($start, $end) 36 { 37 $this->start = $start; 38 $this->end = $end; 39 $this->data = []; 40 41 $this->data[] = [ 42 'start' => $this->start, 43 'end' => $this->end, 44 'type' => 'FREE', 45 ]; 46 } 47 48 /** 49 * Adds free or busytime to the data. 50 * 51 * @param int $start 52 * @param int $end 53 * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE 54 */ 55 public function add($start, $end, $type) 56 { 57 if ($start > $this->end || $end < $this->start) { 58 // This new data is outside our timerange. 59 return; 60 } 61 62 if ($start < $this->start) { 63 // The item starts before our requested time range 64 $start = $this->start; 65 } 66 if ($end > $this->end) { 67 // The item ends after our requested time range 68 $end = $this->end; 69 } 70 71 // Finding out where we need to insert the new item. 72 $currentIndex = 0; 73 while ($start > $this->data[$currentIndex]['end']) { 74 ++$currentIndex; 75 } 76 77 // The standard insertion point will be one _after_ the first 78 // overlapping item. 79 $insertStartIndex = $currentIndex + 1; 80 81 $newItem = [ 82 'start' => $start, 83 'end' => $end, 84 'type' => $type, 85 ]; 86 87 $preceedingItem = $this->data[$insertStartIndex - 1]; 88 if ($this->data[$insertStartIndex - 1]['start'] === $start) { 89 // The old item starts at the exact same point as the new item. 90 --$insertStartIndex; 91 } 92 93 // Now we know where to insert the item, we need to know where it 94 // starts overlapping with items on the tail end. We need to start 95 // looking one item before the insertStartIndex, because it's possible 96 // that the new item 'sits inside' the previous old item. 97 if ($insertStartIndex > 0) { 98 $currentIndex = $insertStartIndex - 1; 99 } else { 100 $currentIndex = 0; 101 } 102 103 while ($end > $this->data[$currentIndex]['end']) { 104 ++$currentIndex; 105 } 106 107 // What we are about to insert into the array 108 $newItems = [ 109 $newItem, 110 ]; 111 112 // This is the amount of items that are completely overwritten by the 113 // new item. 114 $itemsToDelete = $currentIndex - $insertStartIndex; 115 if ($this->data[$currentIndex]['end'] <= $end) { 116 ++$itemsToDelete; 117 } 118 119 // If itemsToDelete was -1, it means that the newly inserted item is 120 // actually sitting inside an existing one. This means we need to split 121 // the item at the current position in two and insert the new item in 122 // between. 123 if (-1 === $itemsToDelete) { 124 $itemsToDelete = 0; 125 if ($newItem['end'] < $preceedingItem['end']) { 126 $newItems[] = [ 127 'start' => $newItem['end'] + 1, 128 'end' => $preceedingItem['end'], 129 'type' => $preceedingItem['type'], 130 ]; 131 } 132 } 133 134 array_splice( 135 $this->data, 136 $insertStartIndex, 137 $itemsToDelete, 138 $newItems 139 ); 140 141 $doMerge = false; 142 $mergeOffset = $insertStartIndex; 143 $mergeItem = $newItem; 144 $mergeDelete = 1; 145 146 if (isset($this->data[$insertStartIndex - 1])) { 147 // Updating the start time of the previous item. 148 $this->data[$insertStartIndex - 1]['end'] = $start; 149 150 // If the previous and the current are of the same type, we can 151 // merge them into one item. 152 if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) { 153 $doMerge = true; 154 --$mergeOffset; 155 ++$mergeDelete; 156 $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start']; 157 } 158 } 159 if (isset($this->data[$insertStartIndex + 1])) { 160 // Updating the start time of the next item. 161 $this->data[$insertStartIndex + 1]['start'] = $end; 162 163 // If the next and the current are of the same type, we can 164 // merge them into one item. 165 if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) { 166 $doMerge = true; 167 ++$mergeDelete; 168 $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end']; 169 } 170 } 171 if ($doMerge) { 172 array_splice( 173 $this->data, 174 $mergeOffset, 175 $mergeDelete, 176 [$mergeItem] 177 ); 178 } 179 } 180 181 public function getData() 182 { 183 return $this->data; 184 } 185} 186