PageRenderTime 38ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/ZarafaBridge.php

http://sabre-zarafa.googlecode.com/
PHP | 692 lines | 428 code | 99 blank | 165 comment | 42 complexity | b62151c77abd6f58a480b4e55fc79e2f MD5 | raw file
  1. <?php
  2. /*
  3. * Copyright 2011 - 2012 Guillaume Lapierre
  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. * "Zarafa" is a registered trademark of Zarafa B.V.
  10. *
  11. * This software use SabreDAV, an open source software distributed
  12. * with New BSD License. Please see <http://code.google.com/p/sabredav/>
  13. * for more information about SabreDAV
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  22. *
  23. * Project page: <http://code.google.com/p/sabre-zarafa/>
  24. *
  25. */
  26. // Load config and common
  27. include (BASE_PATH . "config.inc.php");
  28. include (BASE_PATH . "version.inc.php");
  29. include (BASE_PATH . "common.inc.php");
  30. // Logging
  31. include_once ("log4php/Logger.php");
  32. Logger::configure("log4php.xml");
  33. // PHP-MAPI
  34. require_once("mapi/mapi.util.php");
  35. require_once("mapi/mapicode.php");
  36. require_once("mapi/mapidefs.php");
  37. require_once("mapi/mapitags.php");
  38. require_once("mapi/mapiguid.php");
  39. // VObject for vcard
  40. include_once "Sabre/VObject/includes.php";
  41. // VObject to mapi properties
  42. require_once "vcard/IVCardParser.php"; // too many vcard formats :(
  43. include_once "vcard/VCardParser2.php";
  44. include_once "vcard/VCardParser3.php";
  45. include_once "vcard/VCardParser4.php";
  46. require_once "vcard/IVCardProducer.php";
  47. include_once "vcard/VCardProducer.php";
  48. /**
  49. * This is main class for Sabre backends
  50. */
  51. class Zarafa_Bridge {
  52. protected $session;
  53. protected $store;
  54. protected $rootFolder;
  55. protected $rootFolderId;
  56. protected $extendedProperties;
  57. protected $connectedUser;
  58. protected $adressBooks;
  59. private $logger;
  60. /**
  61. * Constructor
  62. */
  63. public function __construct() {
  64. // Stores a reference to Zarafa Auth Backend so as to get the session
  65. $this->logger = Logger::getLogger(__CLASS__);
  66. }
  67. /**
  68. * Connect to Zarafa and do some init
  69. * @param $user user login
  70. * @param $password user password
  71. */
  72. public function connect($user, $password) {
  73. $this->logger->debug("connect($user," . md5($password) . ")");
  74. $this->session = NULL;
  75. try {
  76. $session = mapi_logon_zarafa($user, $password, ZARAFA_SERVER);
  77. } catch (Exception $e) {
  78. $this->logger->debug("connection failed: " . get_mapi_error_name());
  79. return false;
  80. }
  81. if ($session === FALSE) {
  82. // Failed
  83. return false;
  84. }
  85. $this->logger->trace("Connected to zarafa server - init bridge");
  86. $this->session = $session;
  87. // Find user store
  88. $storesTable = mapi_getmsgstorestable($session);
  89. $stores = mapi_table_queryallrows($storesTable, array(PR_ENTRYID, PR_MDB_PROVIDER));
  90. for($i = 0; $i < count($stores); $i++){
  91. if ($stores[$i][PR_MDB_PROVIDER] == ZARAFA_SERVICE_GUID) {
  92. $storeEntryid = $stores[$i][PR_ENTRYID];
  93. break;
  94. }
  95. }
  96. if (!isset($storeEntryid)) {
  97. trigger_error("Default store not found", E_USER_ERROR);
  98. }
  99. $this->store = mapi_openmsgstore($this->session, $storeEntryid);
  100. $root = mapi_msgstore_openentry($this->store, null);
  101. $rootProps = mapi_getprops($root, array(PR_IPM_CONTACT_ENTRYID));
  102. // Store rootfolder
  103. $this->rootFolder = mapi_msgstore_openentry($this->store, $rootProps[PR_IPM_CONTACT_ENTRYID]);
  104. $this->rootFolderId = $rootProps[PR_IPM_CONTACT_ENTRYID];
  105. // Check for unicode
  106. $this->isUnicodeStore($this->store);
  107. // Load properties
  108. $this->initProperties();
  109. // Store username for principals
  110. $this->connectedUser = $user;
  111. // Set protected variable to NULL.
  112. $this->adressBooks = NULL;
  113. return true;
  114. }
  115. /**
  116. * Get MAPI session
  117. * @return MAPI session
  118. */
  119. public function getMapiSession() {
  120. $this->logger->trace("getMapiSession");
  121. return $this->session;
  122. }
  123. /**
  124. * Get user store
  125. * @return user store
  126. */
  127. public function getStore() {
  128. $this->logger->trace("getStore");
  129. return $this->store;
  130. }
  131. /**
  132. * Get root folder
  133. * @return root folder
  134. */
  135. public function getRootFolder() {
  136. $this->logger->trace("getRootFolder");
  137. return $this->rootFolder;
  138. }
  139. /**
  140. * Get connected user login
  141. * @return connected user
  142. */
  143. public function getConnectedUser() {
  144. $this->logger->trace("getConnectedUser");
  145. return $this->connectedUser;
  146. }
  147. public function getExtendedProperties() {
  148. $this->logger->trace("getExtendedProperties");
  149. return $this->extendedProperties;
  150. }
  151. /**
  152. * Get connected user email address
  153. * @return email address
  154. */
  155. public function getConnectedUserMailAddress() {
  156. $this->logger->trace("getConnectedUserMailAddress");
  157. $userInfo = mapi_zarafa_getuser_by_name($this->store, $this->connectedUser);
  158. $this->logger->debug("User email address: " . $userInfo["emailaddress"]);
  159. return $userInfo["emailaddress"];
  160. }
  161. /**
  162. * Get list of addressbooks
  163. */
  164. public function getAdressBooks() {
  165. $this->logger->trace("getAdressBooks");
  166. if ($this->adressBooks === NULL) {
  167. $this->logger->debug("Building list of address books");
  168. $this->adressBooks = array();
  169. $this->buildAdressBooks('', $this->rootFolder, $this->rootFolderId);
  170. }
  171. return $this->adressBooks;
  172. }
  173. /**
  174. * Build user list of adress books
  175. * Recursively find folders in Zarafa
  176. */
  177. private function buildAdressBooks($prefix, $folder, $parentFolderId) {
  178. $this->logger->trace("buildAdressBooks");
  179. $folderProperties = mapi_getprops($folder);
  180. $currentFolderName = $this->to_charset($folderProperties[PR_DISPLAY_NAME]);
  181. // Compute CTag - issue 8: ctag should be the max of PR_LAST_MODIFICATION_TIME of contacts
  182. // of the folder.
  183. $this->logger->trace("Computing CTag for address book " . $folderProperties[PR_DISPLAY_NAME]);
  184. $ctag = $folderProperties[PR_LAST_MODIFICATION_TIME];
  185. $contactsTable = mapi_folder_getcontentstable($folder);
  186. $contacts = mapi_table_queryallrows($contactsTable, array(PR_LAST_MODIFICATION_TIME));
  187. // Contact count
  188. $contactCount = mapi_table_getrowcount($contactsTable);
  189. $storedContactCount = isset($folderProperties[PR_CARDDAV_AB_CONTACT_COUNT]) ? $folderProperties[PR_CARDDAV_AB_CONTACT_COUNT] : 0;
  190. $this->logger->trace("Contact count: $contactCount");
  191. $this->logger->trace("Stored contact count: $storedContactCount");
  192. if ($contactCount <> $storedContactCount) {
  193. $this->logger->trace("Contact count != stored contact count");
  194. $ctag = time();
  195. mapi_setprops($folder, array(PR_CARDDAV_AB_CONTACT_COUNT => $contactCount, PR_LAST_MODIFICATION_TIME => $ctag));
  196. mapi_savechanges($folder);
  197. } else {
  198. foreach ($contacts as $c) {
  199. if ($c[PR_LAST_MODIFICATION_TIME] > $ctag) {
  200. $ctag = $c[PR_LAST_MODIFICATION_TIME];
  201. $this->logger->trace("Found new ctag: $ctag");
  202. }
  203. }
  204. }
  205. // Add address book
  206. $this->adressBooks[$folderProperties[PR_ENTRYID]] = array(
  207. 'id' => $folderProperties[PR_ENTRYID],
  208. 'displayname' => $folderProperties[PR_DISPLAY_NAME],
  209. 'prefix' => $prefix,
  210. 'description' => (isset($folderProperties[805568542]) ? $folderProperties[805568542] : ''),
  211. 'ctag' => $ctag,
  212. 'parentId' => $parentFolderId
  213. );
  214. // Get subfolders
  215. $foldersTable = mapi_folder_gethierarchytable ($folder);
  216. $folders = mapi_table_queryallrows($foldersTable);
  217. foreach ($folders as $f) {
  218. $subFold = mapi_msgstore_openentry($this->store, $f[PR_ENTRYID]);
  219. $this->buildAdressBooks ($prefix . $currentFolderName . "/", $subFold, $folderProperties[PR_ENTRYID]);
  220. }
  221. }
  222. /**
  223. * Get properties from mapi
  224. * @param $entryId
  225. */
  226. public function getProperties($entryId) {
  227. $this->logger->trace("getProperties(" . bin2hex($entryId) . ")");
  228. $mapiObject = mapi_msgstore_openentry($this->store, $entryId);
  229. $props = mapi_getprops($mapiObject);
  230. return $props;
  231. }
  232. /**
  233. * Convert an entryId to a human readable string
  234. */
  235. public function entryIdToStr($entryId) {
  236. return bin2hex($entryId);
  237. }
  238. /**
  239. * Convert a human readable string to an entryid
  240. */
  241. public function strToEntryId($str) {
  242. // Check if $str is a valid Zarafa entryID. If not returns 0
  243. if (!preg_match('/^[0-9a-zA-Z]*$/', $str)) {
  244. return 0;
  245. }
  246. return pack("H*", $str);
  247. }
  248. /**
  249. * Convert vcard data to an array of MAPI properties
  250. * @param $vcardData
  251. * @return array
  252. */
  253. public function vcardToMapiProperties($vcardData) {
  254. $this->logger->trace("vcardToMapiProperties");
  255. $this->logger->debug("VCARD:\n" . $vcardData);
  256. $vObject = Sabre_VObject_Reader::read($vcardData);
  257. // Extract version to call the correct parser
  258. $version = $vObject->version->value;
  259. $majorVersion = substr($version, 0, 1);
  260. $objectClass = "VCardParser$majorVersion";
  261. $this->logger->debug("Using $objectClass to parse vcard data");
  262. $parser = new $objectClass($this);
  263. $properties = array();
  264. $parser->vObjectToProperties($vObject, $properties);
  265. $dump = '';
  266. ob_start();
  267. print_r ($properties);
  268. $dump = ob_get_contents();
  269. ob_end_clean();
  270. $this->logger->debug("VCard properties:\n" . $dump);
  271. return $properties;
  272. }
  273. /**
  274. * Retrieve vCard for a contact. If need be will "build" the vCard data
  275. * @see RFC6350 http://tools.ietf.org/html/rfc6350
  276. * @param $contactId contact EntryID
  277. * @return VCard 4 UTF-8 encoded content
  278. */
  279. public function getContactVCard($contactId) {
  280. $this->logger->trace("getContactVCard(" . bin2hex($contactId) . ")");
  281. $contact = mapi_msgstore_openentry($this->store, $contactId);
  282. $contactProperties = $this->getProperties($contactId);
  283. $p = $this->extendedProperties;
  284. $this->logger->trace("PR_CARDDAV_RAW_DATA: " . PR_CARDDAV_RAW_DATA);
  285. $this->logger->trace("PR_CARDDAV_RAW_DATA_GENERATION_TIME: " . PR_CARDDAV_RAW_DATA_GENERATION_TIME);
  286. $this->logger->trace("PR_CARDDAV_RAW_DATA_VERSION: " . PR_CARDDAV_RAW_DATA_VERSION);
  287. $this->logger->debug("CACHE VERSION: " . CACHE_VERSION);
  288. // dump properties
  289. $dump = print_r($contactProperties, true);
  290. $this->logger->trace("Contact properties:\n$dump");
  291. if (SAVE_RAW_VCARD && isset($contactProperties[PR_CARDDAV_RAW_DATA])) {
  292. // Check if raw vCard is up-to-date
  293. $vcardGenerationTime = $contactProperties[PR_CARDDAV_RAW_DATA_GENERATION_TIME];
  294. $lastModifiedDate = $contactProperties[$p['last_modification_time']];
  295. // Get cache version
  296. $vcardCacheVersion = isset($contactProperties[PR_CARDDAV_RAW_DATA_VERSION]) ? $contactProperties[PR_CARDDAV_RAW_DATA_VERSION] : 'NONE';
  297. $this->logger->trace("Saved vcard cache version: " . $vcardCacheVersion);
  298. if (($vcardGenerationTime >= $lastModifiedDate) && ($vcardCacheVersion == CACHE_VERSION)) {
  299. $this->logger->debug("Using saved vcard");
  300. return $contactProperties[PR_CARDDAV_RAW_DATA];
  301. } else {
  302. $this->logger->trace("Contact modified or new version of Sabre-Zarafa");
  303. }
  304. } else {
  305. if (SAVE_RAW_VCARD) {
  306. $this->logger->trace("No saved raw vcard");
  307. } else {
  308. $this->logger->trace("Generation of vcards forced by config");
  309. }
  310. }
  311. $producer = new VCardProducer($this, VCARD_VERSION);
  312. $vCard = new Sabre_VObject_Component('VCARD');
  313. // Produce VCard object
  314. $this->logger->trace("Producing vcard from contact properties");
  315. $producer->propertiesToVObject($contact, $vCard);
  316. // Serialize
  317. $vCardData = $vCard->serialize();
  318. $this->logger->debug("Produced VCard\n" . $vCardData);
  319. // Charset conversion?
  320. $targetCharset = (VCARD_CHARSET == '') ? $producer->getDefaultCharset() : VCARD_CHARSET;
  321. if ($targetCharset != 'utf-8') {
  322. $this->logger->debug("Converting from UTF-8 to $targetCharset");
  323. $vCardData = iconv("UTF-8", $targetCharset, $vCardData);
  324. }
  325. if (SAVE_RAW_VCARD) {
  326. $this->logger->debug("Saving vcard to contact properties");
  327. // Check if raw vCard is up-to-date
  328. mapi_setprops($contact, array(
  329. PR_CARDDAV_RAW_DATA => $vCardData,
  330. PR_CARDDAV_RAW_DATA_VERSION => CACHE_VERSION,
  331. PR_CARDDAV_RAW_DATA_GENERATION_TIME => time()
  332. ));
  333. if (mapi_last_hresult() > 0) {
  334. $this->logger->warn("Error setting contact properties: " . get_mapi_error_name());
  335. }
  336. mapi_savechanges($contact);
  337. if (mapi_last_hresult() > 0) {
  338. $this->logger->warn("Error saving vcard to contact: " . get_mapi_error_name());
  339. } else {
  340. $this->logger->trace("VCard successfully added to contact properties");
  341. }
  342. }
  343. return $vCardData;
  344. }
  345. /**
  346. * Init properties to read contact data
  347. */
  348. protected function initProperties() {
  349. $this->logger->trace("initProperties");
  350. $properties = array();
  351. $properties["subject"] = PR_SUBJECT;
  352. $properties["icon_index"] = PR_ICON_INDEX;
  353. $properties["message_class"] = PR_MESSAGE_CLASS;
  354. $properties["display_name"] = PR_DISPLAY_NAME;
  355. $properties["given_name"] = PR_GIVEN_NAME;
  356. $properties["middle_name"] = PR_MIDDLE_NAME;
  357. $properties["surname"] = PR_SURNAME;
  358. $properties["home_telephone_number"] = PR_HOME_TELEPHONE_NUMBER;
  359. $properties["cellular_telephone_number"] = PR_CELLULAR_TELEPHONE_NUMBER;
  360. $properties["office_telephone_number"] = PR_OFFICE_TELEPHONE_NUMBER;
  361. $properties["business_fax_number"] = PR_BUSINESS_FAX_NUMBER;
  362. $properties["company_name"] = PR_COMPANY_NAME;
  363. $properties["title"] = PR_TITLE;
  364. $properties["department_name"] = PR_DEPARTMENT_NAME;
  365. $properties["office_location"] = PR_OFFICE_LOCATION;
  366. $properties["profession"] = PR_PROFESSION;
  367. $properties["manager_name"] = PR_MANAGER_NAME;
  368. $properties["assistant"] = PR_ASSISTANT;
  369. $properties["nickname"] = PR_NICKNAME;
  370. $properties["display_name_prefix"] = PR_DISPLAY_NAME_PREFIX;
  371. $properties["spouse_name"] = PR_SPOUSE_NAME;
  372. $properties["generation"] = PR_GENERATION;
  373. $properties["birthday"] = PR_BIRTHDAY;
  374. $properties["wedding_anniversary"] = PR_WEDDING_ANNIVERSARY;
  375. $properties["sensitivity"] = PR_SENSITIVITY;
  376. $properties["fileas"] = "PT_STRING8:PSETID_Address:0x8005";
  377. $properties["fileas_selection"] = "PT_LONG:PSETID_Address:0x8006";
  378. $properties["email_address_1"] = "PT_STRING8:PSETID_Address:0x8083";
  379. $properties["email_address_display_name_1"] = "PT_STRING8:PSETID_Address:0x8080";
  380. $properties["email_address_display_name_email_1"] = "PT_STRING8:PSETID_Address:0x8084";
  381. $properties["email_address_type_1"] = "PT_STRING8:PSETID_Address:0x8082";
  382. $properties["email_address_2"] = "PT_STRING8:PSETID_Address:0x8093";
  383. $properties["email_address_display_name_2"] = "PT_STRING8:PSETID_Address:0x8090";
  384. $properties["email_address_display_name_email_2"] = "PT_STRING8:PSETID_Address:0x8094";
  385. $properties["email_address_type_2"] = "PT_STRING8:PSETID_Address:0x8092";
  386. $properties["email_address_3"] = "PT_STRING8:PSETID_Address:0x80a3";
  387. $properties["email_address_display_name_3"] = "PT_STRING8:PSETID_Address:0x80a0";
  388. $properties["email_address_display_name_email_3"] = "PT_STRING8:PSETID_Address:0x80a4";
  389. $properties["email_address_type_3"] = "PT_STRING8:PSETID_Address:0x80a2";
  390. $properties["home_address"] = "PT_STRING8:PSETID_Address:0x801a";
  391. $properties["business_address"] = "PT_STRING8:PSETID_Address:0x801b";
  392. $properties["other_address"] = "PT_STRING8:PSETID_Address:0x801c";
  393. $properties["mailing_address"] = "PT_LONG:PSETID_Address:0x8022";
  394. $properties["im"] = "PT_STRING8:PSETID_Address:0x8062";
  395. $properties["webpage"] = "PT_STRING8:PSETID_Address:0x802b";
  396. $properties["business_home_page"] = PR_BUSINESS_HOME_PAGE;
  397. $properties["email_address_entryid_1"] = "PT_BINARY:PSETID_Address:0x8085";
  398. $properties["email_address_entryid_2"] = "PT_BINARY:PSETID_Address:0x8095";
  399. $properties["email_address_entryid_3"] = "PT_BINARY:PSETID_Address:0x80a5";
  400. $properties["address_book_mv"] = "PT_MV_LONG:PSETID_Address:0x8028";
  401. $properties["address_book_long"] = "PT_LONG:PSETID_Address:0x8029";
  402. $properties["oneoff_members"] = "PT_MV_BINARY:PSETID_Address:0x8054";
  403. $properties["members"] = "PT_MV_BINARY:PSETID_Address:0x8055";
  404. $properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
  405. $properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
  406. $properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
  407. $properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
  408. $properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
  409. // Detailed contacts properties
  410. // Properties for phone numbers
  411. $properties["assistant_telephone_number"] = PR_ASSISTANT_TELEPHONE_NUMBER;
  412. $properties["business2_telephone_number"] = PR_BUSINESS2_TELEPHONE_NUMBER;
  413. $properties["callback_telephone_number"] = PR_CALLBACK_TELEPHONE_NUMBER;
  414. $properties["car_telephone_number"] = PR_CAR_TELEPHONE_NUMBER;
  415. $properties["company_telephone_number"] = PR_COMPANY_MAIN_PHONE_NUMBER;
  416. $properties["home2_telephone_number"] = PR_HOME2_TELEPHONE_NUMBER;
  417. $properties["home_fax_number"] = PR_HOME_FAX_NUMBER;
  418. $properties["isdn_number"] = PR_ISDN_NUMBER;
  419. $properties["other_telephone_number"] = PR_OTHER_TELEPHONE_NUMBER;
  420. $properties["pager_telephone_number"] = PR_PAGER_TELEPHONE_NUMBER;
  421. $properties["primary_fax_number"] = PR_PRIMARY_FAX_NUMBER;
  422. $properties["primary_telephone_number"] = PR_PRIMARY_TELEPHONE_NUMBER;
  423. $properties["radio_telephone_number"] = PR_RADIO_TELEPHONE_NUMBER;
  424. $properties["telex_telephone_number"] = PR_TELEX_NUMBER;
  425. $properties["ttytdd_telephone_number"] = PR_TTYTDD_PHONE_NUMBER;
  426. // Additional fax properties
  427. $properties["fax_1_address_type"] = "PT_STRING8:PSETID_Address:0x80B2";
  428. $properties["fax_1_email_address"] = "PT_STRING8:PSETID_Address:0x80B3";
  429. $properties["fax_1_original_display_name"] = "PT_STRING8:PSETID_Address:0x80B4";
  430. $properties["fax_1_original_entryid"] = "PT_BINARY:PSETID_Address:0x80B5";
  431. $properties["fax_2_address_type"] = "PT_STRING8:PSETID_Address:0x80C2";
  432. $properties["fax_2_email_address"] = "PT_STRING8:PSETID_Address:0x80C3";
  433. $properties["fax_2_original_display_name"] = "PT_STRING8:PSETID_Address:0x80C4";
  434. $properties["fax_2_original_entryid"] = "PT_BINARY:PSETID_Address:0x80C5";
  435. $properties["fax_3_address_type"] = "PT_STRING8:PSETID_Address:0x80D2";
  436. $properties["fax_3_email_address"] = "PT_STRING8:PSETID_Address:0x80D3";
  437. $properties["fax_3_original_display_name"] = "PT_STRING8:PSETID_Address:0x80D4";
  438. $properties["fax_3_original_entryid"] = "PT_BINARY:PSETID_Address:0x80D5";
  439. // Properties for addresses
  440. // Home address
  441. $properties["home_address_street"] = PR_HOME_ADDRESS_STREET;
  442. $properties["home_address_city"] = PR_HOME_ADDRESS_CITY;
  443. $properties["home_address_state"] = PR_HOME_ADDRESS_STATE_OR_PROVINCE;
  444. $properties["home_address_postal_code"] = PR_HOME_ADDRESS_POSTAL_CODE;
  445. $properties["home_address_country"] = PR_HOME_ADDRESS_COUNTRY;
  446. // Other address
  447. $properties["other_address_street"] = PR_OTHER_ADDRESS_STREET;
  448. $properties["other_address_city"] = PR_OTHER_ADDRESS_CITY;
  449. $properties["other_address_state"] = PR_OTHER_ADDRESS_STATE_OR_PROVINCE;
  450. $properties["other_address_postal_code"] = PR_OTHER_ADDRESS_POSTAL_CODE;
  451. $properties["other_address_country"] = PR_OTHER_ADDRESS_COUNTRY;
  452. // Business address
  453. $properties["business_address_street"] = "PT_STRING8:PSETID_Address:0x8045";
  454. $properties["business_address_city"] = "PT_STRING8:PSETID_Address:0x8046";
  455. $properties["business_address_state"] = "PT_STRING8:PSETID_Address:0x8047";
  456. $properties["business_address_postal_code"] = "PT_STRING8:PSETID_Address:0x8048";
  457. $properties["business_address_country"] = "PT_STRING8:PSETID_Address:0x8049";
  458. // Mailing address
  459. $properties["country"] = PR_COUNTRY;
  460. $properties["city"] = PR_LOCALITY;
  461. $properties["postal_address"] = PR_POSTAL_ADDRESS;
  462. $properties["postal_code"] = PR_POSTAL_CODE;
  463. $properties["state"] = PR_STATE_OR_PROVINCE;
  464. $properties["street"] = PR_STREET_ADDRESS;
  465. // Special Date such as birthday n anniversary appoitment's entryid is store
  466. $properties["birthday_eventid"] = "PT_BINARY:PSETID_Address:0x804D";
  467. $properties["anniversary_eventid"] = "PT_BINARY:PSETID_Address:0x804E";
  468. $properties["notes"] = PR_BODY;
  469. // Has contact picture
  470. $properties["has_picture"] = "PT_BOOLEAN:{00062004-0000-0000-C000-000000000046}:0x8015";
  471. // Custom properties needed for carddav functionnality
  472. $properties["carddav_uri"] = PR_CARDDAV_URI;
  473. $properties["carddav_rawdata"] = PR_CARDDAV_RAW_DATA;
  474. $properties["carddav_generation_time"] = PR_CARDDAV_RAW_DATA_GENERATION_TIME;
  475. $properties["contact_count"] = PR_CARDDAV_AB_CONTACT_COUNT;
  476. $properties["carddav_version"] = PR_CARDDAV_RAW_DATA_VERSION;
  477. // Ask Mapi to load those properties and store mapping.
  478. $this->extendedProperties = getPropIdsFromStrings($this->store, $properties);
  479. // Dump properties to debug
  480. $dump = print_r ($this->extendedProperties, true);
  481. $this->logger->trace("Properties init done:\n$dump");
  482. }
  483. /**
  484. * Generate a GUID using random numbers (version 4)
  485. * GUID are 128 bits long numbers
  486. * returns string version {8-4-4-4-12}
  487. * Use uuid_create if php5-uuid extension is available
  488. */
  489. public function generateRandomGuid() {
  490. $this->logger->trace("generateRandomGuid");
  491. /*
  492. if (function_exists('uuid_create')) {
  493. // Not yet tested :)
  494. $this->logger->debug("Using uuid_create");
  495. uuid_create($context);
  496. uuid_make($context, UUID_MAKE_V4);
  497. uuid_export($context, UUID_FMT_STR, $uuid);
  498. return trim($uuid);
  499. }
  500. */
  501. $data1a = mt_rand(0, 0xFFFF); // 32 bits - splited
  502. $data1b = mt_rand(0, 0xFFFF);
  503. $data2 = mt_rand(0, 0xFFFF); // 16 bits
  504. $data3 = mt_rand(0, 0xFFF); // 12 bits (last 4 bits is version generator)
  505. // data4 is 64 bits long
  506. $data4a = mt_rand(0, 0xFFFF);
  507. $data4b = mt_rand(0, 0xFFFF);
  508. $data4c = mt_rand(0, 0xFFFF);
  509. $data4d = mt_rand(0, 0xFFFF);
  510. // Force variant 4 + standard for this GUID
  511. $data4a = ($data4a | 0x8000) & 0xBFFF; // standard
  512. return sprintf("%04x%04x-%04x-%03x4-%04x-%04x%04x%04x", $data1a, $data1b, $data2, $data3, $data4a, $data4b, $data4c, $data4d);
  513. }
  514. /**
  515. * Check if store supports UTF-8 (zarafa 7+)
  516. * @param $store
  517. */
  518. public function isUnicodeStore($store) {
  519. $this->logger->trace("Testing store for unicode");
  520. $supportmask = mapi_getprops($store, array(PR_STORE_SUPPORT_MASK));
  521. if (isset($supportmask[PR_STORE_SUPPORT_MASK]) && ($supportmask[PR_STORE_SUPPORT_MASK] & STORE_UNICODE_OK)) {
  522. define('STORE_SUPPORTS_UNICODE', true);
  523. //setlocale to UTF-8 in order to support properties containing Unicode characters
  524. setlocale(LC_CTYPE, "en_US.UTF-8");
  525. }
  526. }
  527. /**
  528. * Assign a contact picture to a contact
  529. * @param entryId contact entry id
  530. * @param contactPicture must be a valid jpeg file. If contactPicture is NULL will remove contact picture from contact if exists
  531. */
  532. public function setContactPicture(&$contact, $contactPicture) {
  533. $this->logger->trace("setContactPicture");
  534. // Find if contact picture is already set
  535. $contactAttachment = -1;
  536. $hasattachProp = mapi_getprops($contact, array(PR_HASATTACH));
  537. if ($hasattachProp) {
  538. $attachmentTable = mapi_message_getattachmenttable($contact);
  539. $attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD, PR_ATTACH_CONTENT_ID, PR_ATTACH_MIME_TAG, PR_ATTACHMENT_CONTACTPHOTO, PR_EC_WA_ATTACHMENT_HIDDEN_OVERRIDE));
  540. foreach ($attachments as $attachmentRow) {
  541. if (isset($attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) && $attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) {
  542. $contactAttachment = $attachmentRow[PR_ATTACH_NUM];
  543. break;
  544. }
  545. }
  546. }
  547. // Remove existing attachment if necessary
  548. if ($contactAttachment != -1) {
  549. $this->logger->trace("removing existing contact picture");
  550. $attach = mapi_message_deleteattach($contact, $contactAttachment);
  551. }
  552. if ($contactPicture !== NULL) {
  553. $this->logger->debug("Saving contact picture as attachment");
  554. // Create attachment
  555. $attach = mapi_message_createattach($contact);
  556. // Update contact attachment properties
  557. $properties = array(
  558. PR_ATTACH_SIZE => strlen($contactPicture),
  559. PR_ATTACH_LONG_FILENAME => 'ContactPicture.jpg',
  560. PR_ATTACHMENT_HIDDEN => false,
  561. PR_DISPLAY_NAME => 'ContactPicture.jpg',
  562. PR_ATTACH_METHOD => ATTACH_BY_VALUE,
  563. PR_ATTACH_MIME_TAG => 'image/jpeg',
  564. PR_ATTACHMENT_CONTACTPHOTO => true,
  565. PR_ATTACH_DATA_BIN => $contactPicture,
  566. PR_ATTACHMENT_FLAGS => 1,
  567. PR_ATTACH_EXTENSION_A => '.jpg',
  568. PR_ATTACH_NUM => 1
  569. );
  570. mapi_setprops($attach, $properties);
  571. mapi_savechanges($attach);
  572. }
  573. // Test
  574. if (mapi_last_hresult() > 0) {
  575. $this->logger->warn("Error saving contact picture: " . get_mapi_error_name());
  576. } else {
  577. $this->logger->trace("contact picture done");
  578. }
  579. }
  580. /**
  581. * Convert string to UTF-8
  582. * you need to check unicode store to ensure valid values
  583. * @param $string string to convert
  584. */
  585. public function to_charset($string) {
  586. //Zarafa 7 supports unicode chars, convert properties to utf-8 if it's another encoding
  587. if (defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) {
  588. return $string;
  589. }
  590. return utf8_encode($string);
  591. }
  592. }
  593. ?>