0013214: allow to set fixed calendars as user preference
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 15 Jun 2017 15:33:45 +0000 (17:33 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 22 Jun 2017 15:46:27 +0000 (17:46 +0200)
* uses container selection from filter in pref panel
* adds uiconfig and recordConfig to pref model

!usermanual

https://forge.tine20.org/view.php?id=13214

Change-Id: I62deba671204cb5275ce3537c8a0e3289b90ac68
Reviewed-on: http://gerrit.tine20.com/customers/4881
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/PreferenceTest.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Frontend/Json.php
tine20/Calendar/Preference.php
tine20/Tinebase/Model/Preference.php
tine20/Tinebase/Preference/Abstract.php
tine20/Tinebase/js/Models.js
tine20/Tinebase/js/widgets/container/FilterModel.js
tine20/Tinebase/js/widgets/dialog/PreferencesPanel.js

index d32c6eb..d1a3e08 100644 (file)
@@ -17,7 +17,7 @@ require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php'
 /**
  * Test class for Tinebase_PreferenceTest
  */
-class Tinebase_PreferenceTest extends PHPUnit_Framework_TestCase
+class Tinebase_PreferenceTest extends TestCase
 {
     /**
      * unit under test (UIT)
@@ -290,6 +290,50 @@ class Tinebase_PreferenceTest extends PHPUnit_Framework_TestCase
         }
     }
 
+    /**
+     * @see 0013214: allow to set fixed calendars as user preference
+     */
+    public function testSetFixedCalendarsPreference()
+    {
+        $personalContainer = $this->_getPersonalContainer('Calendar');
+        $data =
+          array (
+            'Calendar' =>
+            array (
+              'fixedCalendars' =>
+              array (
+                'value' =>
+                array (
+                  0 =>
+                      $personalContainer->toArray()
+                ),
+              ),
+            ),
+        );
+
+        $frontend = new Tinebase_Frontend_Json();
+        $result = $frontend->savePreferences($data, /* adminMode */ false);
+        self::assertEquals('success', $result['status']);
+
+        $preferences = $frontend->searchPreferencesForApplication('Calendar', array());
+        $fixedCalendarsPref = false;
+        foreach ($preferences['results'] as $preference) {
+            if ($preference['name'] == 'fixedCalendars') {
+                $fixedCalendarsPref = $preference;
+            }
+        }
+        self::assertTrue(is_array($fixedCalendarsPref), print_r($preferences['results'], true));
+        self::assertEquals(1, count($fixedCalendarsPref['value']), 'did not find personal container in value: '
+            . print_r($fixedCalendarsPref, true));
+        $container = $fixedCalendarsPref['value'][0];
+        self::assertTrue(is_array($container));
+        self::assertEquals($personalContainer->getId(), $container['id']);
+        self::assertTrue(isset($fixedCalendarsPref['uiconfig']), 'uiconfig missing: '
+            . print_r($fixedCalendarsPref, true));
+        $fixedCalendarIds = Calendar_Controller_Event::getInstance()->getFixedCalendarIds();
+        self::assertTrue(in_array($personalContainer->getId(), $fixedCalendarIds));
+    }
+
     /******************** protected helper funcs ************************/
     
     /**
index 6ad49c1..dd2fd7b 100644 (file)
@@ -2966,4 +2966,27 @@ class Calendar_Controller_Event extends Tinebase_Controller_Record_Abstract impl
             $eventNotificationController->doSendNotifications($event, null, 'tentative');
         }
     }
+
+    /**
+     * returns active fixed calendars for users (combines config and preference)
+     *
+     * @return array
+     * @throws Tinebase_Exception_NotFound
+     */
+    public function getFixedCalendarIds()
+    {
+        $fixedCalendars = (array) Calendar_Config::getInstance()->get(Calendar_Config::FIXED_CALENDARS);
+
+        // add fixed calendars from user preference
+        $fixedCalendarsPref = Tinebase_Core::getPreference('Calendar')->getValue(Calendar_Preference::FIXED_CALENDARS);
+        if (is_array($fixedCalendarsPref)) {
+            foreach ($fixedCalendarsPref as $container) {
+                if (isset($container['id'])) {
+                    $fixedCalendars[] = $container['id'];
+                }
+            }
+        }
+
+        return $fixedCalendars;
+    }
 }
index 6c4c9e0..c67d7f7 100644 (file)
@@ -319,8 +319,8 @@ class Calendar_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         $clientFilter = $filter = $this->_decodeFilter($filter, 'Calendar_Model_EventFilter');
 
         // find out if fixed calendars should be used
-        $fixedCalendars = Calendar_Config::getInstance()->get(Calendar_Config::FIXED_CALENDARS);
-        $useFixedCalendars = is_array($fixedCalendars) && ! empty($fixedCalendars);
+        $fixedCalendarIds = Calendar_Controller_Event::getInstance()->getFixedCalendarIds();
+        $useFixedCalendars = is_array($fixedCalendarIds) && ! empty($fixedCalendarIds);
         
         $periodFilter = $filter->getFilter('period');
         
@@ -336,7 +336,7 @@ class Calendar_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         // add fixed calendar on demand
         if ($useFixedCalendars) {
             $fixed = new Calendar_Model_EventFilter(array(), 'AND');
-            $fixed->addFilter( new Tinebase_Model_Filter_Text('container_id', 'in', $fixedCalendars));
+            $fixed->addFilter( new Tinebase_Model_Filter_Text('container_id', 'in', $fixedCalendarIds));
             
             $fixed->addFilter($periodFilter);
             
@@ -358,7 +358,7 @@ class Calendar_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             'filter'        => $clientFilter->toArray(TRUE),
         );
     }
