class.freebusypublish.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <?php
  2. /*
  3. * Copyright 2005 - 2016 Zarafa and its licensors
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License, version 3,
  7. * as published by the Free Software Foundation.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Affero General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. */
  18. ?>
  19. <?php
  20. include_once('class.recurrence.php');
  21. class FreeBusyPublish {
  22. var $session;
  23. var $calendar;
  24. var $entryid;
  25. var $starttime;
  26. var $length;
  27. var $store;
  28. var $proptags;
  29. /**
  30. * @param mapi_session $session MAPI Session
  31. * @param mapi_folder $calendar Calendar to publish
  32. * @param string $entryid AddressBook Entry ID for the user we're publishing for
  33. */
  34. function __construct($session, $store, $calendar, $entryid)
  35. {
  36. $properties["entryid"] = PR_ENTRYID;
  37. $properties["parent_entryid"] = PR_PARENT_ENTRYID;
  38. $properties["message_class"] = PR_MESSAGE_CLASS;
  39. $properties["icon_index"] = PR_ICON_INDEX;
  40. $properties["subject"] = PR_SUBJECT;
  41. $properties["display_to"] = PR_DISPLAY_TO;
  42. $properties["importance"] = PR_IMPORTANCE;
  43. $properties["sensitivity"] = PR_SENSITIVITY;
  44. $properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
  45. $properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
  46. $properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
  47. $properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
  48. $properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
  49. $properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
  50. $properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
  51. $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
  52. $properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
  53. $properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
  54. $properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
  55. $properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
  56. $properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
  57. $properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
  58. $properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
  59. $properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
  60. $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
  61. $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
  62. $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
  63. $properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
  64. $properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
  65. $properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
  66. $properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
  67. $properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
  68. $this->proptags = getPropIdsFromStrings($store, $properties);
  69. $this->session = $session;
  70. $this->calendar = $calendar;
  71. $this->entryid = $entryid;
  72. $this->store = $store;
  73. }
  74. /**
  75. * Publishes the infomation
  76. * @paam timestamp $starttime Time from which to publish data (usually now)
  77. * @paam integer $length Amount of seconds from $starttime we should publish
  78. */
  79. function publishFB($starttime, $length) {
  80. $start = $starttime;
  81. $end = $starttime + $length;
  82. // Get all the items in the calendar that we need
  83. $calendaritems = Array();
  84. $restrict = Array(RES_OR,
  85. Array(
  86. // OR
  87. // (item[start] >= start && item[start] <= end)
  88. Array(RES_AND,
  89. Array(
  90. Array(RES_PROPERTY,
  91. Array(RELOP => RELOP_GE,
  92. ULPROPTAG => $this->proptags["startdate"],
  93. VALUE => $start
  94. )
  95. ),
  96. Array(RES_PROPERTY,
  97. Array(RELOP => RELOP_LE,
  98. ULPROPTAG => $this->proptags["startdate"],
  99. VALUE => $end
  100. )
  101. )
  102. )
  103. ),
  104. // OR
  105. // (item[end] >= start && item[end] <= end)
  106. Array(RES_AND,
  107. Array(
  108. Array(RES_PROPERTY,
  109. Array(RELOP => RELOP_GE,
  110. ULPROPTAG => $this->proptags["duedate"],
  111. VALUE => $start
  112. )
  113. ),
  114. Array(RES_PROPERTY,
  115. Array(RELOP => RELOP_LE,
  116. ULPROPTAG => $this->proptags["duedate"],
  117. VALUE => $end
  118. )
  119. )
  120. )
  121. ),
  122. // OR
  123. // (item[start] < start && item[end] > end)
  124. Array(RES_AND,
  125. Array(
  126. Array(RES_PROPERTY,
  127. Array(RELOP => RELOP_LT,
  128. ULPROPTAG => $this->proptags["startdate"],
  129. VALUE => $start
  130. )
  131. ),
  132. Array(RES_PROPERTY,
  133. Array(RELOP => RELOP_GT,
  134. ULPROPTAG => $this->proptags["duedate"],
  135. VALUE => $end
  136. )
  137. )
  138. )
  139. ),
  140. // OR
  141. Array(RES_OR,
  142. Array(
  143. // OR
  144. // (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
  145. Array(RES_AND,
  146. Array(
  147. Array(RES_EXIST,
  148. Array(ULPROPTAG => $this->proptags["enddate_recurring"],
  149. )
  150. ),
  151. Array(RES_PROPERTY,
  152. Array(RELOP => RELOP_EQ,
  153. ULPROPTAG => $this->proptags["recurring"],
  154. VALUE => true
  155. )
  156. ),
  157. Array(RES_PROPERTY,
  158. Array(RELOP => RELOP_GE,
  159. ULPROPTAG => $this->proptags["enddate_recurring"],
  160. VALUE => $start
  161. )
  162. )
  163. )
  164. ),
  165. // OR
  166. // (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
  167. Array(RES_AND,
  168. Array(
  169. Array(RES_NOT,
  170. Array(
  171. Array(RES_EXIST,
  172. Array(ULPROPTAG => $this->proptags["enddate_recurring"]
  173. )
  174. )
  175. )
  176. ),
  177. Array(RES_PROPERTY,
  178. Array(RELOP => RELOP_LE,
  179. ULPROPTAG => $this->proptags["startdate"],
  180. VALUE => $end
  181. )
  182. ),
  183. Array(RES_PROPERTY,
  184. Array(RELOP => RELOP_EQ,
  185. ULPROPTAG => $this->proptags["recurring"],
  186. VALUE => true
  187. )
  188. )
  189. )
  190. )
  191. )
  192. ) // EXISTS OR
  193. )
  194. ); // global OR
  195. $contents = mapi_folder_getcontentstable($this->calendar);
  196. mapi_table_restrict($contents, $restrict);
  197. while(1) {
  198. $rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
  199. if(!is_array($rows))
  200. break;
  201. if(empty($rows))
  202. break;
  203. foreach ($rows as $row) {
  204. $occurrences = Array();
  205. if(isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
  206. $recur = new Recurrence($this->store, $row);
  207. $occurrences = $recur->getItems($starttime, $starttime + $length);
  208. } else {
  209. $occurrences[] = $row;
  210. }
  211. $calendaritems = array_merge($calendaritems, $occurrences);
  212. }
  213. }
  214. // $calendaritems now contains all the calendar items in the specified time
  215. // frame. We now need to merge these into a flat array of begin/end/status
  216. // objects. This also filters out all the 'free' items (status 0)
  217. $freebusy = $this->mergeItemsFB($calendaritems);
  218. // $freebusy now contains the start, end and status of all items, merged.
  219. // Get the FB interface
  220. $fbsupport = false;
  221. try {
  222. $fbsupport = mapi_freebusysupport_open($this->session, $this->store);
  223. } catch (MAPIException $e) {
  224. if ($e->getCode() == MAPI_E_NOT_FOUND)
  225. $e->setHandled();
  226. }
  227. // Open updater for this user
  228. if ($fbsupport !== false) {
  229. $updaters = mapi_freebusysupport_loadupdate($fbsupport, Array($this->entryid));
  230. $updater = $updaters[0];
  231. // Send the data
  232. mapi_freebusyupdate_reset($updater);
  233. mapi_freebusyupdate_publish($updater, $freebusy);
  234. mapi_freebusyupdate_savechanges($updater, $start-24*60*60, $end);
  235. // We're finished
  236. mapi_freebusysupport_close($fbsupport);
  237. }
  238. }
  239. /**
  240. * Sorts by timestamp, if equal, then end before start
  241. */
  242. function cmp($a, $b)
  243. {
  244. if ($a["time"] == $b["time"]) {
  245. if($a["type"] < $b["type"])
  246. return 1;
  247. if($a["type"] > $b["type"])
  248. return -1;
  249. return 0;
  250. }
  251. return ($a["time"] > $b["time"] ? 1 : -1);
  252. }
  253. /**
  254. * Function mergeItems
  255. * @author Steve Hardy
  256. */
  257. function mergeItemsFB($items)
  258. {
  259. $merged = Array();
  260. $timestamps = Array();
  261. $csubj = Array();
  262. $cbusy = Array();
  263. $level = 0;
  264. $laststart = null;
  265. foreach($items as $item)
  266. {
  267. $ts["type"] = 0;
  268. $ts["time"] = $item[$this->proptags["startdate"]];
  269. $ts["subject"] = $item[PR_SUBJECT];
  270. $ts["status"] = $item[$this->proptags["busystatus"]];
  271. $timestamps[] = $ts;
  272. $ts["type"] = 1;
  273. $ts["time"] = $item[$this->proptags["duedate"]];
  274. $ts["subject"] = $item[PR_SUBJECT];
  275. $ts["status"] = $item[$this->proptags["busystatus"]];
  276. $timestamps[] = $ts;
  277. }
  278. usort($timestamps, Array($this, "cmp"));
  279. foreach($timestamps as $ts)
  280. {
  281. switch ($ts["type"])
  282. {
  283. case 0: // Start
  284. if ($level != 0 && $laststart != $ts["time"])
  285. {
  286. $newitem["start"] = $laststart;
  287. $newitem["end"] = $ts["time"];
  288. $newitem["subject"] = join(",", $csubj);
  289. $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
  290. if ($newitem["status"] > 0)
  291. $merged[] = $newitem;
  292. }
  293. $level++;
  294. $csubj[] = $ts["subject"];
  295. $cbusy[] = $ts["status"];
  296. $laststart = $ts["time"];
  297. break;
  298. case 1: // End
  299. if ($laststart != $ts["time"])
  300. {
  301. $newitem["start"] = $laststart;
  302. $newitem["end"] = $ts["time"];
  303. $newitem["subject"] = join(",", $csubj);
  304. $newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
  305. if ($newitem["status"] > 0)
  306. $merged[] = $newitem;
  307. }
  308. $level--;
  309. array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
  310. array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
  311. $laststart = $ts["time"];
  312. break;
  313. }
  314. }
  315. return $merged;
  316. }
  317. }
  318. ?>