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