-    
+
     /**
      * get default period filter
      * 
index 9d226a4..d88de28 100644 (file)
@@ -98,6 +98,11 @@ class Calendar_Preference extends Tinebase_Preference_Abstract
     const DEFAULT_CALENDAR_STRATEGY = 'defaultCalendarStrategy';
 
     /**
+     * fixedCalendars
+     */
+    const FIXED_CALENDARS = 'fixedCalendars';
+
+    /**
      * @var string application
      */
     protected $_application = 'Calendar';
@@ -127,7 +132,8 @@ class Calendar_Preference extends Tinebase_Preference_Abstract
             self::DEFAULTATTENDEE_STRATEGY,
             self::DEFAULT_EVENTS_RRIVATE,
             self::FIRSTDAYOFWEEK,
-            self::DEFAULT_CALENDAR_STRATEGY
+            self::DEFAULT_CALENDAR_STRATEGY,
+            self::FIXED_CALENDARS,
         );
         
         if ($cropDays) {
@@ -211,6 +217,10 @@ class Calendar_Preference extends Tinebase_Preference_Abstract
                 'label'         => $translate->_('Default calendar strategy'),
                 'description'   => $translate->_('The calendar for new events if no container is selected'),
             ),
+            self::FIXED_CALENDARS => array(
+                'label'         => $translate->_('Fixed Calendars'),
+                'description'   => $translate->_('Calendars always selected regardless of all filter parameters.'),
+            ),
         );
         
         return $prefDescriptions;
@@ -436,10 +446,44 @@ class Calendar_Preference extends Tinebase_Preference_Abstract
                         </option>
                     </options>';
                 break;
+            case self::FIXED_CALENDARS:
+                $preference->value = array();
+                // TODO set better (?) options / maybe this could be removed
+                $preference->options    = '<?xml version="1.0" encoding="UTF-8"?>
+                    <options>
+                        <special>' . Tinebase_Preference_Abstract::DEFAULTCONTAINER_OPTIONS . '</special>
+                    </options>';
+
+                $preference->uiconfig = array(
+                    'xtype'       => 'containerspicker',
+                    'appName'     => 'Calendar',
+                    'model'       => 'Event',
+                );
+                $preference->personal_only = true;
+                break;
             default:
                 throw new Tinebase_Exception_NotFound('Default preference with name ' . $_preferenceName . ' not found.');
         }
         
         return $preference;
     }
