25c9a2025fcbb1f7570053b1a27eca2ea05a1cbf
[tine20] / tine20 / Addressbook / Model / Contact.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Addressbook
6  * @subpackage  Model
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Lars Kneschke <l.kneschke@metaways.de>
9  * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
10  */
11
12 /**
13  * class to hold contact data
14  * 
15  * @package     Addressbook
16  * @subpackage  Model
17  *
18  * @property    string $account_id                 id of associated user
19  * @property    string $adr_one_countryname        name of the country the contact lives in
20  * @property    string $adr_one_locality           locality of the contact
21  * @property    string $adr_one_postalcode         postalcode belonging to the locality
22  * @property    string $adr_one_region             region the contact lives in
23  * @property    string $adr_one_street             street where the contact lives
24  * @property    string $adr_one_street2            street2 where contact lives
25  * @property    string $adr_one_lon
26  * @property    string $adr_one_lat
27  * @property    string $adr_two_countryname        second home/country where the contact lives
28  * @property    string $adr_two_locality           second locality of the contact
29  * @property    string $adr_two_postalcode         ostalcode belonging to second locality
30  * @property    string $adr_two_region             second region the contact lives in
31  * @property    string $adr_two_street             second street where the contact lives
32  * @property    string $adr_two_street2            second street2 where the contact lives
33  * @property    string $adr_two_lon
34  * @property    string $adr_two_lat
35  * @property    string $assistent                  name of the assistent of the contact
36  * @property    datetime $bday                     date of birth of contact
37  * @property    integer $container_id              id of container
38  * @property    string $email                      the email address of the contact
39  * @property    string $email_home                 the private email address of the contact
40  * @property    string $jpegphoto                    photo of the contact
41  * @property    string $n_family                   surname of the contact
42  * @property    string $n_fileas                   display surname, name
43  * @property    string $n_fn                       the full name
44  * @property    string $n_given                    forename of the contact
45  * @property    string $n_middle                   middle name of the contact
46  * @property    string $note                       notes of the contact
47  * @property    string $n_prefix
48  * @property    string $n_suffix
49  * @property    string $org_name                   name of the company the contact works at
50  * @property    string $org_unit
51  * @property    string $role                       type of role of the contact
52  * @property    string $tel_assistent              phone number of the assistent
53  * @property    string $tel_car
54  * @property    string $tel_cell                   mobile phone number
55  * @property    string $tel_cell_private           private mobile number
56  * @property    string $tel_fax                    number for calling the fax
57  * @property    string $tel_fax_home               private fax number
58  * @property    string $tel_home                   telephone number of contact's home
59  * @property    string $tel_pager                  contact's pager number
60  * @property    string $tel_work                   contact's office phone number
61  * @property    string $title                      special title of the contact
62  * @property    string $type                       type of contact
63  * @property    string $url                        url of the contact
64  * @property    string $url_home                   private url of the contact
65  * @property    integer $preferred_address         defines which is the preferred address of a contact, 0: business, 1: private
66  */
67 class Addressbook_Model_Contact extends Tinebase_Record_Abstract
68 {
69     /**
70      * const to describe contact of current account id independent
71      * 
72      * @var string
73      */
74     const CURRENTCONTACT = 'currentContact';
75     
76     /**
77      * contact type: contact
78      * 
79      * @var string
80      */
81     const CONTACTTYPE_CONTACT = 'contact';
82     
83     /**
84      * contact type: user
85      * 
86      * @var string
87      */
88     const CONTACTTYPE_USER = 'user';
89
90     /**
91      * small contact photo size
92      *
93      * @var integer
94      */
95     const SMALL_PHOTO_SIZE = 36000;
96     
97     /**
98      * key in $_validators/$_properties array for the filed which 
99      * represents the identifier
100      * 
101      * @var string
102      */
103     protected $_identifier = 'id';
104     
105     /**
106      * application the record belongs to
107      *
108      * @var string
109      */
110     protected $_application = 'Addressbook';
111     
112     /**
113      * if foreign Id fields should be resolved on search and get from json
114      * should have this format: 
115      *     array('Calendar_Model_Contact' => 'contact_id', ...)
116      * or for more fields:
117      *     array('Calendar_Model_Contact' => array('contact_id', 'customer_id), ...)
118      * (e.g. resolves contact_id with the corresponding Model)
119      * 
120      * @var array
121      */
122     protected static $_resolveForeignIdFields = array(
123         'Tinebase_Model_User'        => array('created_by', 'last_modified_by'),
124         'Addressbook_Model_Industry' => array('industry'),
125         'recursive'                  => array('attachments' => 'Tinebase_Model_Tree_Node'),
126         'Addressbook_Model_List' => array('groups'),
127     );
128     
129     /**
130      * list of zend inputfilter
131      * 
132      * this filter get used when validating user generated content with Zend_Input_Filter
133      *
134      * @var array
135      */
136     protected $_filters = array(
137         'adr_one_countryname'   => array('StringTrim', 'StringToUpper'),
138         'adr_two_countryname'   => array('StringTrim', 'StringToUpper'),
139         'email'                 => array('StringTrim', 'StringToLower'),
140         'email_home'            => array('StringTrim', 'StringToLower'),
141         'url'                   => array('StringTrim'),
142         'url_home'              => array('StringTrim'),
143     );
144
145     /**
146      * list of zend validator
147      * 
148      * this validators get used when validating user generated content with Zend_Input_Filter
149      *
150      * @var array
151      */
152     protected $_validators = array(
153         'adr_one_countryname'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
154         'adr_one_locality'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
155         'adr_one_postalcode'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
156         'adr_one_region'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
157         'adr_one_street'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
158         'adr_one_street2'               => array(Zend_Filter_Input::ALLOW_EMPTY => true),
159         'adr_one_lon'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
160         'adr_one_lat'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
161         'adr_two_countryname'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
162         'adr_two_locality'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
163         'adr_two_postalcode'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
164         'adr_two_region'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
165         'adr_two_street'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
166         'adr_two_street2'               => array(Zend_Filter_Input::ALLOW_EMPTY => true),
167         'adr_two_lon'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
168         'adr_two_lat'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
169         'assistent'                     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
170         'bday'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
171         'calendar_uri'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
172         'email'                         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
173         'email_home'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
174         'jpegphoto'                     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
175         'freebusy_uri'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
176         'id'                            => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
177         'account_id'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
178         'note'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
179         'container_id'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
180         'role'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
181         'salutation'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
182         'title'                         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
183         'url'                           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
184         'url_home'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
185         'n_family'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
186         'n_fileas'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
187         'n_fn'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
188         'n_given'                       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
189         'n_middle'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
190         'n_prefix'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
191         'n_suffix'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
192         'org_name'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
193         'org_unit'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
194         'pubkey'                        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
195         'room'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
196         'tel_assistent'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
197         'tel_car'                       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
198         'tel_cell'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
199         'tel_cell_private'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
200         'tel_fax'                       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
201         'tel_fax_home'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
202         'tel_home'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
203         'tel_pager'                     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
204         'tel_work'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
205         'tel_other'                     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
206         'tel_prefer'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
207         'tel_assistent_normalized'      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
208         'tel_car_normalized'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
209         'tel_cell_normalized'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
210         'tel_cell_private_normalized'   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
211         'tel_fax_normalized'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
212         'tel_fax_home_normalized'       => array(Zend_Filter_Input::ALLOW_EMPTY => true),
213         'tel_home_normalized'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
214         'tel_pager_normalized'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
215         'tel_work_normalized'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
216         'tel_other_normalized'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
217         'tel_prefer_normalized'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
218         'tz'                            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
219         'geo'                           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
220         'preferred_address'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
221     // modlog fields
222         'created_by'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
223         'creation_time'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
224         'last_modified_by'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
225         'last_modified_time'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
226         'is_deleted'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
227         'deleted_time'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
228         'deleted_by'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
229         'seq'                           => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
230     // tine 2.0 generic fields
231         'tags'                          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
232         'notes'                         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
233         'relations'                     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
234         'attachments'                   => array(Zend_Filter_Input::ALLOW_EMPTY => true),
235         'customfields'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE         => array()),
236         'type'                          => array(
237             Zend_Filter_Input::ALLOW_EMPTY => true,
238             Zend_Filter_Input::DEFAULT_VALUE => self::CONTACTTYPE_CONTACT,
239             array('InArray', array(self::CONTACTTYPE_USER, self::CONTACTTYPE_CONTACT)),
240         ),
241         'paths'                         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
242         'industry'                      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
243         'syncBackendIds'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
244         'groups' => array(
245             'type' => 'virtual',
246             Zend_Filter_Input::ALLOW_EMPTY => true,
247         ),
248     );
249     
250     /**
251      * name of fields containing datetime or or an array of datetime information
252      *
253      * @var array list of datetime fields
254      */
255     protected $_datetimeFields = array(
256         'bday',
257         'creation_time',
258         'last_modified_time',
259         'deleted_time'
260     );
261     
262     /**
263     * name of fields that should be omited from modlog
264     *
265     * @var array list of modlog omit fields
266     */
267     protected $_modlogOmitFields = array(
268         'jpegphoto',
269     );
270
271     /**
272      * list of telephone country codes
273      *
274      * source of country codes:
275      * $json = json_decode(file_get_contents('https://raw.github.com/mledoze/countries/master/countries.json'));
276      * foreach($json as $val) { foreach($val->callingCode as $cc) $data['+'.$cc] = true;}
277      * ksort($data);
278      * echo 'array(\'' . join('\',\'', array_keys($data)) . '\');';
279      *
280      * @var array list of telephone country codes
281      */
282     protected static $countryCodes = array('+1','+7','+20','+27','+30','+31','+32','+33','+34','+36','+39','+40','+41','+43','+44','+45','+46','+47','+48','+49','+51','+52','+53','+54','+55','+56','+57','+58','+60','+61','+62','+63','+64','+65','+66','+76','+77','+81','+82','+84','+86','+90','+91','+92','+93','+94','+95','+98','+211','+212','+213','+216','+218','+220','+221','+222','+223','+224','+225','+226','+227','+228','+229','+230','+231','+232','+233','+234','+235','+236','+237','+238','+239','+240','+241','+242','+243','+244','+245','+246','+248','+249','+250','+251','+252','+253','+254','+255','+256','+257','+258','+260','+261','+262','+263','+264','+265','+266','+267','+268','+269','+291','+297','+298','+299','+350','+351','+352','+353','+354','+355','+356','+357','+358','+359','+370','+371','+372','+373','+374','+375','+376','+377','+378','+379','+380','+381','+382','+383','+385','+386','+387','+389','+420','+421','+423','+500','+501','+502','+503','+504','+505','+506','+507','+508','+509','+590','+591','+592','+593','+594','+595','+596','+597','+598','+670','+672','+673','+674','+675','+676','+677','+678','+679','+680','+681','+682','+683','+685','+686','+687','+688','+689','+690','+691','+692','+850','+852','+853','+855','+856','+880','+886','+960','+961','+962','+963','+964','+965','+966','+967','+968','+970','+971','+972','+973','+974','+975','+976','+977','+992','+993','+994','+995','+996','+998','+1242','+1246','+1264','+1268','+1284','+1340','+1345','+1441','+1473','+1649','+1664','+1670','+1671','+1684','+1721','+1758','+1767','+1784','+1787','+1809','+1829','+1849','+1868','+1869','+1876','+1939','+4779','+5999','+3906698');
283
284     /**
285      * name of fields which require manage accounts to be updated
286      *
287      * @var array list of fields which require manage accounts to be updated
288      */
289     protected static $_manageAccountsFields = array(
290         'email',
291         'n_fileas',
292         'n_fn',
293         'n_given',
294         'n_family',
295     );
296     
297     /**
298     * overwrite constructor to add more filters
299     *
300     * @param mixed $_data
301     * @param bool $_bypassFilters
302     * @param mixed $_convertDates
303     */
304     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
305     {
306         // set geofields to NULL if empty
307         $geoFields = array('adr_one_lon', 'adr_one_lat', 'adr_two_lon', 'adr_two_lat');
308         foreach ($geoFields as $geoField) {
309             $this->_filters[$geoField]        = new Zend_Filter_Empty(NULL);
310         }
311     
312         parent::__construct($_data, $_bypassFilters, $_convertDates);
313     }
314
315     /**
316      * @return array
317      */
318     static public function getManageAccountFields()
319     {
320         return self::$_manageAccountsFields;
321     }
322
323     /**
324      * returns prefered email address of given contact
325      * 
326      * @return string
327      */
328     public function getPreferredEmailAddress()
329     {
330         // prefer work mail over private mail till we have prefs for this
331         return $this->email ? $this->email : $this->email_home;
332     }
333     
334     /**
335      * @see Tinebase_Record_Abstract::setFromArray
336      *
337      * @param array $_data            the new data to set
338      */
339     public function setFromArray(array $_data)
340     {
341         $_data = $this->_resolveAutoValues($_data);
342         parent::setFromArray($_data);
343     }
344     
345     /**
346      * Resolves the auto values n_fn and n_fileas
347      * @param array $_data
348      * @return array $_data
349      */
350     protected function _resolveAutoValues(array $_data)
351     {
352         if (! (isset($_data['org_name']) || array_key_exists('org_name', $_data))) {
353             $_data['org_name'] = '';
354         }
355
356         // try to guess name from n_fileas
357         // TODO: n_fn
358         if (empty($_data['org_name']) && empty($_data['n_family'])) {
359             if (! empty($_data['n_fileas'])) {
360                 $names = preg_split('/\s*,\s*/', $_data['n_fileas']);
361                 $_data['n_family'] = $names[0];
362                 if (empty($_data['n_given'])&& isset($names[1])) {
363                     $_data['n_given'] = $names[1];
364                 }
365             }
366         }
367         
368         // always update fileas and fn
369         $_data['n_fileas'] = (!empty($_data['n_family'])) ? $_data['n_family']
370             : ((! empty($_data['org_name'])) ? $_data['org_name']
371             : ((isset($_data['n_fileas'])) ? $_data['n_fileas'] : ''));
372
373         if (!empty($_data['n_given'])) {
374             $_data['n_fileas'] .= ', ' . $_data['n_given'];
375         }
376
377         $_data['n_fn'] = (!empty($_data['n_family'])) ? $_data['n_family']
378             : ((! empty($_data['org_name'])) ? $_data['org_name']
379             : ((isset($_data['n_fn'])) ? $_data['n_fn'] : ''));
380
381         if (!empty($_data['n_given'])) {
382             $_data['n_fn'] = $_data['n_given'] . ' ' . $_data['n_fn'];
383         }
384         return $_data;
385     }
386     
387     /**
388      * Overwrites the __set Method from Tinebase_Record_Abstract
389      * Also sets n_fn and n_fileas when org_name, n_given or n_family should be set
390      * @see Tinebase_Record_Abstract::__set()
391      * @param string $_name of property
392      * @param mixed $_value of property
393      */
394     public function __set($_name, $_value) {
395         
396         switch ($_name) {
397             case 'n_given':
398                 $resolved = $this->_resolveAutoValues(array('n_given' => $_value, 'n_family' => $this->__get('n_family'), 'org_name' => $this->__get('org_name')));
399                 parent::__set('n_fn', $resolved['n_fn']);
400                 parent::__set('n_fileas', $resolved['n_fileas']);
401                 break;
402             case 'n_family':
403                 $resolved = $this->_resolveAutoValues(array('n_family' => $_value, 'n_given' => $this->__get('n_given'), 'org_name' => $this->__get('org_name')));
404                 parent::__set('n_fn', $resolved['n_fn']);
405                 parent::__set('n_fileas', $resolved['n_fileas']);
406                 break;
407             case 'org_name':
408                 $resolved = $this->_resolveAutoValues(array('org_name' => $_value, 'n_given' => $this->__get('n_given'), 'n_family' => $this->__get('n_family')));
409                 parent::__set('n_fn', $resolved['n_fn']);
410                 parent::__set('n_fileas', $resolved['n_fileas']);
411                 break;
412             default:
413                 // normalize telephone numbers
414                 if (strpos($_name, 'tel_') === 0 && strpos($_name, '_normalized') === false) {
415                     parent::__set($_name . '_normalized', (empty($_value)? $_value : static::normalizeTelephoneNoCountry($_value)));
416                 }
417                 break;
418         }
419         
420         parent::__set($_name, $_value);
421     }
422
423     /**
424      * normalizes telephone numbers and removes country part
425      * result will be of format 0xxxxxxxxx (only digits)
426      *
427      * @param  string $telNumber
428      * @return string|null
429      */
430     public static function normalizeTelephoneNoCountry($telNumber)
431     {
432         $val = trim($telNumber);
433
434         // replace leading + with 00
435         if ($val[0] === '+') {
436             $val = '00' . mb_substr($val, 1);
437         }
438
439         // remove any non digit characters
440         $val = preg_replace('/\D+/u', '', $val);
441
442         // if not at least 5 digits, stop where
443         if (strlen($val) < 5)
444             return null;
445
446         // replace 00 with +
447         if ($val[0] === '0' && $val[1] === '0') {
448             $val = '+' . mb_substr($val, 2);
449         }
450
451         // normalize to remove leading country codes and make the number start with 0
452         if ($val[0] === '+') {
453             $val = str_replace(static::$countryCodes, '0', $val);
454         } elseif($val[0] !== '0') {
455             $val = '0' . $val;
456         }
457
458         // in case the country codes was not recognized...
459         if ($val[0] === '+') {
460             $val = '0' . mb_substr($val, 1);
461         }
462
463         return $val;
464     }
465
466     /**
467      * fills a contact from json data
468      *
469      * @param array $_data record data
470      * @return void
471      * 
472      * @todo timezone conversion for birthdays?
473      * @todo move this to Addressbook_Convert_Contact_Json
474      */
475     protected function _setFromJson(array &$_data)
476     {
477         $this->_setContactImage($_data);
478         
479         // unset if empty
480         // @todo is this still needed?
481         if (empty($_data['id'])) {
482             unset($_data['id']);
483         }
484     }
485     
486     /**
487      * set contact image
488      * 
489      * @param array $_data
490      */
491     protected function _setContactImage(&$_data)
492     {
493         if (! isset($_data['jpegphoto']) || $_data['jpegphoto'] === '') {
494             return;
495         }
496         
497         $imageParams = Tinebase_ImageHelper::parseImageLink($_data['jpegphoto']);
498         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' image params:' . print_r($imageParams, TRUE));
499         if ($imageParams['isNewImage']) {
500             try {
501                 $_data['jpegphoto'] = Tinebase_ImageHelper::getImageData($imageParams);
502             } catch(Tinebase_Exception_UnexpectedValue $teuv) {
503                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not add contact image: ' . $teuv->getMessage());
504                 unset($_data['jpegphoto']);
505             }
506         } else {
507             unset($_data['jpegphoto']);
508         }
509     }
510
511     /**
512      * set small contact image
513      *
514      * @param $newPhotoBlob
515      * @param $maxSize
516      */
517     public function setSmallContactImage($newPhotoBlob, $maxSize = self::SMALL_PHOTO_SIZE)
518     {
519         if ($this->getId()) {
520             try {
521                 $currentPhoto = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId())->getBlob('image/jpeg', $maxSize);
522             } catch (Exception $e) {
523                 // no current photo
524             }
525         }
526
527         if (isset($currentPhoto) && $currentPhoto == $newPhotoBlob) {
528             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
529                 . " Photo did not change -> preserving current photo");
530         } else {
531             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
532                 . " Setting new contact photo (" . strlen($newPhotoBlob) . "KB)");
533             $this->jpegphoto = $newPhotoBlob;
534         }
535     }
536
537     /**
538      * return small contact image for sync
539      *
540      * @param $maxSize
541      *
542      * @return string
543      * @throws Tinebase_Exception_InvalidArgument
544      * @throws Tinebase_Exception_NotFound
545      */
546     public function getSmallContactImage($maxSize = self::SMALL_PHOTO_SIZE)
547     {
548         $image = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId());
549         return $image->getBlob('image/jpeg', $maxSize);
550     }
551
552     /**
553      * get title
554      *
555      * @return string
556      */
557     public function getTitle()
558     {
559         return $this->n_fn;
560     }
561
562     /**
563      * returns an array containing the parent neighbours relation objects or record(s) (ids) in the key 'parents'
564      * and containing the children neighbours in the key 'children'
565      *
566      * @return array
567      */
568     public function getPathNeighbours()
569     {
570         $listController = Addressbook_Controller_List::getInstance();
571         $oldAclCheck = $listController->doContainerACLChecks(false);
572
573         $lists = $listController->search(new Addressbook_Model_ListFilter(array(
574             array('field' => 'contact',     'operator' => 'equals', 'value' => $this->getId())
575         )));
576
577         $listMemberRoles = $listController->getMemberRolesBackend()->search(new Addressbook_Model_ListMemberRoleFilter(array(
578             array('field' => 'contact_id',  'operator' => 'equals', 'value' => $this->getId())
579         )));
580
581         $listRoles = array();
582         /** @var Addressbook_Model_ListMemberRole $listMemberRole */
583         foreach($listMemberRoles as $listMemberRole) {
584             $listRoles[$listMemberRole->list_role_id] = $listMemberRole->list_role_id;
585             $lists->removeById($listMemberRole->list_id);
586         }
587
588         if (count($listRoles) > 0) {
589             $listRoles = Addressbook_Controller_ListRole::getInstance()->getMultiple($listRoles, true)->asArray();
590         }
591
592         $result = parent::getPathNeighbours();
593         $result['parents'] = array_merge($result['parents'], $lists->asArray(), $listRoles);
594
595         $listController->doContainerACLChecks($oldAclCheck);
596
597         return $result;
598     }
599 }