0005180: deleted container needs to be removed from "recents"
authorsstamer <s.stamer@metaways.de>
Fri, 6 Jan 2017 14:01:19 +0000 (15:01 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Mon, 16 Jan 2017 12:26:13 +0000 (13:26 +0100)
... in container selection combo

* ask server for recent ids to remove deleted and get renames
* also care for renames when setting data from record
* add test for searchContainers

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

Change-Id: I94d55fe06a1cfeeb5a6a9c275252af1af7e22626
Reviewed-on: http://gerrit.tine20.com/customers/4053
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
tests/tine20/Tinebase/Frontend/Json/ContainerTest.php
tine20/Admin/Frontend/Json.php
tine20/Tinebase/Container.php
tine20/Tinebase/Convert/Container/Json.php [new file with mode: 0644]
tine20/Tinebase/Frontend/Json/Container.php
tine20/Tinebase/js/widgets/container/ContainerSelect.js

index 2c4de4e..40c11cf 100644 (file)
@@ -190,7 +190,34 @@ class Tinebase_Frontend_Json_ContainerTest extends PHPUnit_Framework_TestCase
 
         $container = Tinebase_Container::getInstance()->getContainerById($container['id']);
     }
-}        
+    
+    /**
+     * try to search containers
+     *
+     */
+    public function testSearchContainers()
+    {
+        $container = $this->_backend->addContainer('Addressbook', 'Winter', Tinebase_Model_Container::TYPE_PERSONAL);
+        $this->assertEquals('Winter', $container['name']);
+        
+        $filter = array(array(
+                'field'     => 'query',
+                'operator'     => 'contains',
+                'value'     => 'Winter'
+        ));
+        $paging = array(
+                'start'    => 0,
+                'limit'    => 1
+        );
+        
+        $result = $this->_backend->searchContainers($filter, $paging);
+
+        $this->assertGreaterThan(0, $result['totalcount']);
+        $this->assertEquals($container['name'], $result['results'][0]['name']);
+        $this->assertTrue(isset($result['results'][0]['account_grants']['readGrant']), 'account_grants missing');
+        $this->assertTrue(isset($result['results'][0]['ownerContact']['email']), 'ownerContact missing');
+    }
+}
     
 
 if (PHPUnit_MAIN_METHOD == 'Tinebase_Frontend_Json_ContainerTest::main') {
index 7f288c7..1eeebb4 100644 (file)
@@ -1292,9 +1292,9 @@ class Admin_Frontend_Json extends Tinebase_Frontend_Json_Abstract
         $result = parent::_recordToJson($_record);
 
         if ($_record instanceof Tinebase_Model_Container) {
-            $result['account_grants'] = Tinebase_Frontend_Json_Container::resolveAccounts($result['account_grants']);
+            $result['account_grants'] = Tinebase_Frontend_Json_Container::resolveAccounts($_record['account_grants']->toArray());
         }
-        
+
         return $result;
     }
     
index 5d12d68..adb3986 100644 (file)
@@ -23,7 +23,7 @@
  * @package     Tinebase
  * @subpackage  Acl
  */