+
+    /**
+     * overwrite this in concrete classes if needed
+     *
+     * @param $_preferenceName
+     * @return array
+     */
+    public function _getPrefRecordConfig($_preferenceName)
+    {
+        switch ($_preferenceName) {
+            case self::FIXED_CALENDARS:
+                return array(
+                    'modelName'      => 'Tinebase_Model_Container',
+                );
+                break;
+            default:
+                return parent::_getPrefRecordConfig($_preferenceName);
+        }
+    }
 }
index 384a8b3..fe53bb6 100644 (file)
@@ -91,5 +91,64 @@ class Tinebase_Model_Preference extends Tinebase_Record_Abstract
         'personal_only'      => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
     // don't allow user to change value
         'locked'      => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => 0),
+        // multiselection preference
+        //'multiselect'        =>  array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => false),
+        'uiconfig'        =>  array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
+        'recordConfig'    =>  array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
     );
+
+    /**
+     * TODO remove when converted to ModelConfig
+     * TODO generalize / support other models
+     */
+    public function runConvertToRecord()
+    {
+        // convert value property if necessary
+        if (Tinebase_Helper::is_json($this->value)) {
+            $value = Tinebase_Helper::jsonDecode($this->value);
+            switch ($value['modelName']) {
+                case 'Tinebase_Model_Container':
+                    $containers = array();
+                    foreach ($value['ids'] as $containerId) {
+                        try {
+                            $container = Tinebase_Container::getInstance()->getContainerById($containerId);
+                            // TODO should be converted to array by json frontend
+                            $containers[] = $container->toArray();
+                        } catch (Exception $e) {
+                            // not found / no access / ...
+                        }
+                    }
+                    $this->value = $containers;
+                    break;
+                default:
+                    throw new Tinebase_Exception_InvalidArgument('model not supported');
+
+            }
+        } else {
+            parent::runConvertToRecord();
+        }
+    }
+
+    /**
+     * TODO remove when converted to ModelConfig
+     * TODO generalize / support other models
+     */
+    public function runConvertToData()
+    {
+        // convert value property if necessary
+        if (is_array($this->value) && is_array($this->recordConfig) && isset($this->recordConfig['modelName'])) {
+            $value = array(
+                'modelName' => $this->recordConfig['modelName'],
+                'ids' => array(),
+            );
+            foreach ($this->value as $record) {
+                if (isset($record['id'])) {
+                    $value['ids'][] = $record['id'];
+                }
+            }
+            $this->value = json_encode($value);
+        } else {
+            parent::runConvertToData();
+        }
+    }
 }
index 4011fde..0b6e2b2 100644 (file)
@@ -291,7 +291,7 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
         
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
             . ' ' . print_r($queryResult, true));
-        
+
         if (! $queryResult) {
             $pref = $this->getApplicationPreferenceDefaults($_preferenceName, $_accountId, $_accountType);
         } else {
@@ -444,7 +444,8 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
                     'value'             => $_value,
                     'account_id'        => $_accountId,
                     'account_type'      => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
-                    'type'              => Tinebase_Model_Preference::TYPE_USER
+                    'type'              => Tinebase_Model_Preference::TYPE_USER,
+                    'recordConfig'      => $this->_getPrefRecordConfig($_preferenceName),
                 ));
                 $this->create($preference);
                 $action = 'Created';
