PageRenderTime 58ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/vcard/VCardProducer.php

http://sabre-zarafa.googlecode.com/
PHP | 336 lines | 211 code | 50 blank | 75 comment | 40 complexity | b232b63b3db4319ae6248e0c042572b5 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. // Logging
  27. include_once ("log4php/Logger.php");
  28. Logger::configure("log4php.xml");
  29. require_once "vcard/IVCardProducer.php";
  30. require_once "config.inc.php";
  31. // PHP-MAPI
  32. require_once("mapi/mapi.util.php");
  33. require_once("mapi/mapicode.php");
  34. require_once("mapi/mapidefs.php");
  35. require_once("mapi/mapitags.php");
  36. require_once("mapi/mapiguid.php");
  37. class VCardProducer implements IVCardProducer {
  38. public $defaultCharset;
  39. protected $bridge;
  40. protected $version;
  41. protected $logger;
  42. function __construct($bridge, $version) {
  43. $this->bridge = $bridge;
  44. $this->version = $version;
  45. $this->defaultCharset = 'utf-8';
  46. $this->logger = Logger::getLogger(__CLASS__);
  47. }
  48. /**
  49. * Decide charset for vcard
  50. * conversion is done by the bridge, vobject is always UTF8 encoded
  51. */
  52. public function getDefaultCharset() {
  53. $this->logger->debug("getDefaultCharset");
  54. $charset = 'ISO-8859-1//TRANSLIT';
  55. if ($this->version >= 3) {
  56. $charset = "utf-8";
  57. }
  58. $this->logger->debug("Charset: $charset");
  59. return $charset;
  60. }
  61. /**
  62. * Convert vObject to an array of properties
  63. * @param array $properties
  64. * @param object $vCard
  65. */
  66. public function propertiesToVObject($contact, &$vCard) {
  67. $this->logger->debug("Generating contact vCard from properties");
  68. $p = $this->bridge->getExtendedProperties();
  69. $contactProperties = mapi_getprops($contact); // $this->bridge->getProperties($contactId);
  70. $dump = print_r($contactProperties, true);
  71. $this->logger->trace("Contact properties:\n$contactProperties");
  72. // Version check
  73. switch ($this->version) {
  74. case 2: $vCard->add('VERSION', '2.1'); break;
  75. case 3: $vCard->add('VERSION', '3.0'); break;
  76. case 4: $vCard->add('VERSION', '4.0'); break;
  77. default:
  78. $this->logger->fatal("Unrecognised VCard version: " . $this->version);
  79. return;
  80. }
  81. // Private contact ?
  82. if (isset($contactProperties[$p['private']]) && $contactProperties[$p['private']]) {
  83. $vCard->add('CLASS', 'PRIVATE'); // Not in VCARD 4.0 but keep it for compatibility
  84. }
  85. // Mandatory FN
  86. $this->setVCard($vCard, 'FN', $contactProperties, $p['display_name']);
  87. // Contact name and pro information
  88. // N property
  89. /*
  90. Special note: The structured property value corresponds, in
  91. sequence, to the Family Names (also known as surnames), Given
  92. Names, Additional Names, Honorific Prefixes, and Honorific
  93. Suffixes. The text components are separated by the SEMICOLON
  94. character (U+003B). Individual text components can include
  95. multiple text values separated by the COMMA character (U+002C).
  96. This property is based on the semantics of the X.520 individual
  97. name attributes [CCITT.X520.1988]. The property SHOULD be present
  98. in the vCard object when the name of the object the vCard
  99. represents follows the X.520 model.
  100. The SORT-AS parameter MAY be applied to this property.
  101. */
  102. $contactInfos = array();
  103. $contactInfos[] = isset($contactProperties[$p['surname']]) ? $contactProperties[$p['surname']] : '';
  104. $contactInfos[] = isset($contactProperties[$p['given_name']]) ? $contactProperties[$p['given_name']] : '';
  105. $contactInfos[] = isset($contactProperties[$p['middle_name']]) ? $contactProperties[$p['middle_name']] : '';
  106. $contactInfos[] = isset($contactProperties[$p['display_name_prefix']]) ? $contactProperties[$p['display_name_prefix']] : '';
  107. $contactInfos[] = isset($contactProperties[$p['generation']]) ? $contactProperties[$p['generation']] : '';
  108. $element = new Sabre_VObject_Property("N");
  109. $element->setValue(implode(';', $contactInfos));
  110. // $element->offsetSet("SORT-AS", '"' . $contactProperties[$p['fileas']] . '"');
  111. $vCard->add($element);
  112. $this->setVCard($vCard, 'SORT-AS', $contactProperties, $p['fileas']);
  113. $this->setVCard($vCard, 'NICKNAME', $contactProperties, $p['nickname']);
  114. $this->setVCard($vCard, 'TITLE', $contactProperties, $p['title']);
  115. $this->setVCard($vCard, 'ROLE', $contactProperties, $p['profession']);
  116. $this->setVCard($vCard, 'ORG', $contactProperties, $p['company_name']);
  117. $this->setVCard($vCard, 'OFFICE', $contactProperties, $p['office_location']);
  118. if ($this->version >= 4) {
  119. if (isset($contactProperties[$p['assistant']])) {
  120. if (!empty ($contactProperties[$p['assistant']])) {
  121. $element = new Sabre_VObject_Property('RELATED');
  122. $element->setValue( $contactProperties[$p['assistant']]);
  123. $element->offsetSet('TYPE','assistant'); // Not RFC compliant
  124. $vCard->add($element);
  125. }
  126. }
  127. if (isset($contactProperties[$p['manager_name']])) {
  128. if (!empty ($contactProperties[$p['manager_name']])) {
  129. $element = new Sabre_VObject_Property('RELATED');
  130. $element->setValue( $contactProperties[$p['manager_name']]);
  131. $element->offsetSet('TYPE','manager'); // Not RFC compliant
  132. $vCard->add($element);
  133. }
  134. }
  135. if (isset($contactProperties[$p['spouse_name']])) {
  136. if (!empty ($contactProperties[$p['spouse_name']])) {
  137. $element = new Sabre_VObject_Property('RELATED');
  138. $element->setValue( $contactProperties[$p['spouse_name']]);
  139. $element->offsetSet('TYPE','spouse');
  140. $vCard->add($element);
  141. }
  142. }
  143. }
  144. // older syntax - may be needed by some clients so keep it!
  145. $this->setVCard($vCard, 'X-MS-ASSISTANT', $contactProperties, $p['assistant']);
  146. $this->setVCard($vCard, 'X-MS-MANAGER', $contactProperties, $p['manager_name']);
  147. $this->setVCard($vCard, 'X-MS-SPOUSE', $contactProperties, $p['spouse_name']);
  148. // Dates
  149. if (isset($contactProperties[$p['birthday']]) && ($contactProperties[$p['birthday']] > 0))
  150. $vCard->add('BDAY', date(DATE_PATTERN, $contactProperties[$p['birthday']]));
  151. if (isset($contactProperties[$p['wedding_anniversary']]) && ($contactProperties[$p['wedding_anniversary']] > 0)) {
  152. if ($this->version >= 4) {
  153. $vCard->add('ANNIVERSARY', date(DATE_PATTERN, $contactProperties[$p['wedding_anniversary']]));
  154. } else {
  155. $vCard->add('X-ANNIVERSARY', date(DATE_PATTERN, $contactProperties[$p['wedding_anniversary']]));
  156. }
  157. }
  158. // Telephone numbers
  159. // webaccess can handle 19 telephone numbers...
  160. $this->setVCard($vCard,'TEL;TYPE=HOME,VOICE', $contactProperties,$p['home_telephone_number']);
  161. $this->setVCard($vCard,'TEL;TYPE=HOME,VOICE', $contactProperties,$p['home2_telephone_number']);
  162. $this->setVCard($vCard,'TEL;TYPE=CELL', $contactProperties,$p['cellular_telephone_number']);
  163. $this->setVCard($vCard,'TEL;TYPE=WORK,VOICE', $contactProperties,$p['office_telephone_number']);
  164. $this->setVCard($vCard,'TEL;TYPE=WORK,VOICE', $contactProperties,$p['business2_telephone_number']);
  165. $this->setVCard($vCard,'TEL;TYPE=WORK,FAX', $contactProperties,$p['business_fax_number']);
  166. $this->setVCard($vCard,'TEL;TYPE=HOME,FAX', $contactProperties,$p['home_fax_number']);
  167. $this->setVCard($vCard,'TEL;TYPE=PAGER', $contactProperties,$p['pager_telephone_number']);
  168. $this->setVCard($vCard,'TEL;TYPE=ISDN', $contactProperties,$p['isdn_number']);
  169. $this->setVCard($vCard,'TEL;TYPE=WORK', $contactProperties,$p['company_telephone_number']);
  170. $this->setVCard($vCard,'TEL;TYPE=CAR', $contactProperties,$p['car_telephone_number']);
  171. $this->setVCard($vCard,'TEL;TYPE=SECR', $contactProperties,$p['assistant_telephone_number']);
  172. // There are unmatched telephone numbers in zarafa, use them!
  173. $unmatchedProperties = array("callback_telephone_number", "other_telephone_number", "primary_fax_number",
  174. "primary_telephone_number", "radio_telephone_number", "telex_telephone_number",
  175. "ttytdd_telephone_number"
  176. );
  177. if (in_array(DEFAULT_TELEPHONE_NUMBER_PROPERTY, $unmatchedProperties)) {
  178. // unmatched found a match!
  179. $this->setVCard($vCard, 'TEL', $contactProperties, $p[DEFAULT_TELEPHONE_NUMBER_PROPERTY]);
  180. }
  181. $this->setVCardAddress($vCard, 'HOME', $contactProperties, 'home');
  182. $this->setVCardAddress($vCard, 'WORK', $contactProperties, 'business');
  183. $this->setVCardAddress($vCard, 'OTHER', $contactProperties, 'other');
  184. // emails
  185. for ($i = 1; $i <= 3; $i++) {
  186. if (isset($contactProperties[$p["email_address_$i"]])) {
  187. // Zarafa needs an email display name
  188. $emailProperty = new Sabre_VObject_Property('EMAIL', $contactProperties[$p["email_address_$i"]]);
  189. // Get display name
  190. $dn = isset($contactProperties[$p["email_address_display_name_$i"]]) ? $contactProperties[$p["email_address_display_name_$i"]]
  191. : $contactProperties[$p['display_name']];
  192. $emailProperty->offsetSet("X-CN", '"' . $dn . '"');
  193. $vCard->add($emailProperty);
  194. }
  195. }
  196. // URL and Instant Messenging (vCard 3.0 extension)
  197. $this->setVCard($vCard,'URL', $contactProperties,$p["webpage"]);
  198. $this->setVCard($vCard,'IMPP', $contactProperties,$p["im"]);
  199. // Categories
  200. $contactCategories = '';
  201. if (isset($contactProperties[$p['categories']])) {
  202. if (is_array($contactProperties[$p['categories']])) {
  203. $contactCategories = implode(',', $contactProperties[$p['categories']]);
  204. } else {
  205. $contactCategories = $contactProperties[$p['categories']];
  206. }
  207. }
  208. if ($contactCategories != '') {
  209. $vCard->add('CATEGORIES', $contactCategories);
  210. }
  211. // Contact picture?
  212. $hasattachProp = mapi_getprops($contact, array(PR_HASATTACH));
  213. $photo = NULL;
  214. $photoMime = '';
  215. if (isset($hasattachProp[PR_HASATTACH])&& $hasattachProp[PR_HASATTACH]) {
  216. $attachmentTable = mapi_message_getattachmenttable($contact);
  217. $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));
  218. $dump = print_r ($attachments, true);
  219. $this->logger->trace("Contact attachments:\n$dump");
  220. foreach ($attachments as $attachmentRow) {
  221. if (isset($attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) && $attachmentRow[PR_ATTACHMENT_CONTACTPHOTO]) {
  222. $attach = mapi_message_openattach($contact, $attachmentRow[PR_ATTACH_NUM]);
  223. $photo = mapi_attach_openbin($attach,PR_ATTACH_DATA_BIN);
  224. if (isset($attachmentRow[PR_ATTACH_MIME_TAG])) {
  225. $photoMime = $attachmentRow[PR_ATTACH_MIME_TAG];
  226. } else {
  227. $photoMime = 'image/jpeg';
  228. }
  229. break;
  230. }
  231. }
  232. }
  233. if ($photo != NULL) {
  234. // SogoConnector does not like image/jpeg
  235. if ($photoMime == 'image/jpeg') {
  236. $photoMime = 'JPEG';
  237. }
  238. $this->logger->trace("Adding contact picture to VCard");
  239. $photoEncoded = base64_encode($photo);
  240. $photoProperty = new Sabre_VObject_Property('PHOTO',$photoEncoded);
  241. $photoProperty->offsetSet('TYPE', $photoMime);
  242. $photoProperty->offsetSet('ENCODING','b');
  243. $vCard->add($photoProperty);
  244. }
  245. // Misc
  246. $vCard->add('UID', "urn:uuid:" . substr($contactProperties[PR_CARDDAV_URI], 0, -4)); // $this->entryIdToStr($contactProperties[PR_ENTRYID]));
  247. $this->setVCard($vCard, 'NOTE', $contactProperties, $p['notes']);
  248. $vCard->add('PRODID', VCARD_PRODUCT_ID);
  249. $vCard->add('REV', date('c',$contactProperties[$p['last_modification_time']]));
  250. }
  251. /**
  252. * Helper function to set a vObject property
  253. */
  254. protected function setVCard($vCard, $vCardProperty, &$contactProperties, $propertyId) {
  255. if (isset($contactProperties[$propertyId]) && ($contactProperties[$propertyId] != '')) {
  256. $vCard->add($vCardProperty, $contactProperties[$propertyId]);
  257. }
  258. }
  259. /**
  260. * Helper function to set an address in vObject
  261. */
  262. protected function setVCardAddress($vCard, $addressType, &$contactProperties, $propertyPrefix) {
  263. $this->logger->trace("setVCardAddress - $addressType");
  264. $p = $this->bridge->getExtendedProperties();
  265. $address = array();
  266. if (isset($contactProperties[$p[$propertyPrefix ."_address"]])) {
  267. $address[] = ''; // post office box
  268. $address[] = ''; // extended address
  269. $address[] = isset($contactProperties[$p[$propertyPrefix . '_address_street']]) ? $contactProperties[$p[$propertyPrefix . '_address_street']] : '';
  270. $address[] = isset($contactProperties[$p[$propertyPrefix . '_address_city']]) ? $contactProperties[$p[$propertyPrefix . '_address_city']] : '';
  271. $address[] = isset($contactProperties[$p[$propertyPrefix . '_address_state']]) ? $contactProperties[$p[$propertyPrefix . '_address_state']] : '';
  272. $address[] = isset($contactProperties[$p[$propertyPrefix . '_address_postal_code']]) ? $contactProperties[$p[$propertyPrefix . '_address_postal_code']] : '';
  273. $address[] = isset($contactProperties[$p[$propertyPrefix . '_address_country']]) ? $contactProperties[$p[$propertyPrefix . '_address_country']] : '';
  274. }
  275. $address = implode(';', $address);
  276. if ($address != ';;;;;;') {
  277. $this->logger->trace("Not empty address - adding $address");
  278. $element = new Sabre_VObject_Property('ADR');
  279. $element->setValue($address);
  280. $element->offsetSet('TYPE', $addressType);
  281. $vCard->add($element);
  282. }
  283. }
  284. }
  285. ?>