0011960: print with 300 dpi by default
[tine20] / tine20 / Tinebase / Frontend / Json.php
1 <?php
2 /**
3  * Tine 2.0
4  * 
5  * @package     Tinebase
6  * @subpackage  Server
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Lars Kneschke <l.kneschke@metaways.de>
10  * 
11  */
12
13 /**
14  * Json interface to Tinebase
15  * 
16  * @package     Tinebase
17  * @subpackage  Server
18  */
19 class Tinebase_Frontend_Json extends Tinebase_Frontend_Json_Abstract
20 {
21     const REQUEST_TYPE = 'JSON-RPC';
22     
23     /**
24      *
25      * @var boolean
26      */
27     protected $_hasCaptcha = null;
28     
29     /**
30      * wait for changes
31      * 
32      * @todo do we still need this?
33      */
34     public function ping()
35     {
36         Tinebase_Session::writeClose(true);
37         sleep(10);
38         return array('changes' => 'contacts');
39     }
40     
41     /**
42      * get list of translated country names
43      * 
44      * Wrapper for {@see Tinebase_Core::getCountrylist}
45      * 
46      * @return array list of countrys
47      */
48     public function getCountryList()
49     {
50         return Tinebase_Translation::getCountryList();
51     }
52     
53     /**
54      * returns list of all available translations
55      *
56      * @return array list of all available translations
57      */
58     public function getAvailableTranslations()
59     {
60         $availableTranslations = Tinebase_Translation::getAvailableTranslations();
61         foreach($availableTranslations as &$info) {
62             unset($info['path']);
63         }
64         
65         return array(
66             'results'    => array_values($availableTranslations),
67             'totalcount' => count($availableTranslations)
68         );
69     }
70     
71     /**
72      * sets locale
73      *
74      * @param  string $localeString
75      * @param  bool   $saveaspreference
76      * @param  bool   $setcookie
77      * @return array
78      */
79     public function setLocale($localeString, $saveaspreference, $setcookie)
80     {
81         Tinebase_Core::setupUserLocale($localeString);
82         
83         if ($saveaspreference && is_object(Tinebase_Core::getUser())) {
84             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
85                 . " Saving locale: " . $localeString);
86             Tinebase_Core::getPreference()->{Tinebase_Preference::LOCALE} = $localeString;
87         }
88         
89         // save in cookie (expires in 365 days)
90         if ($setcookie) {
91             setcookie('TINE20LOCALE', $localeString, time()+60*60*24*365);
92         }
93         
94         return array(
95             'success'      => TRUE
96         );
97     }
98     
99     /**
100      * sets timezone
101      *
102      * @param  string $timezoneString
103      * @param  bool   $saveaspreference
104      * @return string
105      */
106     public function setTimezone($timezoneString, $saveaspreference)
107     {
108         $timezone = Tinebase_Core::setupUserTimezone($timezoneString, $saveaspreference);
109         
110         return $timezone;
111     }
112     
113     /**
114      * get users
115      *
116      * @param string $filter
117      * @param string $sort
118      * @param string $dir
119      * @param int $start
120      * @param int $limit
121      * @return array with results array & totalcount (int)
122      */
123     public function getUsers($filter, $sort, $dir, $start, $limit)
124     {
125         $result = array(
126             'results'     => array(),
127             'totalcount'  => 0
128         );
129         
130         if($rows = Tinebase_User::getInstance()->getUsers($filter, $sort, $dir, $start, $limit)) {
131             $result['results']    = $rows->toArray();
132             if($start == 0 && count($result['results']) < $limit) {
133                 $result['totalcount'] = count($result['results']);
134             } else {
135                 //$result['totalcount'] = $backend->getCountByAddressbookId($addressbookId, $filter);
136             }
137         }
138         
139         return $result;
140     }
141     
142     /**
143      * Search for roles
144      *
145      * @param  array $_filter
146      * @param  array $_paging
147      * @return array
148      */
149     public function searchRoles($filter, $paging)
150     {
151         $result = array(
152             'results'     => array(),
153             'totalcount'  => 0
154         );
155         
156         $filter = new Tinebase_Model_RoleFilter(array(
157             'name'        => '%' . $filter[0]['value'] . '%',
158             'description' => '%' . $filter[0]['value'] . '%'
159         ));
160         
161         $paging['sort'] = isset($paging['sort']) ? $paging['sort'] : 'name';
162         $paging['dir'] = isset($paging['dir']) ? $paging['dir'] : 'ASC';
163         
164         $result['results'] = Tinebase_Acl_Roles::getInstance()->searchRoles($filter, new Tinebase_Model_Pagination($paging))->toArray();
165         $result['totalcount'] = Tinebase_Acl_Roles::getInstance()->searchCount($filter);
166         
167         return $result;
168     }
169     
170     /**
171      * change password of user
172      *
173      * @param  string $oldPassword the old password
174      * @param  string $newPassword the new password
175      * @return array
176      */
177     public function changePassword($oldPassword, $newPassword)
178     {
179         $response = array(
180             'success'      => TRUE
181         );
182         
183         try {
184             Tinebase_Controller::getInstance()->changePassword($oldPassword, $newPassword);
185         } catch (Tinebase_Exception $e) {
186             $response = array(
187                 'success'      => FALSE,
188                 'errorMessage' => "New password could not be set! Error: " . $e->getMessage()
189             );
190         }
191         
192         return $response;
193     }
194     
195     /**
196      * clears state
197      *
198      * @param  string $name
199      * @return void
200      */
201     public function clearState($name)
202     {
203         Tinebase_State::getInstance()->clearState($name);
204     }
205     
206     /**
207      * retuns all states
208      *
209      * @return array of name => value
210      */
211     public function loadState()
212     {
213         return Tinebase_State::getInstance()->loadStateInfo();
214     }
215     
216     /**
217      * set state
218      *
219      * @param  string $name
220      * @param  string $value
221      * @return void
222      */
223     public function setState($name, $value)
224     {
225         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Setting state: {$name} -> {$value}");
226         Tinebase_State::getInstance()->setState($name, $value);
227     }
228     
229     /**
230      * adds a new personal tag
231      *
232      * @param  array $tag
233      * @return array
234      */
235     public function saveTag($tag)
236     {
237         $inTag = new Tinebase_Model_Tag($tag);
238         
239         if (strlen($inTag->getId()) < 40) {
240             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' creating tag: ' . print_r($inTag->toArray(), true));
241             $outTag = Tinebase_Tags::getInstance()->createTag($inTag);
242         } else {
243             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' updating tag: ' .print_r($inTag->toArray(), true));
244             $outTag = Tinebase_Tags::getInstance()->updateTag($inTag);
245         }
246         
247         return $outTag->toArray();
248     }
249     
250     /**
251      * Used for updating multiple records
252      * 
253      * @param string $appName
254      * @param string $modelName
255      * @param array $changes
256      * @param array $filter
257      */
258     public function updateMultipleRecords($appName, $modelName, $changes, $filter)
259     {
260         // increase execution time to 30 minutes
261         Tinebase_Core::setExecutionLifeTime(1800);
262         
263         $filterModel = $appName . '_Model_' . $modelName . 'Filter';
264         $data = array();
265         foreach ($changes as $f) {
266             $data[preg_replace('/^customfield_/','#', $f['name'])] = $f['value'];
267         }
268         
269         return $this->_updateMultiple($filter, $data, Tinebase_Core::getApplicationInstance($appName, $modelName), $filterModel);
270     }
271
272     /**
273      * search tags
274      *
275      * @param  array $filter filter array
276      * @param  array $paging pagination info
277      * @return array
278      */
279     public function searchTags($filter, $paging)
280     {
281         $filter = new Tinebase_Model_TagFilter($filter);
282         $paging = new Tinebase_Model_Pagination($paging);
283         
284         return array(
285             'results'    => Tinebase_Tags::getInstance()->searchTags($filter, $paging)->toArray(),
286             // TODO we normally use 'totalcount' (all lower case) - this should be streamlined
287             'totalCount' => Tinebase_Tags::getInstance()->getSearchTagsCount($filter)
288         );
289     }
290     
291     /**
292     * search tags by foreign filter
293     *
294     * @param  array $filterData
295     * @param  string $filterName
296     * @return array
297     */
298     public function searchTagsByForeignFilter($filterData, $filterName)
299     {
300         $filter = $this->_getFilterGroup($filterData, $filterName);
301         
302         $result = Tinebase_Tags::getInstance()->searchTagsByForeignFilter($filter)->toArray();
303         return array(
304             'results'    => $result,
305             'totalCount' => count($result)
306         );
307     }
308     
309     /**
310      * get filter group defined by filterName and filterData
311      *
312      * @param array $_filterData
313      * @param string $_filterName
314      * @return Tinebase_Model_Filter_FilterGroup
315      * @throws Tinebase_Exception_AccessDenied
316      */
317     protected function _getFilterGroup($_filterData, $_filterName)
318     {
319         // NOTE: this function makes a new instance of a class whose name is given by user input.
320         //       we need to do some sanitising first!
321         /** @noinspection PhpUnusedLocalVariableInspection */
322         list($appName, $modelString, $filterGroupName) = explode('_', $_filterName);
323         if ($modelString !== 'Model') {
324             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' spoofing attempt detected, affected account: ' . print_r(Tinebase_Core::getUser()->toArray(), TRUE));
325             die('go away!');
326         }
327         
328         if (! Tinebase_Core::getUser()->hasRight($appName, Tinebase_Acl_Rights_Abstract::RUN)) {
329             throw new Tinebase_Exception_AccessDenied('No right to access application');
330         }
331         
332         $filterGroup = new $_filterName(array());
333         if (! $filterGroup instanceof Tinebase_Model_Filter_FilterGroup) {
334             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' spoofing attempt detected, affected account: ' . print_r(Tinebase_Core::getUser()->toArray(), TRUE));
335             die('go away!');
336         }
337         
338         // at this point we are sure request is save ;-)
339         $filterGroup->setFromArray($_filterData);
340         
341         return $filterGroup;
342     }
343     
344     /**
345      * attach tag to multiple records identified by a filter
346      *
347      * @param array  $filterData
348      * @param string $filterName
349      * @param mixed  $tag       string|array existing and non-existing tag
350      * @return array
351      */
352     public function attachTagToMultipleRecords($filterData, $filterName, $tag)
353     {
354         $this->_longRunningRequest();
355         $filter = $this->_getFilterGroup($filterData, $filterName);
356         
357         Tinebase_Tags::getInstance()->attachTagToMultipleRecords($filter, $tag);
358         return array('success' => true);
359     }
360     
361     /**
362      * attach multiple tags to multiple records identified by a filter
363      *
364      * @param array  $filterData
365      * @param string $filterName
366      * @param mixed  $tags         array of existing and non-existing tags
367      * @return void
368      */
369     public function attachMultipleTagsToMultipleRecords($filterData, $filterName, $tags)
370     {
371         $this->_longRunningRequest();
372         $filter = $this->_getFilterGroup($filterData, $filterName);
373
374         foreach ($tags as $tag) {
375             Tinebase_Tags::getInstance()->attachTagToMultipleRecords(clone $filter, $tag);
376         }
377
378         return array('success' => true);
379     }
380
381     /**
382      * detach tags to multiple records identified by a filter
383      *
384      * @param array  $filterData
385      * @param string $filterName
386      * @param mixed  $tag       string|array existing and non-existing tag
387      * @return array
388      */
389     public function detachTagsFromMultipleRecords($filterData, $filterName, $tag)
390     {
391         $this->_longRunningRequest();
392         $filter = $this->_getFilterGroup($filterData, $filterName);
393         
394         Tinebase_Tags::getInstance()->detachTagsFromMultipleRecords($filter, $tag);
395         return array('success' => true);
396     }
397     
398     /**
399      * search / get notes
400      * - used by activities grid
401      *
402      * @param  array $filter filter array
403      * @param  array $paging pagination info
404      * @return array
405      */
406     public function searchNotes($filter, $paging)
407     {
408         $filter = new Tinebase_Model_NoteFilter($filter);
409         $paging = new Tinebase_Model_Pagination($paging);
410         
411         $records = Tinebase_Notes::getInstance()->searchNotes($filter, $paging, /* ignoreACL = */ false);
412         $result = $this->_multipleRecordsToJson($records);
413         
414         return array(
415             'results'       => $result,
416             'totalcount'    => Tinebase_Notes::getInstance()->searchNotesCount($filter, /* ignoreACL = */ false)
417         );
418     }
419     
420     /**
421      * get note types
422      *
423      */
424     public function getNoteTypes()
425     {
426         $noteTypes = Tinebase_Notes::getInstance()->getNoteTypes();
427         $noteTypes->translate();
428         
429         return array(
430             'results'       => $noteTypes->toArray(),
431             'totalcount'    => count($noteTypes)
432         );
433     }
434     
435     /**
436      * deletes tags identified by an array of identifiers
437      *
438      * @param  array $ids
439      * @return array
440      */
441     public function deleteTags($ids)
442     {
443         Tinebase_Tags::getInstance()->deleteTags($ids);
444         return array('success' => true);
445     }
446
447     /**
448      * authenticate user by username and password
449      *
450      * @param  string $username the username
451      * @param  string $password the password
452      * @return array
453      */
454     public function authenticate($username, $password)
455     {
456         $authResult = Tinebase_Auth::getInstance()->authenticate($username, $password);
457         
458         if ($authResult->isValid()) {
459             $response = array(
460                 'status'    => 'success',
461                 'msg'       => 'authentication succseed',
462                 //'loginUrl'  => 'someurl',
463             );
464         } else {
465             $response = array(
466                 'status'    => 'fail',
467                 'msg'       => 'authentication failed',
468             );
469         }
470         
471         return $response;
472     }
473
474     /**
475      * login user with given username and password
476      *
477      * @param  string $username the username
478      * @param  string $password the password
479      * @param  string $securitycode the security code(captcha)
480      * @return array
481      */
482     public function login($username, $password, $securitycode = null)
483     {
484         Tinebase_Core::startCoreSession();
485         
486         if (is_array(($response = $this->_getCaptchaResponse($securitycode)))) {
487             return $response;
488         }
489         
490         // try to login user
491         $success = Tinebase_Controller::getInstance()->login(
492             $username,
493             $password,
494             Tinebase_Core::get(Tinebase_Core::REQUEST),
495             self::REQUEST_TYPE,
496             $securitycode
497         );
498         
499         if ($success === true) {
500             return $this->_getLoginSuccessResponse($username);
501         } else {
502             return $this->_getLoginFailedResponse();
503         }
504     }
505     
506     /**
507      * Returns TRUE if there is a captcha
508      * @return boolean
509      */
510     protected function _hasCaptcha()
511     {
512         if ($this->_hasCaptcha === null){
513             $this->_hasCaptcha = isset(Tinebase_Core::getConfig()->captcha->count) && Tinebase_Core::getConfig()->captcha->count != 0;
514         }
515         
516         return $this->_hasCaptcha;
517     }
518     
519     /**
520      *
521      * @param string $securitycode
522      * @return array | NULL
523      */
524     protected function _getCaptchaResponse($securitycode)
525     {
526         if ($this->_hasCaptcha()) {
527             $config_count = Tinebase_Core::getConfig()->captcha->count;
528             
529             $count = (isset(Tinebase_Session::getSessionNamespace()->captcha['count'])
530                 ? Tinebase_Session::getSessionNamespace()->captcha['count']
531                 : 1
532             );
533             
534             if ($count >= $config_count) {
535                 $aux = isset(Tinebase_Session::getSessionNamespace()->captcha['code'])
536                     ? Tinebase_Session::getSessionNamespace()->captcha['code']
537                     : null;
538                 
539                 if ($aux != $securitycode) {
540                     $rets = Tinebase_Controller::getInstance()->makeCaptcha(); 
541                     $response = array(
542                         'success'      => false,
543                         'errorMessage' => "Wrong username or password!",
544                         'c1'           => $rets['1']
545                     );
546                     
547                     return $response;
548                 }
549             }
550         }
551         
552         return null;
553     }
554     
555     /**
556      *
557      * @param string $username
558      * @return array
559      */
560     protected function _getLoginSuccessResponse($username)
561     {
562             $response = array(
563                 'success'        => true,
564                 'account'        => Tinebase_Core::getUser()->getPublicUser()->toArray(),
565                 'jsonKey'        => Tinebase_Core::get('jsonKey'),
566                 'welcomeMessage' => "Welcome to Tine 2.0!"
567             );
568              
569             if (Tinebase_Config::getInstance()->get(Tinebase_Config::REUSEUSERNAME_SAVEUSERNAME, 0)) {
570                 // save in cookie (expires in 2 weeks)
571                 setcookie('TINE20LASTUSERID', $username, time()+60*60*24*14);
572             } else {
573                 setcookie('TINE20LASTUSERID', '', 0);
574             }
575
576             $this->_setCredentialCacheCookie();
577             
578         return $response;
579     }
580     
581     /**
582      *
583      * @return array
584      */
585     protected function _getLoginFailedResponse()
586     {
587         $response = array(
588             'success'      => false,
589             'errorMessage' => "Wrong username or password!",
590         );
591         
592         Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->resetCache();
593         
594         if ($this->_hasCaptcha()) {
595             $config_count = Tinebase_Core::getConfig()->captcha->count;
596             
597             if (!isset(Tinebase_Session::getSessionNamespace()->captcha['count'])) {
598                 Tinebase_Session::getSessionNamespace()->captcha['count'] = 1;
599             } else {
600                 Tinebase_Session::getSessionNamespace()->captcha['count'] = Tinebase_Session::getSessionNamespace()->captcha['count'] + 1;
601             }
602             
603             if (Tinebase_Session::getSessionNamespace()->captcha['count'] >= $config_count) {
604                 $rets = Tinebase_Controller::getInstance()->makeCaptcha(); 
605                 $response = array(
606                     'success'      => false,
607                     'errorMessage' => "Wrong username or password!",
608                     'c1'           => $rets['1']
609                 );
610             }
611         } else {
612             Tinebase_Session::destroyAndMantainCookie();
613         }
614         
615         return $response;
616     }
617
618     /**
619      * set credential cache cookie
620      *
621      * @return boolean
622      */
623     protected function _setCredentialCacheCookie()
624     {
625         if (!Tinebase_Core::isRegistered(Tinebase_Core::USERCREDENTIALCACHE)) {
626             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Something went wrong with the CredentialCache / no CC registered.');
627             
628             return false;
629         }
630         
631         Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->setCache(Tinebase_Core::getUserCredentialCache());
632         
633         return true;
634     }
635
636     /**
637      * update user credential cache
638      *
639      * - fires Tinebase_Event_User_ChangeCredentialCache
640      *
641      * @param string $password
642      * @return array
643      */
644     public function updateCredentialCache($password)
645     {
646         $oldCredentialCache = Tinebase_Core::getUserCredentialCache();
647         $credentialCache = Tinebase_Auth_CredentialCache::getInstance()->cacheCredentials(Tinebase_Core::getUser()->accountLoginName, $password);
648         Tinebase_Core::set(Tinebase_Core::USERCREDENTIALCACHE, $credentialCache);
649         
650         $success = $this->_setCredentialCacheCookie();
651         
652         if ($success) {
653             // close session to allow other requests
654             Tinebase_Session::writeClose(true);
655             $event = new Tinebase_Event_User_ChangeCredentialCache($oldCredentialCache);
656             Tinebase_Event::fireEvent($event);
657         }
658         
659         return array(
660             'success'      => $success
661         );
662     }
663
664     /**
665      * destroy session
666      *
667      * @return array
668      */
669     public function logout()
670     {
671         Tinebase_Controller::getInstance()->logout($_SERVER['REMOTE_ADDR']);
672         
673         Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->resetCache();
674         
675         if (Tinebase_Session::isStarted()) {
676             Tinebase_Session::destroyAndRemoveCookie();
677         }
678         
679         $result = array(
680             'success'=> true,
681         );
682         
683         return $result;
684     }
685     
686     /**
687      * Returns registry data of tinebase.
688      * @see Tinebase_Application_Json_Abstract
689      *
690      * @return mixed array 'variable name' => 'data'
691      */
692     public function getRegistryData()
693     {
694         $registryData = $this->_getAnonymousRegistryData();
695         
696         if (Tinebase_Core::isRegistered(Tinebase_Core::USER)) {
697             $userRegistryData = $this->_getUserRegistryData();
698             $registryData += $userRegistryData;
699         }
700         
701         return $registryData;
702     }
703     
704     /**
705      * get anonymous registry
706      * 
707      * @return array
708      */
709     protected function _getAnonymousRegistryData()
710     {
711         $locale = Tinebase_Core::get('locale');
712         $tbFrontendHttp = new Tinebase_Frontend_Http();
713         
714         // default credentials
715         if (isset(Tinebase_Core::getConfig()->login)) {
716             $loginConfig = Tinebase_Core::getConfig()->login;
717             $defaultUsername = (isset($loginConfig->username)) ? $loginConfig->username : '';
718             $defaultPassword = (isset($loginConfig->password)) ? $loginConfig->password : '';
719         } else {
720             $defaultUsername = '';
721             $defaultPassword = '';
722         }
723         
724         $symbols = Zend_Locale::getTranslationList('symbols', $locale);
725         
726         $registryData =  array(
727             'modSsl'           => Tinebase_Auth::getConfiguredBackend() == Tinebase_Auth::MODSSL,
728             'serviceMap'       => $tbFrontendHttp->getServiceMap(),
729             'locale'           => array(
730                 'locale'   => $locale->toString(),
731                 'language' => Zend_Locale::getTranslation($locale->getLanguage(), 'language', $locale),
732                 'region'   => Zend_Locale::getTranslation($locale->getRegion(), 'country', $locale),
733             ),
734             'version'          => array(
735                 'buildType'     => TINE20_BUILDTYPE,
736                 'codeName'      => TINE20_CODENAME,
737                 'packageString' => TINE20_PACKAGESTRING,
738                 'releaseTime'   => TINE20_RELEASETIME,
739                 'filesHash'     => TINE20_BUILDTYPE != 'DEVELOPMENT' ? $tbFrontendHttp->getJsCssHash() : null
740             ),
741             'defaultUsername'   => $defaultUsername,
742             'defaultPassword'   => $defaultPassword,
743             'denySurveys'       => Tinebase_Core::getConfig()->denySurveys,
744             'titlePostfix'      => Tinebase_Config::getInstance()->get(Tinebase_Config::PAGETITLEPOSTFIX),
745             'redirectUrl'       => Tinebase_Config::getInstance()->get(Tinebase_Config::REDIRECTURL),
746             'helpUrl'           => Tinebase_Core::getConfig()->helpUrl,
747             'maxFileUploadSize' => Tinebase_Helper::convertToBytes(ini_get('upload_max_filesize')),
748             'maxPostSize'       => Tinebase_Helper::convertToBytes(ini_get('post_max_size')),
749             'thousandSeparator' => $symbols['group'],
750             'decimalSeparator'  => $symbols['decimal'],
751             'filesystemAvailable' => Tinebase_Core::isFilesystemAvailable(),
752         );
753         
754         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
755             . ' Anonymous registry: ' . print_r($registryData, TRUE));
756         
757         return $registryData;
758     }
759     
760     /**
761      * get user registry
762      * 
763      * @return array
764      */
765     protected function _getUserRegistryData()
766     {
767         $user = Tinebase_Core::getUser();
768         $userContactArray = array();
769         if (Tinebase_Application::getInstance()->isInstalled('Addressbook') === true) {
770             try {
771                 $userContactArray = Addressbook_Controller_Contact::getInstance()->getContactByUserId($user->getId(), TRUE)->toArray();
772             } catch (Addressbook_Exception_NotFound $aenf) {
773                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) /** @noinspection PhpUndefinedMethodInspection */
774                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
775                     . ' User not found in Addressbook: ' . $user->accountDisplayName);
776             }
777         }
778         
779         try {
780             $persistentFilters = Tinebase_Frontend_Json_PersistentFilter::getAllPersistentFilters();
781         } catch (Tinebase_Exception_NotFound $tenf) {
782             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
783                 . " Failed to fetch persistent filters. Exception: \n". $tenf);
784             $persistentFilters = array();
785         }  catch (Exception $e) {
786             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
787                 . " Failed to fetch persistent filters. Exception: \n". $e);
788             $persistentFilters = array();
789         }
790
791         
792         $userRegistryData = array(
793             'timeZone'           => Tinebase_Core::getUserTimezone(),
794             'currentAccount'     => $user->toArray(),
795             'userContact'        => $userContactArray,
796             'accountBackend'     => Tinebase_User::getConfiguredBackend(),
797             'jsonKey'            => Tinebase_Core::get('jsonKey'),
798             'userApplications'   => $user->getApplications()->toArray(),
799             'NoteTypes'          => $this->getNoteTypes(),
800             'stateInfo'          => Tinebase_State::getInstance()->loadStateInfo(),
801             'mustchangepw'       => $user->mustChangePassword(),
802             'confirmLogout'      => Tinebase_Core::getPreference()->getValue(Tinebase_Preference::CONFIRM_LOGOUT, 1),
803             'advancedSearch'     => Tinebase_Core::getPreference()->getValue(Tinebase_Preference::ADVANCED_SEARCH, 0),
804             'persistentFilters'  => $persistentFilters,
805             'userAccountChanged' => Tinebase_Controller::getInstance()->userAccountChanged(),
806         );
807         
808         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
809             . ' User registry: ' . print_r($userRegistryData, TRUE));
810         
811         return $userRegistryData;
812     }
813
814     /**
815      * Returns registry data of all applications current user has access to
816      * @see Tinebase_Application_Json_Abstract
817      *
818      * @return mixed array 'variable name' => 'data'
819      */
820     public function getAllRegistryData()
821     {
822         $registryData = array();
823         
824         if (Tinebase_Core::getUser()) {
825             $userApplications = Tinebase_Core::getUser()->getApplications(TRUE);
826             $clientConfig = Tinebase_Config::getInstance()->getClientRegistryConfig();
827             
828             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE))
829                 /** @noinspection PhpUndefinedFieldInspection */
830                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
831                . ' User applications to fetch registry for: ' . print_r($userApplications->name, TRUE));
832
833             /** @noinspection PhpUndefinedFieldInspection */
834             if (! in_array('Tinebase', $userApplications->name)) {
835                 Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' User has no permissions to run Tinebase.');
836                 $this->logout();
837                 throw new Tinebase_Exception_AccessDenied('User has no permissions to run Tinebase');
838             }
839             
840             foreach ($userApplications as $application) {
841                 $jsonAppName = $application->name . '_Frontend_Json';
842                 
843                 if (class_exists($jsonAppName)) {
844                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
845                         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Getting registry data for app ' . $application->name);
846                     }
847                     
848                     try {
849                         $applicationJson = new $jsonAppName();
850                         $registryData[$application->name] = ((isset($registryData[$application->name]) || array_key_exists($application->name, $registryData)))
851                             ? array_merge_recursive($registryData[$application->name], $applicationJson->getRegistryData()) 
852                             : $applicationJson->getRegistryData();
853                     
854                     } catch (Exception $e) {
855                         Tinebase_Exception::log($e);
856                         if (! $e instanceof Tinebase_Exception_AccessDenied && ! in_array($application->name, array('Tinebase', 'Addressbook', 'Admin'))) {
857                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Disabling ' . $application->name . ': ' . $e);
858                             Tinebase_Application::getInstance()->setApplicationState(array($application->getId()), Tinebase_Application::DISABLED);
859                         }
860                         unset($registryData[$application->name]);
861                         continue;
862                     }
863                     
864                     $registryData[$application->name]['rights'] = Tinebase_Core::getUser()->getRights($application->name);
865                     $registryData[$application->name]['config'] = isset($clientConfig[$application->name]) ? $clientConfig[$application->name]->toArray() : array();
866                     $registryData[$application->name]['models'] = $applicationJson->getModelsConfiguration();
867                     $registryData[$application->name]['defaultModel'] = $applicationJson->getDefaultModel();
868                     
869                     foreach ($applicationJson->getRelatableModels() as $relModel) {
870                         $registryData[$relModel['ownApp']]['relatableModels'][] = $relModel;
871                     }
872
873                     // @todo do we need this for all apps?
874                     $exportDefinitions = Tinebase_ImportExportDefinition::getInstance()->getExportDefinitionsForApplication($application);
875                     $registryData[$application->name]['exportDefinitions'] = array(
876                         'results'               => $exportDefinitions->toArray(),
877                         'totalcount'            => count($exportDefinitions),
878                     );
879                     
880                     $customfields = Tinebase_CustomField::getInstance()->getCustomFieldsForApplication($application);
881                     Tinebase_CustomField::getInstance()->resolveConfigGrants($customfields);
882                     $registryData[$application->name]['customfields'] = $customfields->toArray();
883                     
884                     // add preferences for app
885                     $appPrefs = Tinebase_Core::getPreference($application->name);
886                     if ($appPrefs !== NULL) {
887                         $allPrefs = $appPrefs->getAllApplicationPreferences();
888                         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
889                             . ' ' . print_r($allPrefs, TRUE));
890                         
891                         foreach ($allPrefs as $pref) {
892                             try {
893                                 $registryData[$application->name]['preferences'][$pref] = $appPrefs->{$pref};
894                             } catch (Exception $e) {
895                                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not get ' . $pref . '  preference: ' . $e);
896                             }
897                         }
898                     }
899                 }
900             }
901         } else {
902             $registryData['Tinebase'] = $this->getRegistryData();
903         }
904         
905         return $registryData;
906     }
907     
908     /**
909      * search / get custom field values
910      *
911      * @param  array $filter filter array
912      * @param  array $paging pagination info
913      * @return array
914      */
915     public function searchCustomFieldValues($filter, $paging)
916     {
917         $result = $this->_search($filter, $paging, Tinebase_CustomField::getInstance(), 'Tinebase_Model_CustomField_ValueFilter');
918         return $result;
919     }
920     
921     /************************ preferences functions ***************************/
922     
923     /**
924      * search preferences
925      *
926      * @param  string $applicationName
927      * @param  array  $filter json encoded
928      * @return array
929      */
930     public function searchPreferencesForApplication($applicationName, $filter)
931     {
932         $decodedFilter = $this->_prepareParameter($filter);
933         
934         $filter = new Tinebase_Model_PreferenceFilter();
935         if (! empty($decodedFilter)) {
936             $filter->setFromArrayInUsersTimezone($decodedFilter);
937         }
938         $appId = Tinebase_Application::getInstance()->getApplicationByName($applicationName)->getId();
939         $filter->addFilter($filter->createFilter(array('field'     => 'application_id',  'operator'  => 'equals', 'value'     => $appId)));
940         
941         $backend = Tinebase_Core::getPreference($applicationName);
942         if ($backend) {
943             $records = $backend->search($filter);
944             
945             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
946                 . ' Got ' . count($records) . ' preferences for app ' . $applicationName);
947             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
948                 . ' ' . print_r($records->toArray(), TRUE));
949             
950             $result = $this->_multipleRecordsToJson($records, $filter);
951             
952             // add translated labels and descriptions
953             $translations = $backend->getTranslatedPreferences();
954             foreach ($result as $key => $prefArray) {
955                 if (isset($translations[$prefArray['name']])) {
956                     $result[$key] = array_merge($prefArray, $translations[$prefArray['name']]);
957                 } else {
958                     $result[$key] = array_merge($prefArray, array('label' => $prefArray['name']));
959                 }
960             }
961             
962             // sort prefs by definition
963             $allPrefs = (array) $backend->getAllApplicationPreferences();
964             usort($result, function($a, $b) use ($allPrefs) {
965                 $a = (int) array_search($a['name'], $allPrefs);
966                 $b = (int) array_search($b['name'], $allPrefs);
967                 
968                 if ($a == $b) {
969                     return 0;
970                 }
971                 return ($a < $b) ? -1 : 1;
972             });
973             
974         } else {
975             $result = array();
976         }
977         
978         return array(
979             'results'       => $result,
980             'totalcount'    => count($result)
981         );
982     }
983     
984     /**
985      * save preferences for application
986      *
987      * @param string    $data       json encoded preferences data
988      * @param bool      $adminMode  submit in admin mode?
989      * @return array with the changed prefs
990      *
991      * @todo move saving of user values to preferences controller
992      */
993     public function savePreferences($data, $adminMode)
994     {
995         $decodedData = $this->_prepareParameter($data);
996         
997         $result = array();
998         foreach ($decodedData as $applicationName => $data) {
999             
1000             if ($applicationName == 'Tinebase.UserProfile') {
1001                 $userProfileData = array();
1002                 foreach($data as $fieldName => $valueArray) {
1003                     $userProfileData[$fieldName] = $valueArray['value'];
1004                 }
1005                 $this->updateUserProfile($userProfileData);
1006                 
1007             } else {
1008                 $backend = Tinebase_Core::getPreference($applicationName);
1009                 if ($backend !== NULL) {
1010                     if ($adminMode) {
1011                         $backend->saveAdminPreferences($data);
1012                         // TODO return preference values?
1013                         $result = array();
1014                     } else {
1015                         // set user prefs
1016                         foreach ($data as $name => $value) {
1017                             try {
1018                                 $backend->doSpecialJsonFrontendActions($this, $name, $value['value'], $applicationName);
1019                                 $backend->$name = $value['value'];
1020                                 $result[$applicationName][] = array('name' => $name, 'value' => $backend->$name);
1021                             } catch (Exception $e) {
1022                                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
1023                                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' '
1024                                         . 'Could not save preference '. $name . ' -> ' . $e->getMessage());
1025                             }
1026                         }
1027                     }
1028                 }
1029             }
1030         }
1031         
1032         return array(
1033             'status'    => 'success',
1034             'results'   => $result
1035         );
1036     }
1037
1038     /**
1039      * get profile of current user
1040      *
1041      * @param string $userId
1042      * @return array
1043      */
1044     public function getUserProfile($userId)
1045     {
1046         // NOTE: $userProfile is a contact where non readable fields are clearad out!
1047         $userProfile = Tinebase_UserProfile::getInstance()->get($userId);
1048         
1049         // NOTE: This hurts! We don't have methods to call in our frontends yet which convert
1050         //       a record to the json representaion :( Thus image link will be broken!
1051         $userProfile->setTimezone(Tinebase_Core::getUserTimezone());
1052         
1053         return array(
1054             'userProfile'      => $userProfile->toArray(),
1055             'readableFields'   => Tinebase_UserProfile::getInstance()->getReadableFields(),
1056             'updateableFields' => Tinebase_UserProfile::getInstance()->getUpdateableFields(),
1057         );
1058     }
1059     
1060     /**
1061      * update user profile
1062      *
1063      * @param  array $profileData
1064      * @return array
1065      */
1066     public function updateUserProfile($profileData)
1067     {
1068         $contact = new Addressbook_Model_Contact(array(), TRUE);
1069         $contact->setFromJsonInUsersTimezone($profileData);
1070         
1071         // NOTE: $userProfile is a contact where non readable fields are clearad out!
1072         $userProfile = Tinebase_UserProfile::getInstance()->update($contact);
1073         
1074         // NOTE: This hurts! We don't have methods to call in our frontends yet which convert
1075         //       a record to the json representaion :( Thus image link will be broken!
1076         $userProfile->setTimezone(Tinebase_Core::getUserTimezone());
1077         return $userProfile->toArray();
1078     }
1079     
1080     /**
1081      * dummy function to measure speed of framework initialization
1082      */
1083     public function void()
1084     {
1085         return array();
1086     }
1087     
1088     /**
1089      * gets the userProfile config
1090      *
1091      * @return @array
1092      */
1093     public function getUserProfileConfig()
1094     {
1095         return array(
1096             'possibleFields'   => array_values(Tinebase_UserProfile::getInstance()->getPossibleFields()),
1097             'readableFields'   => array_values(Tinebase_UserProfile::getInstance()->getReadableFields()),
1098             'updateableFields' => array_values(Tinebase_UserProfile::getInstance()->getUpdateableFields()),
1099         );
1100     }
1101     
1102     /**
1103      * saves userProfile config
1104      *
1105      * @param array $configData
1106      */
1107     public function setUserProfileConfig($configData)
1108     {
1109         Tinebase_UserProfile::getInstance()->setReadableFields($configData['readableFields']);
1110         Tinebase_UserProfile::getInstance()->setUpdateableFields($configData['updateableFields']);
1111     }
1112     
1113     /**
1114      * switch to another user's account
1115      * 
1116      * @param string $loginName
1117      * @return array
1118      */
1119     public function changeUserAccount($loginName)
1120     {
1121         $result = Tinebase_Controller::getInstance()->changeUserAccount($loginName);
1122         return array(
1123             'success' => $result
1124         );
1125     }
1126     
1127     /************************ department functions **************************/
1128     
1129     /**
1130      * search / get departments
1131      *
1132      * @param  array $filter filter array
1133      * @param  array $paging pagination info
1134      * @return array
1135      */
1136     public function searchDepartments($filter, $paging)
1137     {
1138         $result = $this->_search($filter, $paging, Tinebase_Department::getInstance(), 'Tinebase_Model_DepartmentFilter');
1139         return $result;
1140     }
1141     
1142     /************************* relation functions ***************************/
1143     
1144     /**
1145      * get all relations of a given record
1146      *
1147      * @param  string       $model         own model to get relations for
1148      * @param  string       $id            own id to get relations for
1149      * @param  string       $degree        only return relations of given degree
1150      * @param  array        $type          only return relations of given type
1151      * @param  string       $relatedModel  only return relations having this related model
1152      * @return array
1153      */
1154     public function getRelations($model, $id, $degree = NULL, $type = array(), $relatedModel = NULL)
1155     {
1156         if (! is_array($type)) {
1157             $type = array();
1158         }
1159         $relations = Tinebase_Relations::getInstance()->getRelations($model, 'Sql', $id, $degree, $type, false, $relatedModel);
1160
1161         // @TODO we still have no converter for relations :-(
1162         // -> related records returned here are different to the records returned by the apps itself!
1163         // -> this problem also applies to to generic json converter!
1164         if (count($relations) > 0) {
1165             $relations->setTimezone(Tinebase_Core::getUserTimezone());
1166             $relations->bypassFilters = true;
1167             $result = $relations->toArray();
1168         } else {
1169             $result = array();
1170         }
1171         return array(
1172             'results'       => array_values($result),
1173             'totalcount'    => count($result),
1174         );
1175     }
1176     
1177     /************************ config functions ******************************/
1178     
1179     /**
1180      * get config settings for application
1181      *
1182      * @param string $id application name
1183      * @return array
1184      */
1185     public function getConfig($id)
1186     {
1187         $controllerName = $id . '_Controller';
1188         $appController = Tinebase_Controller_Abstract::getController($controllerName);
1189         
1190         return array(
1191             'id'        => $id,
1192             'settings'  => $appController->getConfigSettings(TRUE),
1193         );
1194     }
1195     
1196     /**
1197      * save application config
1198      *
1199      * @param array $recordData
1200      * @return array
1201      */
1202     public function saveConfig($recordData)
1203     {
1204         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($recordData, TRUE));
1205         
1206         $controllerName = $recordData['id'] . '_Controller';
1207         $appController = Tinebase_Controller_Abstract::getController($controllerName);
1208         $appController->saveConfigSettings($recordData['settings']);
1209         
1210         return $this->getConfig($recordData['id']);
1211     }
1212     
1213     /************************ tempFile functions ******************************/
1214     
1215     /**
1216      * joins all given tempfiles in given order to a single new tempFile
1217      *
1218      * @param array $tempFilesData of tempfiles arrays $tempFiles
1219      * @return array new tempFile
1220      */
1221     public function joinTempFiles($tempFilesData)
1222     {
1223         $tempFileRecords = new Tinebase_Record_RecordSet('Tinebase_Model_TempFile');
1224         foreach($tempFilesData as $tempFileData) {
1225             $record = new Tinebase_Model_TempFile(array(), TRUE);
1226             $record->setFromJsonInUsersTimezone($tempFileData);
1227             $tempFileRecords->addRecord($record);
1228         }
1229         
1230         $joinedTempFile = Tinebase_TempFile::getInstance()->joinTempFiles($tempFileRecords);
1231         
1232         return $joinedTempFile->toArray();
1233     }
1234     
1235     /************************ protected functions ***************************/
1236     
1237     /**
1238      * returns multiple records prepared for json transport
1239      *
1240      * @param Tinebase_Record_RecordSet $_records Tinebase_Record_Abstract
1241      * @param Tinebase_Model_Filter_FilterGroup $_filter
1242      * @param Tinebase_Model_Pagination $_pagination
1243      * @return array data
1244      */
1245     protected function _multipleRecordsToJson(Tinebase_Record_RecordSet $_records, $_filter = NULL, $_pagination = NULL)
1246     {
1247         if (count($_records) == 0) {
1248             return array();
1249         }
1250         
1251         switch ($_records->getRecordClassName()) {
1252             case 'Tinebase_Model_Preference':
1253                 $accountFilterArray = $_filter->getFilter('account')->toArray();
1254                 $adminMode = ($accountFilterArray['value']['accountId'] == 0 && $accountFilterArray['value']['accountType'] == Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE);
1255                 foreach ($_records as $record) {
1256                     if (! isset($app) || $record->application_id != $app->getId()) {
1257                         $app = Tinebase_Application::getInstance()->getApplicationById($record->application_id);
1258                     }
1259                     $preference = Tinebase_Core::getPreference($app->name, TRUE);
1260                     $preference->resolveOptions($record);
1261                     if ($record->type == Tinebase_Model_Preference::TYPE_DEFAULT || ! $adminMode && $record->type == Tinebase_Model_Preference::TYPE_ADMIN) {
1262                         $record->value = Tinebase_Model_Preference::DEFAULT_VALUE;
1263                     }
1264                 }
1265                 break;
1266         }
1267         
1268         $result = parent::_multipleRecordsToJson($_records, $_filter, $_pagination);
1269         return $result;
1270     }
1271     
1272     /**
1273      * return autocomplete suggestions for a given recordclass, the property and value
1274      *
1275      * @param string $appName
1276      * @param string $modelName
1277      * @param string $property
1278      * @param string $startswith
1279      * 
1280      * @return array
1281      */
1282     public function autoComplete($appName, $modelName, $property, $startswith)
1283     {
1284         $recordClassName = $appName . '_Model_' . $modelName;
1285         $controller      = Tinebase_Core::getApplicationInstance($appName, $modelName);
1286         $filterClassName = $recordClassName . 'Filter';
1287         
1288         if (! class_exists($recordClassName)) {
1289             throw new Tinebase_Exception_InvalidArgument('A record class for the given appName and modelName does not exist!');
1290         }
1291         
1292         if (! $controller) {
1293             throw new Tinebase_Exception_InvalidArgument('A controller for the given appName and modelName does not exist!');
1294         }
1295         
1296         if (! class_exists($filterClassName)) {
1297             throw new Tinebase_Exception_InvalidArgument('A filter for the given appName and modelName does not exist!');
1298         }
1299         
1300         if (! in_array($property, $recordClassName::getAutocompleteFields())) {
1301             throw new Tinebase_Exception_UnexpectedValue('bad property name');
1302         }
1303         
1304         $filter = new $filterClassName(array(
1305             array('field' => $property, 'operator' => 'startswith', 'value' => $startswith),
1306         ));
1307         
1308         $paging = new Tinebase_Model_Pagination(array('sort' => $property));
1309         
1310         $values = array_unique($controller->search($filter, $paging)->{$property});
1311         
1312         $result = array(
1313             'results'   => array(),
1314             'totalcount' => count($values)
1315         );
1316         
1317         foreach($values as $value) {
1318             $result['results'][] = array($property => $value);
1319         }
1320         
1321         return $result;
1322     }
1323
1324     /**
1325      * Toogles advanced search preference
1326      *
1327      * @param $state
1328      * @return true
1329      */
1330     public function toogleAdvancedSearch($state)
1331     {
1332         Tinebase_Core::getPreference()->setValue(Tinebase_Preference::ADVANCED_SEARCH, (int)$state);
1333         return $state == Tinebase_Core::getPreference()->getValue(Tinebase_Preference::ADVANCED_SEARCH, 0);
1334     }
1335 }