@@ -471,13 +472,26 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
                 $action = 'Reset';
             } else {
                 $preference->value = $_value;
+                $preference->recordConfig = $this->_getPrefRecordConfig($_preferenceName);
                 $this->update($preference);
                 $action = 'Updated';
             }
         }
         
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
-            . ' ' . $action . ': ' . $_preferenceName . ' for user ' . $_accountId . ' -> ' . $_value);
+            . ' ' . $action . ': ' . $_preferenceName . ' for user ' . $_accountId . ' -> '
+            . (is_array($_value) ? print_r($_value, true) : $_value));
+    }
+
+    /**
+     * overwrite this in concrete classes if needed
+     *
+     * @param $_preferenceName
+     * @return array
+     */
+    public function _getPrefRecordConfig($_preferenceName)
+    {
+        return array();
     }
 
     /**
@@ -525,6 +539,9 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
                 }
             }
             // add default setting to the top of options
+            if (is_array($valueLabel)) {
+                $valueLabel = implode(',', $valueLabel);
+            }
             $defaultLabel = Tinebase_Translation::getTranslation('Tinebase')->_('default') . 
                 ' (' . $valueLabel . ')';
             
@@ -532,6 +549,10 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
                 Tinebase_Model_Preference::DEFAULT_VALUE,
                 $defaultLabel,
             ));
+
+            if (isset($default->uiconfig)) {
+                $_preference->uiconfig = $default->uiconfig;
+            }
         }
         
         $_preference->options = $options;
@@ -790,7 +811,7 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
             case self::DEFAULTCONTAINER_OPTIONS:
                 $result = $this->_getDefaultContainerOptions();
                 break;
-                
+
             case self::DEFAULTPERSISTENTFILTER:
                 $result = Tinebase_PersistentFilter::getPreferenceValues($this->_application);
                 break;
@@ -827,7 +848,7 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
         }
         return $result;
     }
-    
+
     /**
      * adds defaults to default container pref
      * 
index 89b2b26..f59eb1b 100644 (file)
@@ -255,6 +255,7 @@ Tine.Tinebase.Model.Preference = Ext.data.Record.create([
     {name: 'description'    },
     {name: 'personal_only',         type: 'boolean' },
     {name: 'locked',                type: 'boolean' },
+    {name: 'uiconfig'       },
     {name: 'options'        }
 ]);
 
index 9cb8753..0fa7234 100644 (file)
@@ -391,3 +391,4 @@ Tine.widgets.container.FilterModelMultipleValueField = Ext.extend(Ext.ux.form.La
     }
 });
 
+Ext.reg('containerspicker', Tine.widgets.container.FilterModelMultipleValueField);
\ No newline at end of file
index 9307dcf..14b834b 100644 (file)
@@ -112,14 +112,23 @@ Tine.widgets.dialog.PreferencesPanel = Ext.extend(Ext.Panel, {
                 var options = pref.get('options');
                 // NOTE: some prefs have no default and only one option (e.g. std email account)
                 if (options.length > 1 || (options.length == 1 && options[0][0] !== '_default_')) {
-                    Ext.apply(fieldDef, {
-                        xtype: (this.adminMode ? 'lockCombo' : 'combo'),
-                        store: pref.get('options'),
-                        mode: 'local',
-                        forceSelection: true,
-                        allowBlank: false,
-                        triggerAction: 'all'
-                    });
+                    if (pref.get('uiconfig') && pref.get('uiconfig').xtype) {
+                        // TODO support admin mode / currently this is personal_only
+                        Ext.apply(fieldDef, {
+                            xtype: pref.get('uiconfig').xtype,
+                            recordClass: Tine[pref.get('uiconfig').appName].Model[pref.get('uiconfig').model],
+                            value: pref.get('value') === '_default_' ? [] : pref.get('value'),
+                        });
+                    } else {
+                        Ext.apply(fieldDef, {
+                            xtype: (this.adminMode ? 'lockCombo' : 'combo'),
+                            store: pref.get('options'),
+                            mode: 'local',
+                            forceSelection: true,
+                            allowBlank: false,
+                            triggerAction: 'all'
+                        });
+                    }
                 } else {
                     Ext.apply(fieldDef, {
                         xtype: (this.adminMode ? 'lockTextfield' : 'textfield'),