-class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
+class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tinebase_Controller_SearchInterface
 {
     /**
      * Table name without prefix
@@ -120,6 +120,33 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract
         
         return $this->_contentBackend;
     }
+
+    /**
+     * get list of records
+     *
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param Tinebase_Model_Pagination $_pagination
+     * @param bool $_getRelations
+     * @param bool $_onlyIds
+     * @param string $_action
+     * @return Tinebase_Record_RecordSet
+     */
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Model_Pagination $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
+    {
+        return parent::search($_filter, $_pagination, $_onlyIds);
+    }
+
+    /**
+     * Gets total count of search with $_filter
+     *
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param string $_action
+     * @return int
+     */
+    public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
+    {
+        return parent::searchCount($_filter);
+    }
     
     /**
      * creates a new container
diff --git a/tine20/Tinebase/Convert/Container/Json.php b/tine20/Tinebase/Convert/Container/Json.php
new file mode 100644 (file)
index 0000000..33adb3a
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * convert functions for Container from/to json (array) format
+ * 
+ * @package     Tinebase
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * convert functions for Container from/to json (array) format
+ *
+ * @package     Tinebase
+ * @subpackage  Convert
+ */
+class Tinebase_Convert_Container_Json extends Tinebase_Convert_Json
+{
+    /**
+     * converts Tinebase_Record_Abstract to external format
+     * 
+     * @param  Tinebase_Record_Abstract $_model
+     * @return mixed
+     */
+    public function fromTine20Model(Tinebase_Record_Abstract $_model)
+    {
+        $recordSet = new Tinebase_Record_RecordSet('Tinebase_Model_Container', array($_model));
+        $result = $this->fromTine20RecordSet($recordSet);
+
+        
+        return $result[0];
+    }
+
+
+    /**
+     * converts Tinebase_Record_RecordSet to external format
+     * 
+     * @param Tinebase_Record_RecordSet  $_records
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param Tinebase_Model_Pagination $_pagination
+     * 
+     * @return mixed
+     */
+    public function fromTine20RecordSet(Tinebase_Record_RecordSet $_records = NULL, $_filter = NULL, $_pagination = NULL)
+    {
+        $response = array();
+        foreach ($_records as $container) {
+            $containerArray = $container->toArray();
+
+            if ($container instanceof Tinebase_Model_Container) {
+                $containerArray['account_grants'] = Tinebase_Container::getInstance()->getGrantsOfAccount(Tinebase_Core::getUser(), $container->getId())->toArray();
+                $containerArray['path'] = $container->getPath();
+                $ownerId = $container->getOwner();
+            } else {
+                $containerArray['path'] = "personal/{$container->getId()}";
+                $ownerId = $container->getId();
+            }
+            if (! empty($ownerId)) {
+                try {
+                    $containerArray['ownerContact'] = Addressbook_Controller_Contact::getInstance()->getContactByUserId($ownerId, true)->toArray();
+                } catch (Exception $e) {
+                    Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__ . " can't resolve ownerContact: " . $e);
+                }
+            }
+
+            $response[] = $containerArray;
+        }
+
+        return $response;
+    }
+}
index 5b32563..1077456 100644 (file)
  */
 class Tinebase_Frontend_Json_Container extends  Tinebase_Frontend_Json_Abstract
 {
-   /**
+
+    /**
+     * Search for Container matching given arguments
+     *
+     * @param array $filter
+     * @param array $paging
+     * @return array
+     */
+    public function searchContainers($filter, $paging)
+    {
+        return parent::_search($filter, $paging, Tinebase_Container::getInstance(), 'Tinebase_Model_ContainerFilter');
+    }
+
+    /**
      * gets container / container folder
      * 
      * Backend function for containerTree widget
@@ -52,30 +65,8 @@ class Tinebase_Frontend_Json_Container extends  Tinebase_Frontend_Json_Abstract
                 throw new Exception('no such NodeType');
         }
         
-        $response = array();
-        foreach ($containers as $container) {
-            $containerArray = $container->toArray();
-            
-            if ($container instanceof Tinebase_Model_Container) {
-                $containerArray['account_grants'] = Tinebase_Container::getInstance()->getGrantsOfAccount(Tinebase_Core::getUser(), $container->getId())->toArray();
-                $containerArray['path'] = $container->getPath();
-                $ownerId = $container->getOwner();
-            } else {
-                $containerArray['path'] = "personal/{$container->getId()}";
-                $ownerId = $container->getId();
-            }
-            if (! empty($ownerId)) {
-                try {
-                    $containerArray['ownerContact'] = Addressbook_Controller_Contact::getInstance()->getContactByUserId($ownerId, true)->toArray();
-                } catch (Exception $e) {
-                    Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__ . " can't resolve ownerContact: " . $e);
-                }
-            }
-            
-            $response[] = $containerArray;
-        }
-        
-        return $response;
+        $converter = new Tinebase_Convert_Container_Json();
+        return $converter->fromTine20RecordSet($containers);
     }
     
     /**
index cf00253..a23e4f9 100644 (file)
@@ -17,6 +17,10 @@ Ext.ns('Tine.widgets', 'Tine.widgets.container');
  */
 Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
     /**
+     * @cfg {Tine.Tinebase.Application} app
+     */
+    app: null,
+    /**
      * @cfg {Boolean} allowNodeSelect
      */
     allowNodeSelect: false,
@@ -90,7 +94,7 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
     stateful: true,
     stateEvents: ['select'],
     
-    mode: 'local',
+    mode: 'remote',
     valueField: 'id',
     displayField: 'name',
     
@@ -148,7 +152,20 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
             ]};
             
         }
-        
+
+        // autoconfig app
+        if (! this.app && this.appName) {
+            this.app = Tine.Tinebase.appMgr.get(this.appName);
+        }
+        if (! this.app && this.recordClass) {
+            this.app = Tine.Tinebase.appMgr.get(this.recordClass.getMeta('appName'));
+        }
+        if (this.app && ! this.appName) {
+            this.appName = this.app.appName;
+        }
+
+        this.recents = {};
+
         //this.title = String.format(_('Recently used {0}:'), this.containersName);
         
         Tine.widgets.container.selectionComboBox.superclass.initComponent.call(this);
