mapi.util.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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. /**
  21. * Function to make a MAPIGUID from a php string.
  22. * The C++ definition for the GUID is:
  23. * struct GUID {
  24. * unsigned long Data1;
  25. * unsigned short Data2;
  26. * unsigned short Data3;
  27. * unsigned char Data4[8];
  28. * };
  29. *
  30. * A GUID is normally represented in the following form:
  31. * {00062008-0000-0000-C000-000000000046}
  32. *
  33. * @param String GUID
  34. */
  35. function makeGuid($guid)
  36. {
  37. return pack("vvvv", hexdec(substr($guid, 5, 4)), hexdec(substr($guid, 1, 4)), hexdec(substr($guid, 10, 4)), hexdec(substr($guid, 15, 4))) . hex2bin(substr($guid, 20, 4)) . hex2bin(substr($guid, 25, 12));
  38. }
  39. /**
  40. * Function to get a human readable string from a MAPI error code
  41. *
  42. *@param int $errcode the MAPI error code, if not given, we use mapi_last_hresult
  43. *@return string The defined name for the MAPI error code
  44. */
  45. function get_mapi_error_name($errcode=null)
  46. {
  47. if ($errcode === null){
  48. $errcode = mapi_last_hresult();
  49. }
  50. if ($errcode === 0)
  51. return "NOERROR";
  52. // get_defined_constants(true) is preferred, but crashes PHP
  53. // https://bugs.php.net/bug.php?id=61156
  54. $allConstants = get_defined_constants();
  55. foreach ($allConstants as $key => $value) {
  56. /**
  57. * If PHP encounters a number beyond the bounds of the integer type,
  58. * it will be interpreted as a float instead, so when comparing these error codes
  59. * we have to manually typecast value to integer, so float will be converted in integer,
  60. * but still its out of bound for integer limit so it will be auto adjusted to minus value
  61. */
  62. if ($errcode != (int) $value)
  63. continue;
  64. // Check that we have an actual MAPI error or warning definition
  65. $prefix = substr($key, 0, 7);
  66. if ($prefix == "MAPI_E_" || $prefix == "MAPI_W_")
  67. return $key;
  68. }
  69. // error code not found, return hex value (this is a fix for 64-bit systems, we can't use the dechex() function for this)
  70. $result = unpack("H*", pack("N", $errcode));
  71. return "0x" . $result[1];
  72. }
  73. /**
  74. * Parses properties from an array of strings. Each "string" may be either an ULONG, which is a direct property ID,
  75. * or a string with format "PT_TYPE:{GUID}:StringId" or "PT_TYPE:{GUID}:0xXXXX" for named
  76. * properties.
  77. *
  78. * @returns array of properties
  79. */
  80. function getPropIdsFromStrings($store, $mapping)
  81. {
  82. $props = array();
  83. $ids = array("name"=>array(), "id"=>array(), "guid"=>array(), "type"=>array()); // this array stores all the information needed to retrieve a named property
  84. $num = 0;
  85. // caching
  86. $guids = array();
  87. foreach($mapping as $name=>$val){
  88. if (!is_string($val)) {
  89. // not a named property
  90. $props[$name] = $val;
  91. continue;
  92. }
  93. $split = explode(":", $val);
  94. if (count($split) != 3) { // invalid string, ignore
  95. trigger_error(sprintf("Invalid property: %s \"%s\"", $name, $val), E_USER_NOTICE);
  96. continue;
  97. }
  98. if (substr($split[2], 0, 2) == "0x")
  99. $id = hexdec(substr($split[2], 2));
  100. else
  101. $id = $split[2];
  102. // have we used this guid before?
  103. if (!defined($split[1])) {
  104. if (!array_key_exists($split[1], $guids))
  105. $guids[$split[1]] = makeguid($split[1]);
  106. $guid = $guids[$split[1]];
  107. } else {
  108. $guid = constant($split[1]);
  109. }
  110. // temp store info about named prop, so we have to call mapi_getidsfromnames just one time
  111. $ids["name"][$num] = $name;
  112. $ids["id"][$num] = $id;
  113. $ids["guid"][$num] = $guid;
  114. $ids["type"][$num] = $split[0];
  115. $num++;
  116. }
  117. if (empty($ids["id"]))
  118. return $props;
  119. // get the ids
  120. $named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]);
  121. foreach($named as $num=>$prop)
  122. $props[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop));
  123. return $props;
  124. }
  125. /**
  126. * Check wether a call to mapi_getprops returned errors for some properties.
  127. * mapi_getprops function tries to get values of properties requested but somehow if
  128. * if a property value can not be fetched then it changes type of property tag as PT_ERROR
  129. * and returns error for that particular property, probable errors
  130. * that can be returned as value can be MAPI_E_NOT_FOUND, MAPI_E_NOT_ENOUGH_MEMORY
  131. *
  132. * @param long $property Property to check for error
  133. * @param Array $propArray An array of properties
  134. * @return mixed Gives back false when there is no error, if there is, gives the error
  135. */
  136. function propIsError($property, $propArray)
  137. {
  138. if (array_key_exists(mapi_prop_tag(PT_ERROR, mapi_prop_id($property)), $propArray))
  139. return $propArray[mapi_prop_tag(PT_ERROR, mapi_prop_id($property))];
  140. return false;
  141. }
  142. /******** Macro Functions for PR_DISPLAY_TYPE_EX values *********/
  143. /**
  144. * check addressbook object is a remote mailuser
  145. */
  146. function DTE_IS_REMOTE_VALID($value) {
  147. return !!($value & DTE_FLAG_REMOTE_VALID);
  148. }
  149. /**
  150. * check addressbook object is able to receive permissions
  151. */
  152. function DTE_IS_ACL_CAPABLE($value) {
  153. return !!($value & DTE_FLAG_ACL_CAPABLE);
  154. }
  155. function DTE_REMOTE($value) {
  156. return (($value & DTE_MASK_REMOTE) >> 8);
  157. }
  158. function DTE_LOCAL($value) {
  159. return ($value & DTE_MASK_LOCAL);
  160. }
  161. /**
  162. * Note: Static function, more like a utility function.
  163. *
  164. * Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
  165. * included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
  166. * is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
  167. * [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
  168. *
  169. * @param $store resource The store in which the calendar resides
  170. * @param $calendar resource The calendar to get the items from
  171. * @param $viewstart int Timestamp of beginning of view window
  172. * @param $viewend int Timestamp of end of view window
  173. * @param $propsrequested array Array of properties to return
  174. * @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
  175. * expanded so that it seems that there are only many single appointments in the table.
  176. */
  177. function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested){
  178. $result = array();
  179. $properties = getPropIdsFromStrings($store, Array( "duedate" => "PT_SYSTIME:PSETID_Appointment:0x820e",
  180. "startdate" => "PT_SYSTIME:PSETID_Appointment:0x820d",
  181. "enddate_recurring" => "PT_SYSTIME:PSETID_Appointment:0x8236",
  182. "recurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
  183. "recurring_data" => "PT_BINARY:PSETID_Appointment:0x8216",
  184. "timezone_data" => "PT_BINARY:PSETID_Appointment:0x8233",
  185. "label" => "PT_LONG:PSETID_Appointment:0x8214"
  186. ));
  187. // Create a restriction that will discard rows of appointments that are definitely not in our
  188. // requested time frame
  189. $table = mapi_folder_getcontentstable($calendar);
  190. $restriction =
  191. // OR
  192. Array(RES_OR,
  193. Array(
  194. Array(RES_AND, // Normal items: itemEnd must be after viewStart, itemStart must be before viewEnd
  195. Array(
  196. Array(RES_PROPERTY,
  197. Array(RELOP => RELOP_GT,
  198. ULPROPTAG => $properties["duedate"],
  199. VALUE => $viewstart
  200. )
  201. ),
  202. Array(RES_PROPERTY,
  203. Array(RELOP => RELOP_LT,
  204. ULPROPTAG => $properties["startdate"],
  205. VALUE => $viewend
  206. )
  207. )
  208. )
  209. ),
  210. // OR
  211. Array(RES_PROPERTY,
  212. Array(RELOP => RELOP_EQ,
  213. ULPROPTAG => $properties["recurring"],
  214. VALUE => true
  215. )
  216. )
  217. ) // EXISTS OR
  218. ); // global OR
  219. // Get requested properties, plus whatever we need
  220. $proplist = array(PR_ENTRYID, $properties["recurring"], $properties["recurring_data"], $properties["timezone_data"]);
  221. $proplist = array_merge($proplist, $propsrequested);
  222. $propslist = array_unique($proplist);
  223. $rows = mapi_table_queryallrows($table, $proplist, $restriction);
  224. // $rows now contains all the items that MAY be in the window; a recurring item needs expansion before including in the output.
  225. foreach($rows as $row) {
  226. $items = array();
  227. if(isset($row[$properties["recurring"]]) && $row[$properties["recurring"]]) {
  228. // Recurring item
  229. $rec = new Recurrence($store, $row);
  230. // GetItems guarantees that the item overlaps the interval <$viewstart, $viewend>
  231. $occurrences = $rec->getItems($viewstart, $viewend);
  232. foreach($occurrences as $occurrence) {
  233. // The occurrence takes all properties from the main row, but overrides some properties (like start and end obviously)
  234. $item = $occurrence + $row;
  235. array_push($items, $item);
  236. }
  237. } else {
  238. // Normal item, it matched the search criteria and therefore overlaps the interval <$viewstart, $viewend>
  239. array_push($items, $row);
  240. }
  241. $result = array_merge($result,$items);
  242. }
  243. // All items are guaranteed to overlap the interval <$viewstart, $viewend>. Note that we may be returning a few extra
  244. // properties that the caller did not request (recurring, etc). This shouldn't be a problem though.
  245. return $result;
  246. }
  247. /**
  248. * Get the main calendar
  249. */
  250. function getCalendar($store)
  251. {
  252. $inbox = mapi_msgstore_getreceivefolder($store);
  253. $inboxprops = mapi_getprops($inbox, array(PR_IPM_APPOINTMENT_ENTRYID));
  254. if(!isset($inboxprops[PR_IPM_APPOINTMENT_ENTRYID]))
  255. return false;
  256. $calendar = mapi_msgstore_openentry($store, $inboxprops[PR_IPM_APPOINTMENT_ENTRYID]);
  257. return $calendar;
  258. }
  259. /**
  260. * Get the default store for this session
  261. */
  262. function getDefaultStore($session)
  263. {
  264. $msgstorestable = mapi_getmsgstorestable($session);
  265. $msgstores = mapi_table_queryallrows($msgstorestable, array(PR_DEFAULT_STORE, PR_ENTRYID));
  266. foreach ($msgstores as $row) {
  267. if($row[PR_DEFAULT_STORE]) {
  268. $storeentryid = $row[PR_ENTRYID];
  269. }
  270. }
  271. if(!$storeentryid) {
  272. print "Can't find default store\n";
  273. return false;
  274. }
  275. $store = mapi_openmsgstore($session, $storeentryid);
  276. return $store;
  277. }
  278. function forceUTF8($category)
  279. {
  280. $old_locale = setlocale($category, "");
  281. if(!isset($old_locale) || !$old_locale) {
  282. print "Unable to initialize locale\n";
  283. exit(1);
  284. }
  285. $dot = strpos($old_locale, ".");
  286. if($dot) {
  287. if(strrchr($old_locale, ".") == ".UTF-8" || strrchr($old_locale, ".") == ".utf8")
  288. return true;
  289. $old_locale = substr($old_locale, 0, $dot);
  290. }
  291. $new_locale = $old_locale . ".UTF-8";
  292. $old_locale = setlocale($category, $new_locale);
  293. if(!$old_locale) {
  294. $new_locale = "en_US.UTF-8";
  295. $old_locale = setlocale($category, $new_locale);
  296. }
  297. if(!$old_locale) {
  298. print "Unable to set locale $new_locale\n";
  299. exit(1);
  300. }
  301. return true;
  302. }
  303. ?>