0013292: customfield as duplicate check field does not work for empty values
[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, Zend_Filter_Input::DEFAULT_VALUE => 0),
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         $this->_filters['preferred_address']        = new Zend_Filter_Empty(0);
312     
313         parent::__construct($_data, $_bypassFilters, $_convertDates);
314     }
315
316     /**
317      * @return array
318      */
319     static public function getManageAccountFields()
320     {
321         return self::$_manageAccountsFields;
322     }
323
324     /**
325      * returns prefered email address of given contact
326      * 
327      * @return string
328      */
329     public function getPreferredEmailAddress()
330     {
331         // prefer work mail over private mail till we have prefs for this
332         return $this->email ? $this->email : $this->email_home;
333     }
334     
335     /**
336      * @see Tinebase_Record_Abstract::setFromArray
337      *
338      * @param array $_data            the new data to set
339      */
340     public function setFromArray(array $_data)
341     {
342         $_data = $this->_resolveAutoValues($_data);
343         parent::setFromArray($_data);
344     }
345     
346     /**
347      * Resolves the auto values n_fn and n_fileas
348      * @param array $_data
349      * @return array $_data
350      */
351     protected function _resolveAutoValues(array $_data)
352     {
353         if (! (isset($_data['org_name']) || array_key_exists('org_name', $_data))) {
354             $_data['org_name'] = '';
355         }
356
357         // try to guess name from n_fileas
358         // TODO: n_fn
359         if (empty($_data['org_name']) && empty($_data['n_family'])) {
360             if (! empty($_data['n_fileas'])) {
361                 $names = preg_split('/\s*,\s*/', $_data['n_fileas']);
362                 $_data['n_family'] = $names[0];
363                 if (empty($_data['n_given'])&& isset($names[1])) {
364                     $_data['n_given'] = $names[1];
365                 }
366             }
367         }
368         
369         // always update fileas and fn
370         $_data['n_fileas'] = (!empty($_data['n_family'])) ? $_data['n_family']
371             : ((! empty($_data['org_name'])) ? $_data['org_name']
372             : ((isset($_data['n_fileas'])) ? $_data['n_fileas'] : ''));
373
374         if (!empty($_data['n_given'])) {
375             $_data['n_fileas'] .= ', ' . $_data['n_given'];
376         }
377
378         $_data['n_fn'] = (!empty($_data['n_family'])) ? $_data['n_family']
379             : ((! empty($_data['org_name'])) ? $_data['org_name']
380             : ((isset($_data['n_fn'])) ? $_data['n_fn'] : ''));
381
382         if (!empty($_data['n_given'])) {
383             $_data['n_fn'] = $_data['n_given'] . ' ' . $_data['n_fn'];
384         }
385         return $_data;
386     }
387     
388     /**
389      * Overwrites the __set Method from Tinebase_Record_Abstract
390      * Also sets n_fn and n_fileas when org_name, n_given or n_family should be set
391      * @see Tinebase_Record_Abstract::__set()
392      * @param string $_name of property
393      * @param mixed $_value of property
394      */
395     public function __set($_name, $_value) {
396         
397         switch ($_name) {
398             case 'n_given':
399                 $resolved = $this->_resolveAutoValues(array('n_given' => $_value, 'n_family' => $this->__get('n_family'), 'org_name' => $this->__get('org_name')));
400                 parent::__set('n_fn', $resolved['n_fn']);
401                 parent::__set('n_fileas', $resolved['n_fileas']);
402                 break;
403             case 'n_family':
404                 $resolved = $this->_resolveAutoValues(array('n_family' => $_value, 'n_given' => $this->__get('n_given'), 'org_name' => $this->__get('org_name')));
405                 parent::__set('n_fn', $resolved['n_fn']);
406                 parent::__set('n_fileas', $resolved['n_fileas']);
407                 break;
408             case 'org_name':
409                 $resolved = $this->_resolveAutoValues(array('org_name' => $_value, 'n_given' => $this->__get('n_given'), 'n_family' => $this->__get('n_family')));
410                 parent::__set('n_fn', $resolved['n_fn']);
411                 parent::__set('n_fileas', $resolved['n_fileas']);
412                 break;
413             default:
414                 // normalize telephone numbers
415                 if (strpos($_name, 'tel_') === 0 && strpos($_name, '_normalized') === false) {
416                     parent::__set($_name . '_normalized', (empty($_value)? $_value : static::normalizeTelephoneNoCountry($_value)));
417                 }
418                 break;
419         }
420         
421         parent::__set($_name, $_value);
422     }
423
424     /**
425      * normalizes telephone numbers and removes country part
426      * result will be of format 0xxxxxxxxx (only digits)
427      *
428      * @param  string $telNumber
429      * @return string|null
430      */
431     public static function normalizeTelephoneNoCountry($telNumber)
432     {
433         $val = trim($telNumber);
434
435         // replace leading + with 00
436         if ($val[0] === '+') {
437             $val = '00' . mb_substr($val, 1);
438         }
439
440         // remove any non digit characters
441         $val = preg_replace('/\D+/u', '', $val);
442
443         // if not at least 5 digits, stop where
444         if (strlen($val) < 5)
445             return null;
446
447         // replace 00 with +
448         if ($val[0] === '0' && $val[1] === '0') {
449             $val = '+' . mb_substr($val, 2);
450         }
451
452         // normalize to remove leading country codes and make the number start with 0
453         if ($val[0] === '+') {
454             $val = str_replace(static::$countryCodes, '0', $val);
455         } elseif($val[0] !== '0') {
456             $val = '0' . $val;
457         }
458
459         // in case the country codes was not recognized...
460         if ($val[0] === '+') {
461             $val = '0' . mb_substr($val, 1);
462         }
463
464         return $val;
465     }
466
467     /**
468      * fills a contact from json data
469      *
470      * @param array $_data record data
471      * @return void
472      * 
473      * @todo timezone conversion for birthdays?
474      * @todo move this to Addressbook_Convert_Contact_Json
475      */
476     protected function _setFromJson(array &$_data)
477     {
478         $this->_setContactImage($_data);
479         
480         // unset if empty
481         // @todo is this still needed?
482         if (empty($_data['id'])) {
483             unset($_data['id']);
484         }
485     }
486     
487     /**
488      * set contact image
489      * 
490      * @param array $_data
491      */
492     protected function _setContactImage(&$_data)
493     {
494         if (! isset($_data['jpegphoto']) || $_data['jpegphoto'] === '') {
495             return;
496         }
497         
498         $imageParams = Tinebase_ImageHelper::parseImageLink($_data['jpegphoto']);
499         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' image params:' . print_r($imageParams, TRUE));
500         if ($imageParams['isNewImage']) {
501             try {
502                 $_data['jpegphoto'] = Tinebase_ImageHelper::getImageData($imageParams);
503             } catch(Tinebase_Exception_UnexpectedValue $teuv) {
504                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not add contact image: ' . $teuv->getMessage());
505                 unset($_data['jpegphoto']);
506             }
507         } else {
508             unset($_data['jpegphoto']);
509         }
510     }
511
512     /**
513      * set small contact image
514      *
515      * @param $newPhotoBlob
516      * @param $maxSize
517      */
518     public function setSmallContactImage($newPhotoBlob, $maxSize = self::SMALL_PHOTO_SIZE)
519     {
520         if ($this->getId()) {
521             try {
522                 $currentPhoto = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId())->getBlob('image/jpeg', $maxSize);
523             } catch (Exception $e) {
524                 // no current photo
525             }
526         }
527
528         if (isset($currentPhoto) && $currentPhoto == $newPhotoBlob) {
529             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
530                 . " Photo did not change -> preserving current photo");
531         } else {
532             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
533                 . " Setting new contact photo (" . strlen($newPhotoBlob) . "KB)");
534             $this->jpegphoto = $newPhotoBlob;
535         }
536     }
537
538     /**
539      * return small contact image for sync
540      *
541      * @param $maxSize
542      *
543      * @return string
544      * @throws Tinebase_Exception_InvalidArgument
545      * @throws Tinebase_Exception_NotFound
546      */
547     public function getSmallContactImage($maxSize = self::SMALL_PHOTO_SIZE)
548     {
549         $image = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId());
550         return $image->getBlob('image/jpeg', $maxSize);
551     }
552
553     /**
554      * get title
555      *
556      * @return string
557      */
558     public function getTitle()
559     {
560         return $this->n_fn;
561     }
562
563     /**
564      * returns an array containing the parent neighbours relation objects or record(s) (ids) in the key 'parents'
565      * and containing the children neighbours in the key 'children'
566      *
567      * @return array
568      */
569     public function getPathNeighbours()
570     {
571         $listController = Addressbook_Controller_List::getInstance();
572         $oldAclCheck = $listController->doContainerACLChecks(false);
573
574         $lists = $listController->search(new Addressbook_Model_ListFilter(array(
575             array('field' => 'contact',     'operator' => 'equals', 'value' => $this->getId())
576         )));
577
578         $listMemberRoles = $listController->getMemberRolesBackend()->search(new Addressbook_Model_ListMemberRoleFilter(array(
579             array('field' => 'contact_id',  'operator' => 'equals', 'value' => $this->getId())
580         )));
581
582         $listRoles = array();
583         /** @var Addressbook_Model_ListMemberRole $listMemberRole */
584         foreach($listMemberRoles as $listMemberRole) {
585             $listRoles[$listMemberRole->list_role_id] = $listMemberRole->list_role_id;
586             $lists->removeById($listMemberRole->list_id);
587         }
588
589         if (count($listRoles) > 0) {
590             $listRoles = Addressbook_Controller_ListRole::getInstance()->getMultiple($listRoles, true)->asArray();
591         }
592
593         $result = parent::getPathNeighbours();
594         $result['parents'] = array_merge($result['parents'], $lists->asArray(), $listRoles);
595
596         $listController->doContainerACLChecks($oldAclCheck);
597
598         return $result;
599     }
600 }