@@ -162,7 +179,6 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
                 return false;
             }
         }, this);
-        this.on('beforequery', this.onBeforeQuery, this);
     },
     
     /**
@@ -171,13 +187,19 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
     initStore: function() {
         var state = this.stateful ? Ext.state.Manager.get(this.stateId) : null;
         var recentsData = state && state.recentsData || [];
-        
+
         this.store = new Ext.data.JsonStore({
             id: 'id',
-            data: recentsData,
+            data: {results: recentsData},
             remoteSort: false,
             fields: Tine.Tinebase.Model.Container,
-            listeners: {beforeload: this.onBeforeLoad.createDelegate(this)}
+            root: 'results',
+            totalProperty: 'totalcount',
+            id: 'id',
+            listeners: {
+                beforeload: this.onBeforeLoad.createDelegate(this),
+                load: this.onStoreLoad.createDelegate(this)
+            }
         });
         this.setStoreFilter();
     },
@@ -233,31 +255,50 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
      */
     onBeforeLoad: function(store, options) {
         options.params = {
-            method: 'Tinebase_Container.getContainer',
-            application: this.appName,
-            containerType: Tine.Tinebase.container.TYPE_PERSONAL,
-            owner: Tine.Tinebase.container.pathIsPersonalNode(this.startPath)
+            method: 'Tinebase_Container.searchContainers',
+            filter: [
+                {field: 'application_id', operator: 'equals', value: this.app.id },
+            ]
         };
+        if (this.recordClass) {
+            options.params.filter.push(
+                {field: 'model', operator: 'equals', value: this.recordClass.getMeta('phpClassName')}
+            );
+        }
+
+        if (Tine.Tinebase.container.pathIsPersonalNode(this.startPath)) {
+            // direct search
+            options.params.filter.push(
+                {field: 'type',     operator: 'equals', value: Tine.Tinebase.container.TYPE_PERSONAL},
+                {field: 'owner_id', operator: 'equals', value: Tine.Tinebase.container.pathIsPersonalNode(this.startPath)}
+            );
+        } else {
+            // validate recents
+            this.recents = {};
+            this.store.each(function(r) {
+                if (r != this.otherRecord) {
+                    this.recents[r.id] = r.get('dtselect');
+                }
+            }, this);
+
+            options.params.filter.push(
+                {field: 'id',       operator: 'in',     value: Object.keys(this.recents)}
+            );
+        }
     },
-    
-    /**
-     * if this.startPath correspondes to a personal node, 
-     * directly do a remote search w.o. container tree dialog
-     * 
-     * @private
-     * @param {} queryEvent
-     */
-    onBeforeQuery: function(queryEvent) {
-        queryEvent.query = new Date().getTime();
-        this.mode = Tine.Tinebase.container.pathIsPersonalNode(this.startPath) ? 'remote' : 'local' ;
-        
-        // skip combobox nativ filtering to preserv our filters
-        if (this.mode == 'local') {
-            this.onLoad();
-            return false;
+
+    onStoreLoad: function() {
+        if (! this.store) return;
+
+        if (! Tine.Tinebase.container.pathIsPersonalNode(this.startPath)) {
+            this.store.each(function(r) {
+                r.set('dtselect', this.recents[r.id]);
+            }, this);
+            this.store.sort('dtselect', 'DESC');
+            this.store.add(this.otherRecord);
         }
     },
-    
+
     /**
      * filter store according to this.startPath and this.allowNodeSelect
      * 
@@ -337,7 +378,6 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
     
     /**
      * @private
-     * @todo // records might be in from state, but value is conainerData only
      */
     setValue: function(container) {
         
@@ -349,11 +389,12 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
             // store already has a record of this container
             container = this.store.getById(container);
             
-        } else if (container.path && this.store.findExact('path', container.path) >= 0) {
-            // store already has a record of this container
-            container = this.store.getAt(this.store.findExact('path', container.path));
-            
         } else if (container.path || container.id) {
+            if (container.path && this.store.findExact('path', container.path) >= 0) {
+                // store already has a record of this container -> refresh
+                this.store.remove(this.store.getAt(this.store.findExact('path', container.path)));
+            }
+
             // ignore server name for node 'My containers'
             if (container.path && container.path === Tine.Tinebase.container.getMyNodePath()) {
                 container.name = null;
@@ -375,7 +416,7 @@ Tine.widgets.container.selectionComboBox = Ext.extend(Ext.form.ComboBox, {
         // make sure other is _last_ entry in list
         this.store.remove(this.otherRecord);
         this.store.add(this.otherRecord);
-        
+
         Tine.widgets.container.selectionComboBox.superclass.setValue.call(this, container.id);
         
         if (container.account_grants) {