c9a6e8003eb484ac8b755b9d6e97849220f57cc6
[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     * overwrite constructor to add more filters
271     *
272     * @param mixed $_data
273     * @param bool $_bypassFilters
274     * @param mixed $_convertDates
275     */
276     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
277     {
278         // set geofields to NULL if empty
279         $geoFields = array('adr_one_lon', 'adr_one_lat', 'adr_two_lon', 'adr_two_lat');
280         foreach ($geoFields as $geoField) {
281             $this->_filters[$geoField]        = new Zend_Filter_Empty(NULL);
282         }
283     
284         parent::__construct($_data, $_bypassFilters, $_convertDates);
285     }
286     
287     /**
288      * returns prefered email address of given contact
289      * 
290      * @return string
291      */
292     public function getPreferedEmailAddress()
293     {
294         // prefer work mail over private mail till we have prefs for this
295         return $this->email ? $this->email : $this->email_home;
296     }
297     
298     /**
299      * (non-PHPdoc)
300      * @see Tinebase/Record/Tinebase_Record_Abstract#setFromArray($_data)
301      */
302     public function setFromArray(array $_data)
303     {
304         $_data = $this->_resolveAutoValues($_data);
305         parent::setFromArray($_data);
306     }
307     
308     /**
309      * Resolves the auto values n_fn and n_fileas
310      * @param array $_data
311      * @return array $_data
312      */
313     protected function _resolveAutoValues(array $_data) {
314         if (! (isset($_data['org_name']) || array_key_exists('org_name', $_data))) {
315             $_data['org_name'] = '';
316         }
317
318         // try to guess name from n_fileas
319         // TODO: n_fn
320         if (empty($_data['org_name']) && empty($_data['n_family'])) {
321             if (! empty($_data['n_fileas'])) {
322                 $names = preg_split('/\s*,\s*/', $_data['n_fileas']);
323                 $_data['n_family'] = $names[0];
324                 if (empty($_data['n_given'])&& isset($names[1])) {
325                     $_data['n_given'] = $names[1];
326                 }
327             }
328         }
329         
330         // always update fileas and fn
331         $_data['n_fileas'] = (!empty($_data['n_family'])) ? $_data['n_family']
332             : ((! empty($_data['org_name'])) ? $_data['org_name']
333             : ((isset($_data['n_fileas'])) ? $_data['n_fileas'] : ''));
334
335         if (!empty($_data['n_given'])) {
336             $_data['n_fileas'] .= ', ' . $_data['n_given'];
337         }
338
339         $_data['n_fn'] = (!empty($_data['n_family'])) ? $_data['n_family']
340             : ((! empty($_data['org_name'])) ? $_data['org_name']
341             : ((isset($_data['n_fn'])) ? $_data['n_fn'] : ''));
342
343         if (!empty($_data['n_given'])) {
344             $_data['n_fn'] = $_data['n_given'] . ' ' . $_data['n_fn'];
345         }
346         return $_data;
347     }
348     
349     /**
350      * Overwrites the __set Method from Tinebase_Record_Abstract
351      * Also sets n_fn and n_fileas when org_name, n_given or n_family should be set
352      * @see Tinebase_Record_Abstract::__set()
353      */
354     public function __set($_name, $_value) {
355         
356         switch ($_name) {
357             case 'n_given':
358                 $resolved = $this->_resolveAutoValues(array('n_given' => $_value, 'n_family' => $this->__get('n_family'), 'org_name' => $this->__get('org_name')));
359                 parent::__set('n_fn', $resolved['n_fn']);
360                 parent::__set('n_fileas', $resolved['n_fileas']);
361                 break;
362             case 'n_family':
363                 $resolved = $this->_resolveAutoValues(array('n_family' => $_value, 'n_given' => $this->__get('n_given'), 'org_name' => $this->__get('org_name')));
364                 parent::__set('n_fn', $resolved['n_fn']);
365                 parent::__set('n_fileas', $resolved['n_fileas']);
366                 break;
367             case 'org_name':
368                 $resolved = $this->_resolveAutoValues(array('org_name' => $_value, 'n_given' => $this->__get('n_given'), 'n_family' => $this->__get('n_family')));
369                 parent::__set('n_fn', $resolved['n_fn']);
370                 parent::__set('n_fileas', $resolved['n_fileas']);
371                 break;
372             default:
373                 // normalize telephone numbers
374                 if (!empty($_value) && strpos($_name, 'tel_') === 0 && strpos($_name, '_normalized') === false) {
375                     parent::__set($_name . '_normalized', static::normalizeTelephoneNoCountry($_value));
376                 }
377                 break;
378         }
379         
380         parent::__set($_name, $_value);
381     }
382
383     /**
384      * normalizes telephone numbers and removes country part
385      * result will be of format 0xxxxxxxxx (only digits)
386      *
387      * @param  string $telNumber
388      * @return string|null
389      */
390     public static function normalizeTelephoneNoCountry($telNumber)
391     {
392         $val = trim($telNumber);
393
394         // replace leading + with 00
395         if ($val[0] === '+') {
396             $val = '00' . mb_substr($val, 1);
397         }
398
399         // remove any non digit characters
400         $val = preg_replace('/\D+/u', '', $val);
401
402         // if not at least 5 digits, stop where
403         if (strlen($val) < 5)
404             return null;
405
406         // replace 00 with +
407         if ($val[0] === '0' && $val[1] === '0') {
408             $val = '+' . mb_substr($val, 2);
409         }
410
411         // normalize to remove leading country codes and make the number start with 0
412         if ($val[0] === '+') {
413             $val = str_replace(static::$countryCodes, '0', $val);
414         } elseif($val[0] !== '0') {
415             $val = '0' . $val;
416         }
417
418         // in case the country codes was not recognized...
419         if ($val[0] === '+') {
420             $val = '0' . mb_substr($val, 1);
421         }
422
423         return $val;
424     }
425
426     /**
427      * fills a contact from json data
428      *
429      * @param array $_data record data
430      * @return void
431      * 
432      * @todo timezone conversion for birthdays?
433      * @todo move this to Addressbook_Convert_Contact_Json
434      */
435     protected function _setFromJson(array &$_data)
436     {
437         $this->_setContactImage($_data);
438         
439         // unset if empty
440         // @todo is this still needed?
441         if (empty($_data['id'])) {
442             unset($_data['id']);
443         }
444     }
445     
446     /**
447      * set contact image
448      * 
449      * @param array $_data
450      */
451     protected function _setContactImage(&$_data)
452     {
453         if (! isset($_data['jpegphoto']) || $_data['jpegphoto'] === '') {
454             return;
455         }
456         
457         $imageParams = Tinebase_ImageHelper::parseImageLink($_data['jpegphoto']);
458         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' image params:' . print_r($imageParams, TRUE));
459         if ($imageParams['isNewImage']) {
460             try {
461                 $_data['jpegphoto'] = Tinebase_ImageHelper::getImageData($imageParams);
462             } catch(Tinebase_Exception_UnexpectedValue $teuv) {
463                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not add contact image: ' . $teuv->getMessage());
464                 unset($_data['jpegphoto']);
465             }
466         } else {
467             unset($_data['jpegphoto']);
468         }
469     }
470
471     /**
472      * set small contact image
473      *
474      * @param $newPhotoBlob
475      * @param $maxSize
476      */
477     public function setSmallContactImage($newPhotoBlob, $maxSize = self::SMALL_PHOTO_SIZE)
478     {
479         if ($this->getId()) {
480             try {
481                 $currentPhoto = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId())->getBlob('image/jpeg', $maxSize);
482             } catch (Exception $e) {
483                 // no current photo
484             }
485         }
486
487         if (isset($currentPhoto) && $currentPhoto == $newPhotoBlob) {
488             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
489                 . " Photo did not change -> preserving current photo");
490         } else {
491             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__
492                 . " Setting new contact photo (" . strlen($newPhotoBlob) . "KB)");
493             $this->jpegphoto = $newPhotoBlob;
494         }
495     }
496
497     /**
498      * return small contact image for sync
499      *
500      * @param $maxSize
501      *
502      * @return string
503      * @throws Tinebase_Exception_InvalidArgument
504      * @throws Tinebase_Exception_NotFound
505      */
506     public function getSmallContactImage($maxSize = self::SMALL_PHOTO_SIZE)
507     {
508         $image = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId());
509         return $image->getBlob('image/jpeg', $maxSize);
510     }
511
512     /**
513      * get title
514      *
515      * @return string
516      */
517     public function getTitle()
518     {
519         return $this->n_fn;
520     }
521 }