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