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 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 /** 50 * Adds free or busytime to the data. 51 * 52 * @param int $start 53 * @param int $end 54 * @param string $type FREE, BUSY, BUSY-UNAVAILABLE or BUSY-TENTATIVE 55 * @return void 56 */ 57 function add($start, $end, $type) { 58 59 if ($start > $this->end || $end < $this->start) { 60 61 // This new data is outside our timerange. 62 return; 63 64 } 65 66 if ($start < $this->start) { 67 // The item starts before our requested time range 68 $start = $this->start; 69 } 70 if ($end > $this->end) { 71 // The item ends after our requested time range 72 $end = $this->end; 73 } 74 75 // Finding out where we need to insert the new item. 76 $currentIndex = 0; 77 while ($start > $this->data[$currentIndex]['end']) { 78 $currentIndex++; 79 } 80 81 // The standard insertion point will be one _after_ the first 82 // overlapping item. 83 $insertStartIndex = $currentIndex + 1; 84 85 $newItem = [ 86 'start' => $start, 87 'end' => $end, 88 'type' => $type, 89 ]; 90 91 $preceedingItem = $this->data[$insertStartIndex - 1]; 92 if ($this->data[$insertStartIndex - 1]['start'] === $start) { 93 // The old item starts at the exact same point as the new item. 94 $insertStartIndex--; 95 } 96 97 // Now we know where to insert the item, we need to know where it 98 // starts overlapping with items on the tail end. We need to start 99 // looking one item before the insertStartIndex, because it's possible 100 // that the new item 'sits inside' the previous old item. 101 if ($insertStartIndex > 0) { 102 $currentIndex = $insertStartIndex - 1; 103 } else { 104 $currentIndex = 0; 105 } 106 107 while ($end > $this->data[$currentIndex]['end']) { 108 109 $currentIndex++; 110 111 } 112 113 // What we are about to insert into the array 114 $newItems = [ 115 $newItem 116 ]; 117 118 // This is the amount of items that are completely overwritten by the 119 // new item. 120 $itemsToDelete = $currentIndex - $insertStartIndex; 121 if ($this->data[$currentIndex]['end'] <= $end) $itemsToDelete++; 122 123 // If itemsToDelete was -1, it means that the newly inserted item is 124 // actually sitting inside an existing one. This means we need to split 125 // the item at the current position in two and insert the new item in 126 // between. 127 if ($itemsToDelete === -1) { 128 $itemsToDelete = 0; 129 if ($newItem['end'] < $preceedingItem['end']) { 130 $newItems[] = [ 131 'start' => $newItem['end'] + 1, 132 'end' => $preceedingItem['end'], 133 'type' => $preceedingItem['type'] 134 ]; 135 } 136 } 137 138 array_splice( 139 $this->data, 140 $insertStartIndex, 141 $itemsToDelete, 142 $newItems 143 ); 144 145 $doMerge = false; 146 $mergeOffset = $insertStartIndex; 147 $mergeItem = $newItem; 148 $mergeDelete = 1; 149 150 if (isset($this->data[$insertStartIndex - 1])) { 151 // Updating the start time of the previous item. 152 $this->data[$insertStartIndex - 1]['end'] = $start; 153 154 // If the previous and the current are of the same type, we can 155 // merge them into one item. 156 if ($this->data[$insertStartIndex - 1]['type'] === $this->data[$insertStartIndex]['type']) { 157 $doMerge = true; 158 $mergeOffset--; 159 $mergeDelete++; 160 $mergeItem['start'] = $this->data[$insertStartIndex - 1]['start']; 161 } 162 } 163 if (isset($this->data[$insertStartIndex + 1])) { 164 // Updating the start time of the next item. 165 $this->data[$insertStartIndex + 1]['start'] = $end; 166 167 // If the next and the current are of the same type, we can 168 // merge them into one item. 169 if ($this->data[$insertStartIndex + 1]['type'] === $this->data[$insertStartIndex]['type']) { 170 $doMerge = true; 171 $mergeDelete++; 172 $mergeItem['end'] = $this->data[$insertStartIndex + 1]['end']; 173 } 174 175 } 176 if ($doMerge) { 177 array_splice( 178 $this->data, 179 $mergeOffset, 180 $mergeDelete, 181 [$mergeItem] 182 ); 183 } 184 185 } 186 187 function getData() { 188 189 return $this->data; 190 191 } 192 193} 194