PageRenderTime 84ms CodeModel.GetById 55ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 1ms

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