0013214: allow to set fixed calendars as user preference
[tine20] / tine20 / Tinebase / Preference / Abstract.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  Preference
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
10  *
11  * @todo        make this a real controller + singleton (create extra sql backend)
12  * @todo        add getAllprefsForApp (similar to config) to get all prefs for the registry in one request
13  * @todo        add getPreference function that returns the complete record
14  * @todo        allow free-form preferences
15  * @todo        support group preferences
16  */
17
18 /**
19  * abstract backend for preferences
20  *
21  * @package     Tinebase
22  * @subpackage  Preference
23  */
24 abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstract
25 {
26     /**
27      * yes no options
28      *
29      * @staticvar string
30      */
31     const YES_NO_OPTIONS = 'yesnoopt';
32
33     /**
34      * default persistent filter
35      */
36     const DEFAULTPERSISTENTFILTER = 'defaultpersistentfilter';
37     
38     /**
39      * name of the filter representing the last used filter
40      */
41     const LASTUSEDFILTER = '_lastusedfilter_';
42     
43     /**
44      * default container options
45      *
46      * @staticvar string
47      */
48     const DEFAULTCONTAINER_OPTIONS = 'defaulcontaineropt';
49     
50     /**************************** backend settings *********************************/
51
52     /**
53      * Table name without prefix
54      *
55      * @var string
56      */
57     protected $_tableName = 'preferences';
58
59     /**
60      * Model name
61      *
62      * @var string
63      */
64     protected $_modelName = 'Tinebase_Model_Preference';
65     
66     /**
67      * application
68      *
69      * @var string
70      */
71     protected $_application = 'Tinebase';
72     
73     /**
74      * preference names that have no default option
75      * 
76      * @var array
77      */
78     protected $_skipDefaultOption = array();
79     
80     /**************************** public abstract functions *********************************/
81
82     /**
83      * get all possible application prefs
84      * - every app should overwrite this
85      *
86      * @return  array   all application prefs
87      */
88     abstract public function getAllApplicationPreferences();
89
90     /**
91      * get translated right descriptions
92      *
93      * @return  array with translated descriptions for this applications preferences
94      */
95     abstract public function getTranslatedPreferences();
96
97     /**
98      * get preference defaults if no default is found in the database
99      *
100      * @param string $_preferenceName
101      * @param string|Tinebase_Model_User $_accountId
102      * @param string $_accountType
103      * @return Tinebase_Model_Preference
104      */
105     abstract public function getApplicationPreferenceDefaults($_preferenceName, $_accountId = NULL, $_accountType = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER);
106
107     /**************************** public interceptior functions *********************************/
108
109     /**
110      * get interceptor (alias for getValue())
111      *
112      * @param string $_preferenceName
113      * @return string
114      */
115     public function __get($_preferenceName)
116     {
117         return $this->getValue($_preferenceName);
118     }
119
120     /**
121      * set interceptor (alias for setValue())
122      *
123      * @param string $_preferenceName
124      * @param string $_value
125      */
126     public function __set($_preferenceName, $_value) {
127         if (in_array($_preferenceName, $this->getAllApplicationPreferences())) {
128             $this->setValue($_preferenceName, $_value);
129         }
130     }
131
132     /**************************** public functions *********************************/
133
134     /**
135      * search for preferences
136      * 
137      * @param  Tinebase_Model_Filter_FilterGroup    $_filter
138      * @param  Tinebase_Model_Pagination            $_pagination
139      * @param  boolean                              $_onlyIds
140      * @return Tinebase_Record_RecordSet|array of preferences / pref ids
141      */
142     public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_onlyIds = FALSE)
143     {
144         if ($_filter === null) {
145             $_filter = new Tinebase_Model_PreferenceFilter();
146         }
147         
148         // make sure account is set in filter
149         $userId = Tinebase_Core::getUser()->getId();
150         if (! $_filter->isFilterSet('account')) {
151             $accountFilter = $_filter->createFilter('account', 'equals', array(
152                 'accountId'   => (string) $userId, 
153                 'accountType' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER
154             ));
155             $_filter->addFilter($accountFilter);
156         } else {
157             // only admins can search for other users prefs
158             $accountFilter = $_filter->getAccountFilter();
159             $accountFilterValue = $accountFilter->getValue();
160             if ($accountFilterValue['accountId'] != $userId && $accountFilterValue['accountType'] == Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
161                 if (!Tinebase_Acl_Roles::getInstance()->hasRight($this->_application, Tinebase_Core::getUser()->getId(), Tinebase_Acl_Rights_Abstract::ADMIN)) {
162                     return new Tinebase_Record_RecordSet('Tinebase_Model_Preference');
163                 }
164             }
165         }
166         
167         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_filter->toArray(), TRUE));
168         
169         $paging = new Tinebase_Model_Pagination(array(
170             'dir'       => 'ASC',
171             'sort'      => array('name')
172         ));
173         
174         $allPrefs = parent::search($_filter, $_pagination, $_onlyIds);
175
176         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r((is_array($allPrefs)) ? $allPrefs : $allPrefs->toArray(), TRUE));
177         
178         if (! $_onlyIds) {
179             $this->_addDefaultAndRemoveUndefinedPrefs($allPrefs, $_filter);
180             
181             // get single matching preferences for each different pref
182             $result = $this->getMatchingPreferences($allPrefs);
183         } else {
184             $result = $allPrefs;
185         }
186         
187         return $result;
188     }
189     
190     /**
191      * add default preferences to and remove undefined preferences from record set
192      * 
193      * @param Tinebase_Record_RecordSet $_prefs
194      * @param Tinebase_Model_Filter_FilterGroup $_filter
195      */
196     protected function _addDefaultAndRemoveUndefinedPrefs(Tinebase_Record_RecordSet $_prefs, Tinebase_Model_Filter_FilterGroup $_filter)
197     {
198         $allAppPrefs = $this->getAllApplicationPreferences();
199         
200         // add default prefs if not already in array (only if no name or type filters are set)
201         if (! $_filter->isFilterSet('name') && ! $_filter->isFilterSet('type')) {
202             $missingDefaultPrefs = array_diff($allAppPrefs, $_prefs->name);
203             foreach ($missingDefaultPrefs as $prefName) {
204                 $_prefs->addRecord($this->getApplicationPreferenceDefaults($prefName));
205             }
206         }
207         // remove all prefs that are not defined
208         $undefinedPrefs = array_diff($_prefs->name, $allAppPrefs);
209         if (count($undefinedPrefs) > 0) {
210             $_prefs->addIndices(array('name'));
211             foreach ($undefinedPrefs as $undefinedPrefName) {
212                 $record = $_prefs->find('name', $undefinedPrefName);
213                 $_prefs->removeRecord($record);
214                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Removed undefined preference from result: ' . $undefinedPrefName);
215             }
216         }
217     }
218     
219     /**
220      * do some call json functions if preferences name match
221      * - every app should define its own special handlers
222      *
223      * @param Tinebase_Frontend_Json_Abstract $_jsonFrontend
224      * @param string $name
225      * @param string $value
226      * @param string $appName
227      */
228     public function doSpecialJsonFrontendActions(Tinebase_Frontend_Json_Abstract $_jsonFrontend, $name, $value, $appName)
229     {
230     }
231
232     /**
233      * get value of preference
234      *
235      * @param string $_preferenceName
236      * @param string $_default return this if no preference found and default given
237      * @return string
238      * @throws Tinebase_Exception_NotFound if no default given and no pref found
239      */
240     public function getValue($_preferenceName, $_default = NULL)
241     {
242         $accountId = $this->_getAccountId();
243
244         try {
245             $result = $this->getValueForUser(
246                 $_preferenceName, $accountId,
247                 ($accountId === '0')
248                 ? Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE
249                 : Tinebase_Acl_Rights::ACCOUNT_TYPE_USER
250             );
251         } catch (Tinebase_Exception_NotFound $tenf) {
252             if ($_default !== NULL) {
253                 $result = $_default;
254             } else {
255                 throw $tenf;
256             }
257         }
258         
259         if ($result == Tinebase_Model_Preference::DEFAULT_VALUE) {
260             $result = $_default;
261         }
262
263         return $result;
264     }
265     
266     /**
267      * get account id
268      * 
269      * @return string
270      */
271     protected function _getAccountId()
272     {
273         return (is_object(Tinebase_Core::getUser())) ? Tinebase_Core::getUser()->getId() : '0';
274     }
275
276     /**
277      * get value of preference for a user/group
278      *
279      * @param string $_preferenceName
280      * @param integer $_accountId
281      * @param string $_accountType
282      * @return string
283      * @throws Tinebase_Exception_NotFound
284      */
285     public function getValueForUser($_preferenceName, $_accountId, $_accountType = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
286     {
287         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
288             . ' Get value for ' . $_preferenceName . ' of account id '. $_accountId . ' / ' . $_accountType);
289         
290         $queryResult = $this->_getPrefs($_preferenceName, $_accountId, $_accountType);
291         
292         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
293             . ' ' . print_r($queryResult, true));
294
295         if (! $queryResult) {
296             $pref = $this->getApplicationPreferenceDefaults($_preferenceName, $_accountId, $_accountType);
297         } else {
298             $pref = $this->_getMatchingPreference($this->_rawDataToRecordSet($queryResult));
299         }
300         
301         $result = $pref->value;
302         
303         return $result;
304     }
305
306     /**
307      * get preferences
308      * 
309      * @param string $_preferenceName
310      * @param string $_accountId
311      * @param string $_accountType
312      * @return array result
313      */
314     protected function _getPrefs($_preferenceName, $_accountId = '0', $_accountType = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE)
315     {
316         $select = $this->_getSelect();
317         
318         $appId = Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId();
319         $filter = new Tinebase_Model_PreferenceFilter(array(
320             array('field'     => 'account',         'operator'  => 'equals', 'value'     => array(
321                     'accountId' => $_accountId, 'accountType' => $_accountType)
322             ),
323             array('field'     => 'name',            'operator'  => 'equals', 'value'     => $_preferenceName),
324             array('field'     => 'application_id',  'operator'  => 'equals', 'value'     => $appId),
325         ));
326         Tinebase_Backend_Sql_Filter_FilterGroup::appendFilters($select, $filter, $this);
327         
328         $stmt = $this->_db->query($select);
329         $queryResult = $stmt->fetchAll();
330         
331         return $queryResult;
332     }
333     
334     /**
335      * get all users who have the preference $_preferenceName = $_value
336      *
337      * @param string $_preferenceName
338      * @param string $_value
339      * @param array $_limitToUserIds [optional]
340      * @return array of user ids
341      */
342     public function getUsersWithPref($_preferenceName, $_value, $_limitToUserIds = array())
343     {
344         $result = array();
345
346         $queryResult = $this->_getPrefs($_preferenceName);
347
348         if (empty($queryResult)) {
349             $pref = $this->getApplicationPreferenceDefaults($_preferenceName);
350         } else {
351             $pref = new Tinebase_Model_Preference($queryResult[0]);
352         }
353
354         if ($pref->value == $_value) {
355
356             if (! empty($_limitToUserIds)) {
357                 $result = Tinebase_User::getInstance()->getMultiple($_limitToUserIds)->getArrayOfIds();
358             } else {
359                 $result = Tinebase_User::getInstance()->getUsers()->getArrayOfIds();
360             }
361
362             if ($pref->type == Tinebase_Model_Preference::TYPE_FORCED) {
363                 // forced: get all users -> do nothing here
364
365             } else if ($pref->type == Tinebase_Model_Preference::TYPE_DEFAULT) {
366                 // default: remove all users/groups who don't have default
367                 $filter = new Tinebase_Model_PreferenceFilter(array(
368                     array('field'   => 'account_type',    'operator'  => 'equals', 'value' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER),
369                     array('field'   => 'name',            'operator'  => 'equals', 'value' => $_preferenceName),
370                     array('field'   => 'value',           'operator'  => 'not',    'value' => $_value),
371                 ));
372                 $accountsWithOtherValues = $this->search($filter)->account_id;
373                 $result = array_diff($result, $accountsWithOtherValues);
374
375             } else {
376                 throw new Tinebase_Exception_UnexpectedValue('Preference should be of type "forced" or "default".');
377             }
378
379         } else {
380             // not default or forced: get all users/groups who have the setting
381             $filter = new Tinebase_Model_PreferenceFilter(array(
382                 array('field'   => 'account_type',    'operator'  => 'equals', 'value' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER),
383                 array('field'   => 'name',            'operator'  => 'equals', 'value' => $_preferenceName),
384                 array('field'   => 'value',           'operator'  => 'equals', 'value' => $_value),
385             ));
386             $result = $this->search($filter)->account_id;
387         }
388
389         return $result;
390     }
391
392     /**
393      * set value of preference
394      *
395      * @param string $_preferenceName
396      * @param string $_value
397      */
398     public function setValue($_preferenceName, $_value)
399     {
400         $accountId = $this->_getAccountId();
401         return $this->setValueForUser($_preferenceName, $_value, $accountId);
402     }
403
404     /**
405      * set value of preference for a user/group
406      *
407      * @param string $_preferenceName
408      * @param string $_value
409      * @param integer $_userId
410      * @param boolean $_ignoreAcl
411      * @return void
412      * 
413      * @todo use generic savePreference fn
414      */
415     public function setValueForUser($_preferenceName, $_value, $_accountId, $_ignoreAcl = FALSE)
416     {
417         // check acl first
418         $userId = $this->_getAccountId();
419         if(!$_ignoreAcl){
420             if (
421                 $_accountId !== $userId
422                 && !Tinebase_Acl_Roles::getInstance()->hasRight($this->_application, $userId, Tinebase_Acl_Rights_Abstract::ADMIN)
423             ) {
424                 throw new Tinebase_Exception_AccessDenied('You are not allowed to change the preferences.');
425             }
426         }
427         // check if already there -> update
428         $queryResult = $this->_getPrefs($_preferenceName, $_accountId, Tinebase_Acl_Rights::ACCOUNT_TYPE_USER);
429         $prefArray = NULL;
430         // need to fetch preference for user account as _getPrefs() returns prefs for ANYONE, too
431         foreach ($queryResult as $row) {
432             if ($row['account_type'] === Tinebase_Acl_Rights::ACCOUNT_TYPE_USER) {
433                 $prefArray = $row;
434                 break;
435             }
436         }
437         
438         if ($prefArray === NULL) {
439             if ($_value !== Tinebase_Model_Preference::DEFAULT_VALUE) {
440                 // no preference yet -> create
441                 $preference = new Tinebase_Model_Preference(array(
442                     'application_id'    => $appId = Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId(),
443                     'name'              => $_preferenceName,
444                     'value'             => $_value,
445                     'account_id'        => $_accountId,
446                     'account_type'      => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
447                     'type'              => Tinebase_Model_Preference::TYPE_USER,
448                     'recordConfig'      => $this->_getPrefRecordConfig($_preferenceName),
449                 ));
450                 $this->create($preference);
451                 $action = 'Created';
452             } else {
453                 $action = 'No action required';
454             }
455
456         } else {
457             $preference = $this->_rawDataToRecord($prefArray);
458
459             if ($preference->locked && ! $_ignoreAcl
460                 // TODO allow this for admins?
461                 /* && !Tinebase_Acl_Roles::getInstance()->hasRight(
462                     $this->_application,
463                     $userId,
464                     Tinebase_Acl_Rights_Abstract::ADMIN
465                 ) */) {
466                 throw new Tinebase_Exception_AccessDenied('You are not allowed to change the locked preference.');
467             }
468
469             if ($_value === Tinebase_Model_Preference::DEFAULT_VALUE) {
470                 // delete if new value = use default
471                 $this->delete($preference->getId());
472                 $action = 'Reset';
473             } else {
474                 $preference->value = $_value;
475                 $preference->recordConfig = $this->_getPrefRecordConfig($_preferenceName);
476                 $this->update($preference);
477                 $action = 'Updated';
478             }
479         }
480         
481         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
482             . ' ' . $action . ': ' . $_preferenceName . ' for user ' . $_accountId . ' -> '
483             . (is_array($_value) ? print_r($_value, true) : $_value));
484     }
485
486     /**
487      * overwrite this in concrete classes if needed
488      *
489      * @param $_preferenceName
490      * @return array
491      */
492     public function _getPrefRecordConfig($_preferenceName)
493     {
494         return array();
495     }
496
497     /**
498      * get matching preferences from recordset with multiple prefs)
499      *
500      * @param Tinebase_Record_RecordSet $_preferences
501      */
502     public function getMatchingPreferences(Tinebase_Record_RecordSet $_preferences)
503     {
504         $_preferences->addIndices(array('name'));
505
506         // get unique names, the matching preference and add it to result set
507         $result = new Tinebase_Record_RecordSet('Tinebase_Model_Preference');
508         $uniqueNames = array_unique($_preferences->name);
509         foreach ($uniqueNames as $name) {
510             $singlePrefSet = $_preferences->filter('name', $name);
511             $result->addRecord($this->_getMatchingPreference($singlePrefSet));
512         }
513
514         return $result;
515     }
516
517     /**
518      * resolve preference options and add 'use default'
519      * 
520      * @param Tinebase_Model_Preference $_preference
521      */
522     public function resolveOptions(Tinebase_Model_Preference $_preference)
523     {
524         $options = array();
525         if (! empty($_preference->options)) {
526              $options = $this->_convertXmlOptionsToArray($_preference->options);
527         }
528         
529         // get default pref
530         if (! in_array($_preference->name, $this->_skipDefaultOption)) {
531             $default = $this->_getDefaultPreference($_preference->name);
532             
533             // check if value is in options and use that label
534             $valueLabel = $default->value;
535             foreach ($options as $option) {
536                 if ($default->value == $option[0]) {
537                     $valueLabel = $option[1];
538                     break;
539                 }
540             }
541             // add default setting to the top of options
542             if (is_array($valueLabel)) {
543                 $valueLabel = implode(',', $valueLabel);
544             }
545             $defaultLabel = Tinebase_Translation::getTranslation('Tinebase')->_('default') . 
546                 ' (' . $valueLabel . ')';
547             
548             array_unshift($options, array(
549                 Tinebase_Model_Preference::DEFAULT_VALUE,
550                 $defaultLabel,
551             ));
552
553             if (isset($default->uiconfig)) {
554                 $_preference->uiconfig = $default->uiconfig;
555             }
556         }
557         
558         $_preference->options = $options;
559     }
560     
561     /**
562      * convert options xml string to array
563      *
564      * @param string $_xmlOptions
565      * @return array
566      */
567     protected function _convertXmlOptionsToArray($_xmlOptions)
568     {
569         $result = array();
570         $optionsXml = new SimpleXMLElement($_xmlOptions);
571
572         if ($optionsXml->special) {
573            $result = $this->_getSpecialOptions($optionsXml->special);
574         } else {
575             foreach ($optionsXml->option as $option) {
576                 $result[] = array((string)$option->value, (string)$option->label);
577             }
578         }
579
580         return $result;
581     }
582
583     /**
584      * delete user preference by name
585      *
586      * @param string $_preferenceName
587      * @return void
588      */
589     public function deleteUserPref($_preferenceName)
590     {
591         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Deleting pref ' . $_preferenceName);
592
593         $where = array(
594         $this->_db->quoteInto($this->_db->quoteIdentifier('name')           . ' = ?', $_preferenceName),
595         $this->_db->quoteInto($this->_db->quoteIdentifier('account_id')     . ' = ?', Tinebase_Core::getUser()->getId()),
596         $this->_db->quoteInto($this->_db->quoteIdentifier('account_type')   . ' = ?', Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
597         );
598
599         $this->_db->delete($this->_tablePrefix . $this->_tableName, $where);
600     }
601
602     /**
603      * Creates new entry
604      *
605      * @param   Tinebase_Record_Interface $_record
606      * @return  Tinebase_Record_Interface
607      * @throws  Tinebase_Exception_UnexpectedValue
608      */
609     public function create(Tinebase_Record_Interface $_record)
610     {
611         // check if personal only and account type=anyone -> throw exception
612         if ($_record->personal_only && $_record->account_type == Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE) {
613             $message = 'It is not allowed to set this preference for anyone.';
614             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' ' . $message);
615             throw new Tinebase_Exception_UnexpectedValue($message);
616         }
617
618         return parent::create($_record);
619     }
620
621     /**
622      * save admin preferences for this app
623      * 
624      * @param array $_data
625      * @param boolean $_adminMode
626      * @return void
627      * 
628      * @todo use generic savePreference fn
629      */
630     public function saveAdminPreferences($_data)
631     {
632         // only admins are allowed to update app pref defaults/forced prefs
633         if (! Tinebase_Acl_Roles::getInstance()->hasRight($this->_application, Tinebase_Core::getUser()->getId(), Tinebase_Acl_Rights_Abstract::ADMIN)) {
634             throw new Tinebase_Exception_AccessDenied('You are not allowed to change the preference defaults.');
635         }
636         
637         // create prefs that don't exist in the db
638         foreach ($_data as $id => $prefData) {
639             if (preg_match('/^default/', $id)
640                 && (isset($prefData['name']) || array_key_exists('name', $prefData))
641                 && ($prefData['type'] == Tinebase_Model_Preference::TYPE_FORCED || (string)$prefData['value'] != Tinebase_Model_Preference::DEFAULT_VALUE)
642             ) {
643                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
644                     . ' Create admin pref: ' . $prefData['name'] . ' = ' . $prefData['value']);
645                 $newPref = $this->getApplicationPreferenceDefaults($prefData['name']);
646                 $newPref->value = $prefData['value'];
647                 $newPref->type = ($prefData['type'] == Tinebase_Model_Preference::TYPE_FORCED) ? $prefData['type'] : Tinebase_Model_Preference::TYPE_ADMIN;
648                 unset($newPref->id);
649                 $this->create($newPref);
650                 
651                 unset($_data[$id]);
652             }
653         }
654         
655         // update default/forced preferences
656         $records = $this->getMultiple(array_keys($_data));
657         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
658             . ' Saving admin prefs: ' . print_r($records->name, TRUE));
659         foreach ($records as $preference) {
660             if ($_data[$preference->getId()]['value'] == Tinebase_Model_Preference::DEFAULT_VALUE) {
661                 $this->delete($preference->getId());
662             } else {
663                 $preference->value = $_data[$preference->getId()]['value'];
664                 $preference->type = ($_data[$preference->getId()]['type'] == Tinebase_Model_Preference::TYPE_FORCED) ? $_data[$preference->getId()]['type'] : Tinebase_Model_Preference::TYPE_ADMIN;
665                 $this->update($preference);
666             }
667         }
668     }
669
670     /**************************** protected functions *********************************/
671
672     /**
673      * get matching preference from result set
674      * - order: forced > user > group > default
675      * - get options xml from default pref if available
676      *
677      * @param Tinebase_Record_RecordSet $_preferences
678      * @return Tinebase_Model_Preference
679      */
680     protected function _getMatchingPreference(Tinebase_Record_RecordSet $_preferences)
681     {
682         //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . print_r($_preferences->toArray(), TRUE));
683         $_preferences->addIndices(array('type', 'account_type'));
684
685         if (count($_preferences) == 1) {
686             $result = $_preferences->getFirstRecord();
687         } else {
688             // check forced
689             $forced = $_preferences->filter('type', Tinebase_Model_Preference::TYPE_FORCED);
690             if (count($forced) > 0) {
691                 $_preferences = $forced;
692             }
693
694             // check user
695             $user = $_preferences->filter('account_type', Tinebase_Acl_Rights::ACCOUNT_TYPE_USER);
696             if (count($user) > 0) {
697                 $result = $user->getFirstRecord();
698             } else {
699                 // check group
700                 $group = $_preferences->filter('account_type', Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP);
701                 if (count($group) > 0) {
702                     $result = $group->getFirstRecord();
703                 } else {
704                     // get first record of the remaining result set (defaults/anyone)
705                     $result = $_preferences->getFirstRecord();
706                 }
707             }
708         }
709
710         // add options and perhaps value from default preference
711         if ($result->type !== Tinebase_Model_Preference::TYPE_DEFAULT && is_object(Tinebase_Core::getUser())) {
712             $defaultPref = $this->_getDefaultPreference($result->name, $_preferences);
713             $result->options = $defaultPref->options;
714         }
715
716         return $result;
717     }
718     
719     /**
720      * get default preference (from recordset, db or app defaults)
721      * 
722      * @param string $_preferenceName
723      * @param Tinebase_Record_RecordSet $_preferences
724      */
725     protected function _getDefaultPreference($_preferenceName, $_preferences = NULL)
726     {
727         if ($_preferences !== NULL) {
728             $defaults = $_preferences->filter('type', Tinebase_Model_Preference::TYPE_ADMIN);
729         } else {
730             $defaults = $this->search(new Tinebase_Model_PreferenceFilter(array(array(
731                 'field'     => 'type',
732                 'operator'  => 'equals',
733                 'value'     => Tinebase_Model_Preference::TYPE_ADMIN
734             ), array(
735                 'field'     => 'name',
736                 'operator'  => 'equals',
737                 'value'     => $_preferenceName
738             ), array(
739                 'field'     => 'account_id',
740                 'operator'  => 'equals',
741                 'value'     => '0'
742             ), array(
743                 'field'     => 'application_id',
744                 'operator'  => 'equals',
745                 'value'     => Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId()
746             ))));
747         }
748         
749         if (count($defaults) > 0) {
750             $defaultPref = $defaults->getFirstRecord();
751         } else {
752             $defaultPref = $this->getApplicationPreferenceDefaults($_preferenceName);
753         }
754         
755         return $defaultPref;
756     }
757
758     /**
759      * return base default preference
760      *
761      * @param string $_preferenceName
762      * @return Tinebase_Model_Preference
763      */
764     protected function _getDefaultBasePreference($_preferenceName)
765     {
766         return new Tinebase_Model_Preference(array(
767             'application_id'    => Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId(),
768             'name'              => $_preferenceName,
769             'account_id'        => 0,
770             'account_type'      => Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE,
771             'type'              => Tinebase_Model_Preference::TYPE_DEFAULT,
772             'options'           => '<?xml version="1.0" encoding="UTF-8"?>
773                 <options>
774                     <special>' . $_preferenceName . '</special>
775                 </options>',
776             'id'                => 'default' . Tinebase_Record_Abstract::generateUID(33),
777             'value'             => Tinebase_Model_Preference::DEFAULT_VALUE,
778         ), TRUE);
779     }
780
781     /**
782      * overwrite this to add more special options for other apps
783      *
784      * - result array has to have the following format:
785      *  array(
786      *      array('value1', 'label1'),
787      *      array('value2', 'label2'),
788      *      ...
789      *  )
790      *
791      * @param  string $_value
792      * @return array
793      */
794     protected function _getSpecialOptions($_value)
795     {
796         $result = array();
797
798         switch ($_value) {
799
800             case self::YES_NO_OPTIONS:
801                 $locale = Tinebase_Core::get(Tinebase_Core::LOCALE);
802                 $question = Zend_Locale::getTranslationList('Question', $locale);
803
804                 list($yes, $dummy) = explode(':', $question['yes']);
805                 list($no, $dummy) = explode(':', $question['no']);
806
807                 $result[] = array(0, $no);
808                 $result[] = array(1, $yes);
809                 break;
810
811             case self::DEFAULTCONTAINER_OPTIONS:
812                 $result = $this->_getDefaultContainerOptions();
813                 break;
814
815             case self::DEFAULTPERSISTENTFILTER:
816                 $result = Tinebase_PersistentFilter::getPreferenceValues($this->_application);
817                 break;
818                     
819             default:
820                 throw new Tinebase_Exception_NotFound("Special option '{$_value}' not found.");
821         }
822
823         return $result;
824     }
825     
826     /**
827      * get all containers of current user and shared containers for app
828      * 
829      * @param string $_appName
830      * @return array
831      */
832     protected function _getDefaultContainerOptions($_appName = NULL)
833     {
834         $result = array();
835         $appName = ($_appName !== NULL) ? $_appName : $this->_application;
836         
837         $myContainers = Tinebase_Container::getInstance()->getPersonalContainer(Tinebase_Core::getUser(), $appName, Tinebase_Core::getUser(), Tinebase_Model_Grants::GRANT_ADD);
838         $sharedAddContainers = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), $appName, Tinebase_Model_Grants::GRANT_ADD);
839         $sharedReadContainers = Tinebase_Container::getInstance()->getSharedContainer(Tinebase_Core::getUser(), $appName, Tinebase_Model_Grants::GRANT_READ);
840
841         foreach ($myContainers as $container) {
842             $result[] = array($container->getId(), $container->name);
843         }
844         foreach($sharedAddContainers as $container) {
845             if ($sharedReadContainers->getById($container->getId())) {
846                 $result[] = array($container->getId(), $container->name);
847             }
848         }
849         return $result;
850     }
851
852     /**
853      * adds defaults to default container pref
854      * 
855      * @param Tinebase_Model_Preference $_preference
856      * @param string|Tinebase_Model_User $_accountId
857      * @param string $_appName
858      * @param string $_optionName
859      */
860     protected function _getDefaultContainerPreferenceDefaults(Tinebase_Model_Preference $_preference, $_accountId, $_appName = NULL, $_optionName = self::DEFAULTCONTAINER_OPTIONS)
861     {
862         $appName = ($_appName !== NULL) ? $_appName : $this->_application;
863         
864         $accountId = ($_accountId) ? $_accountId : Tinebase_Core::getUser()->getId();
865         $containers = Tinebase_Container::getInstance()->getPersonalContainer($accountId, $appName, $accountId, 0, true);
866         
867         $_preference->value  = $containers->sort('creation_time')->getFirstRecord()->getId();
868         $_preference->options = '<?xml version="1.0" encoding="UTF-8"?>
869             <options>
870                 <special>' . $_optionName . '</special>
871             </options>';
872     }
873 }