0005578: activesync device management
authorsstamer <s.stamer@metaways.de>
Mon, 22 Dec 2014 08:05:24 +0000 (09:05 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 28 Jan 2015 17:24:03 +0000 (18:24 +0100)
- Implemented UI
- Implemented Controller
- Integrated into Admin UI
- Inside of ActiveSync
- Adds device management tests
- Refactors ActiveSync controller tests
- adds device management as Admin module (if ActiveSync is installed)

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

Change-Id: I8259769506a1662f4433efaba0c33b4c2cb9961a
Reviewed-on: http://gerrit.tine20.com/customers/1482
Tested-by: Jenkins CI (http://ci.tine20.com/)
Reviewed-by: Lars Kneschke <l.kneschke@metaways.de>
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
20 files changed:
tests/tine20/ActiveSync/AllTests.php
tests/tine20/ActiveSync/Controller/CalendarTests.php
tests/tine20/ActiveSync/Controller/ContactsTests.php
tests/tine20/ActiveSync/Controller/ControllerTest.php [new file with mode: 0644]
tests/tine20/ActiveSync/Controller/TasksTests.php
tests/tine20/ActiveSync/Frontend/JsonTests.php [new file with mode: 0644]
tests/tine20/ActiveSync/TestCase.php
tine20/ActiveSync/Acl/Rights.php [new file with mode: 0644]
tine20/ActiveSync/ActiveSync.jsb2
tine20/ActiveSync/Controller/SyncDevices.php [new file with mode: 0644]
tine20/ActiveSync/Frontend/Json.php
tine20/ActiveSync/Model/Device.php
tine20/ActiveSync/Model/DeviceFilter.php
tine20/ActiveSync/js/AdminPanel.js [new file with mode: 0644]
tine20/ActiveSync/js/EditDialog.js [new file with mode: 0644]
tine20/ActiveSync/js/SyncDevices.js [new file with mode: 0644]
tine20/ActiveSync/js/SyncDevicesGridPanel.js [new file with mode: 0644]
tine20/Admin/css/Admin.css
tine20/Admin/js/Admin.js
tine20/Tinebase/js/widgets/grid/GridPanel.js

index fda3aae..1379d07 100755 (executable)
@@ -4,15 +4,10 @@
  * 
  * @package     ActiveSync
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2010-2010 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Jonas Fischer <j.fischer@metaways.de>
  */
 
-/**
- * Test helper
- */
-require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
 class ActiveSync_AllTests
 {
     public static function main ()
@@ -29,6 +24,8 @@ class ActiveSync_AllTests
         $suite->addTestSuite('ActiveSync_Controller_AllTests');
         $suite->addTestSuite('ActiveSync_Backend_AllTests');
         
+        $suite->addTestSuite('ActiveSync_Frontend_JsonTests');
+        
         return $suite;
     }
 }
index 519fd75..0983a26 100644 (file)
@@ -13,7 +13,7 @@
  * 
  * @package     ActiveSync
  */
-class ActiveSync_Controller_CalendarTests extends ActiveSync_TestCase
+class ActiveSync_Controller_CalendarTests extends ActiveSync_Controller_ControllerTest
 {
     /**
      * name of the application
index 1adc22e..88172b4 100644 (file)
@@ -9,16 +9,11 @@
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * Test class for Calendar_Controller_Event
  * 
  * @package     Calendar
  */
-class ActiveSync_Controller_ContactsTests extends ActiveSync_TestCase
+class ActiveSync_Controller_ContactsTests extends ActiveSync_Controller_ControllerTest
 {
     /**
      * name of the application
diff --git a/tests/tine20/ActiveSync/Controller/ControllerTest.php b/tests/tine20/ActiveSync/Controller/ControllerTest.php
new file mode 100644 (file)
index 0000000..d8347d4
--- /dev/null
@@ -0,0 +1,230 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     ActiveSync
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2010-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * abstract test class for activesync controller tests
+ * 
+ * @package     ActiveSync
+ */
+abstract class ActiveSync_Controller_ControllerTest extends ActiveSync_TestCase
+{
+    /**
+     * name of the controller
+     *
+     * @var string
+     */
+    protected $_controllerName;
+    
+    /**
+     * @var ActiveSync_Controller_Abstract controller
+     */
+    protected $_controller;
+    
+    /**
+     *
+     * @return Syncroton_Model_Folder
+     */
+    public function testCreateFolder()
+    {
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+        
+        $syncrotonFolder = $controller->createFolder(new Syncroton_Model_Folder(array(
+            'parentId' => 0, 
+            'displayName' => 'TestFolder'
+        )));
+    
+        $this->assertTrue(!empty($syncrotonFolder->serverId));
+        
+        return $syncrotonFolder;
+    }
+    
+    /**
+     * 
+     * @return Syncroton_Model_Folder
+     */
+    public function testUpdateFolder()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+        $syncrotonFolder->displayName = 'RenamedTestFolder';
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $updatedSyncrotonFolder = $controller->updateFolder($syncrotonFolder);
+        
+        $allFolders = $controller->getAllFolders();
+        
+        $this->assertArrayHasKey($syncrotonFolder->serverId, $allFolders);
+        $this->assertEquals('RenamedTestFolder', $allFolders[$syncrotonFolder->serverId]->displayName);
+        
+        return $updatedSyncrotonFolder;
+    }
+    
+    /**
+     * @return Syncroton_Model_Folder
+     */
+    public function testUpdateFolderAndroid()
+    {
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_ANDROID), new Tinebase_DateTime(null, null, 'de_DE'));
+        
+        $syncrotonFolders = $controller->getAllFolders();
+    
+        #var_dump($syncrotonFolders); return;
+        
+        $this->setExpectedException('Syncroton_Exception_UnexpectedValue');
+        
+        $updatedSyncrotonFolder = $controller->updateFolder($syncrotonFolders[$this->_specialFolderName]);
+    }
+    
+    /**
+     * test if changed folders got returned
+     */
+    public function testGetChangedFolders()
+    {
+        $syncrotonFolder = $this->testUpdateFolder();
+        
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+        
+        $changedFolders = $controller->getChangedFolders(Tinebase_DateTime::now()->subMinute(1), Tinebase_DateTime::now());
+        
+        //var_dump($changedFolders);
+        
+        $this->assertEquals(1, count($changedFolders));
+        $this->assertArrayHasKey($syncrotonFolder->serverId, $changedFolders);
+    }
+    
+    public function testDeleteFolder()
+    {
+        $this->markTestIncomplete('not yet implemented in controller');
+        
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $controller->deleteFolder($syncrotonFolder);
+    }
+    
+    public function testGetAllFolders()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $allSyncrotonFolders = $controller->getAllFolders();
+        
+        $this->assertArrayHasKey($syncrotonFolder->serverId, $allSyncrotonFolders);
+        $this->assertArrayNotHasKey($this->_specialFolderName, $allSyncrotonFolders);
+        $this->assertTrue($allSyncrotonFolders[$syncrotonFolder->serverId] instanceof Syncroton_Model_Folder);
+        $this->assertEquals($syncrotonFolder->serverId, $allSyncrotonFolders[$syncrotonFolder->serverId]->serverId, 'serverId mismatch');
+        $this->assertEquals($syncrotonFolder->parentId, $allSyncrotonFolders[$syncrotonFolder->serverId]->parentId, 'parentId mismatch');
+        $this->assertEquals($syncrotonFolder->displayName, $allSyncrotonFolders[$syncrotonFolder->serverId]->displayName);
+        $this->assertTrue(!empty($allSyncrotonFolders[$syncrotonFolder->serverId]->type));
+    }
+    
+    public function testGetAllFoldersPalm()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_WEBOS), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $allSyncrotonFolders = $controller->getAllFolders();
+        
+        $this->assertArrayHasKey($this->_specialFolderName, $allSyncrotonFolders, "key {$this->_specialFolderName} not found in " . print_r($allSyncrotonFolders, true));
+    }
+    
+    /**
+     * testDeleteEntry
+     */
+    public function testDeleteEntry()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+        
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+        
+        list($serverId, $syncrotonContact) = $this->testCreateEntry($syncrotonFolder);
+        
+        $controller->deleteEntry($syncrotonFolder->serverId, $serverId, null);
+        
+        try {
+            $syncrotonContact = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
+            $this->fail('should have thrown Syncroton_Exception_NotFound: '
+                . var_export($syncrotonContact, TRUE)
+                . ' tine contact: ' . print_r(Addressbook_Controller_Contact::getInstance()->get($serverId)->toArray(), TRUE));
+        } catch (Syncroton_Exception_NotFound $senf) {
+            $this->assertEquals('Syncroton_Exception_NotFound', get_class($senf));
+        }
+    }
+    
+    public function testGetInvalidEntry()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $this->setExpectedException('Syncroton_Exception_NotFound');
+    
+        $syncrotonContact = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), 'jdszfegd63gfrk');
+    }
+    
+    /**
+     * test get changed entries
+     */
+    public function testGetChangedEntries()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        list($serverId, $syncrotonContact) = $this->testUpdateEntry($syncrotonFolder);
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
+    
+        $changedEntries = $controller->getChangedEntries($syncrotonFolder->serverId, new DateTime('2000-01-01'));
+        
+        $this->assertContains($serverId, $changedEntries, 'did not get changed record id in ' . print_r($changedEntries, TRUE));
+    }
+    
+    /**
+     * test get changed entries for android
+     */
+    public function testGetChangedEntriesAndroid()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+    
+        list($serverId, $syncrotonContact) = $this->testUpdateEntry($syncrotonFolder);
+    
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_ANDROID), Tinebase_DateTime::now());
+    
+        $changedEntries = $controller->getChangedEntries($this->_specialFolderName, new Tinebase_DateTime('2000-01-01'));
+    
+        $this->assertContains($serverId, $changedEntries, 'did not get changed record id in ' . print_r($changedEntries, TRUE));
+    }
+    
+    /**
+     * test convert from XML to Tine 2.0 model
+     */
+    abstract public function testCreateEntry($syncrotonFolder = null);
+    
+    /**
+     * test xml generation for sync to client
+     */
+    abstract public function testUpdateEntry($syncrotonFolder = null);
+    
+    /**
+     * get application activesync controller
+     * 
+     * @param ActiveSync_Model_Device $_device
+     */
+    protected function _getController(ActiveSync_Model_Device $_device)
+    {
+        if ($this->_controller === null) {
+            $this->_controller = Syncroton_Data_Factory::factory($this->_class, $_device, new Tinebase_DateTime(null, null, 'de_DE'));
+        } 
+        
+        return $this->_controller;
+    }
+}
index fcf9646..f13df78 100644 (file)
@@ -9,16 +9,11 @@
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * Test class for Tasks_Controller_Task
  * 
  * @package     ActiveSync
  */
-class ActiveSync_Controller_TasksTests extends ActiveSync_TestCase
+class ActiveSync_Controller_TasksTests extends ActiveSync_Controller_ControllerTest
 {
     /**
      * name of the application
diff --git a/tests/tine20/ActiveSync/Frontend/JsonTests.php b/tests/tine20/ActiveSync/Frontend/JsonTests.php
new file mode 100644 (file)
index 0000000..8c0b2c6
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     ActiveSync
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test class for ActiveSync_Frontend_Json
+ * 
+ * @package     ActiveSync
+ */
+class ActiveSync_Frontend_JsonTests extends ActiveSync_TestCase
+{
+    /**
+     * lazy init of uit
+     *
+     * @return ActiveSync_Frontend_Json
+     *
+     * @todo fix ide object class detection for completions
+     */
+    protected function _getUit()
+    {
+        return parent::_getUit();
+    }
+    
+    /**
+     * Search for records matching given arguments
+     */
+    public function testSearchSyncDevices()
+    {
+        $device = $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE);
+        
+        $result = $this->_getUit()->searchSyncDevices(array(), array());
+        
+        $this->assertEquals(1, $result['totalcount']);
+        $this->assertEquals('iphone-abcd', $result['results'][0]['deviceid'], print_r($result['results'], true));
+    }
+    
+    /**
+     * deletes existing records
+     */
+    public function testDeleteSyncDevices()
+    {
+        $device = $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE);
+        $this->_getUit()->deleteSyncDevices(array($device->id));
+        $result = $this->_getUit()->searchSyncDevices(array(), array());
+        
+        $this->assertEquals(0, $result['totalcount']);
+    }
+    
+    /**
+     * Return a single record
+     * 
+     * @return array
+     */
+    public function testGetSyncDevice()
+    {
+        $device = $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE);
+        $fetchedDevice = $this->_getUit()->getSyncDevice($device->id);
+        
+        $this->assertTrue(is_array($fetchedDevice['owner_id']), print_r($fetchedDevice, true));
+        $this->assertEquals($this->_testUser->getId(), $fetchedDevice['owner_id']['accountId'], print_r($fetchedDevice['owner_id'], true));
+        
+        return $fetchedDevice;
+    }
+    
+    /**
+     * updates a record
+     */
+    public function testSaveSyncDevice()
+    {
+        $device = $this->testGetSyncDevice();
+        
+        $device['friendlyname'] = 'Very friendly name';
+        $device['owner_id'] = $device['owner_id']['accountId'];
+        $updatedDevice = $this->_getUit()->saveSyncDevice($device);
+        
+        $this->assertEquals($device['friendlyname'], $updatedDevice['friendlyname']);
+    }
+}
index e38142a..a54702e 100644 (file)
@@ -4,21 +4,16 @@
  * 
  * @package     ActiveSync
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2010-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * abstract test class for activesync controller tests
  * 
  * @package     ActiveSync
  */
-abstract class ActiveSync_TestCase extends PHPUnit_Framework_TestCase
+abstract class ActiveSync_TestCase extends TestCase
 {
     /**
      * name of the application
@@ -28,18 +23,6 @@ abstract class ActiveSync_TestCase extends PHPUnit_Framework_TestCase
     protected $_applicationName;
     
     /**
-     * name of the controller
-     * 
-     * @var string
-     */
-    protected $_controllerName;
-    
-    /**
-     * @var ActiveSync_Controller_Abstract controller
-     */
-    protected $_controller;
-    
-    /**
      * @var ActiveSync_Model_Device
      */
     protected $_device;
@@ -68,7 +51,7 @@ abstract class ActiveSync_TestCase extends PHPUnit_Framework_TestCase
      */
     protected function setUp()
     {
-        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+        parent::setUp();
         
         $this->_testUser          = Tinebase_Core::getUser();
         $this->_specialFolderName = strtolower($this->_applicationName) . '-root';
@@ -90,202 +73,6 @@ abstract class ActiveSync_TestCase extends PHPUnit_Framework_TestCase
         Syncroton_Registry::setEmailDataClass('ActiveSync_Controller_Email');
         Syncroton_Registry::setTasksDataClass('ActiveSync_Controller_Tasks');
     }
-
-    /**
-     * (non-PHPdoc)
-     * @see PHPUnit_Framework_TestCase::tearDown()
-     */
-    protected function tearDown()
-    {
-        Tinebase_TransactionManager::getInstance()->rollBack();
-    }
-    
-    /**
-     *
-     * @return Syncroton_Model_Folder
-     */
-    public function testCreateFolder()
-    {
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-        
-        $syncrotonFolder = $controller->createFolder(new Syncroton_Model_Folder(array(
-            'parentId' => 0, 
-            'displayName' => 'TestFolder'
-        )));
-    
-        $this->assertTrue(!empty($syncrotonFolder->serverId));
-        
-        return $syncrotonFolder;
-    }
-    
-    /**
-     * 
-     * @return Syncroton_Model_Folder
-     */
-    public function testUpdateFolder()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-        $syncrotonFolder->displayName = 'RenamedTestFolder';
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $updatedSyncrotonFolder = $controller->updateFolder($syncrotonFolder);
-        
-        $allFolders = $controller->getAllFolders();
-        
-        $this->assertArrayHasKey($syncrotonFolder->serverId, $allFolders);
-        $this->assertEquals('RenamedTestFolder', $allFolders[$syncrotonFolder->serverId]->displayName);
-        
-        return $updatedSyncrotonFolder;
-    }
-    
-    /**
-     * @return Syncroton_Model_Folder
-     */
-    public function testUpdateFolderAndroid()
-    {
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_ANDROID), new Tinebase_DateTime(null, null, 'de_DE'));
-        
-        $syncrotonFolders = $controller->getAllFolders();
-    
-        #var_dump($syncrotonFolders); return;
-        
-        $this->setExpectedException('Syncroton_Exception_UnexpectedValue');
-        
-        $updatedSyncrotonFolder = $controller->updateFolder($syncrotonFolders[$this->_specialFolderName]);
-    }
-    
-    /**
-     * test if changed folders got returned
-     */
-    public function testGetChangedFolders()
-    {
-        $syncrotonFolder = $this->testUpdateFolder();
-        
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-        
-        $changedFolders = $controller->getChangedFolders(Tinebase_DateTime::now()->subMinute(1), Tinebase_DateTime::now());
-        
-        //var_dump($changedFolders);
-        
-        $this->assertEquals(1, count($changedFolders));
-        $this->assertArrayHasKey($syncrotonFolder->serverId, $changedFolders);
-    }
-    
-    public function testDeleteFolder()
-    {
-        $this->markTestIncomplete('not yet implemented in controller');
-        
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $controller->deleteFolder($syncrotonFolder);
-    }
-    
-    public function testGetAllFolders()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $allSyncrotonFolders = $controller->getAllFolders();
-        
-        $this->assertArrayHasKey($syncrotonFolder->serverId, $allSyncrotonFolders);
-        $this->assertArrayNotHasKey($this->_specialFolderName, $allSyncrotonFolders);
-        $this->assertTrue($allSyncrotonFolders[$syncrotonFolder->serverId] instanceof Syncroton_Model_Folder);
-        $this->assertEquals($syncrotonFolder->serverId, $allSyncrotonFolders[$syncrotonFolder->serverId]->serverId, 'serverId mismatch');
-        $this->assertEquals($syncrotonFolder->parentId, $allSyncrotonFolders[$syncrotonFolder->serverId]->parentId, 'parentId mismatch');
-        $this->assertEquals($syncrotonFolder->displayName, $allSyncrotonFolders[$syncrotonFolder->serverId]->displayName);
-        $this->assertTrue(!empty($allSyncrotonFolders[$syncrotonFolder->serverId]->type));
-    }
-    
-    public function testGetAllFoldersPalm()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_WEBOS), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $allSyncrotonFolders = $controller->getAllFolders();
-        
-        $this->assertArrayHasKey($this->_specialFolderName, $allSyncrotonFolders, "key {$this->_specialFolderName} not found in " . print_r($allSyncrotonFolders, true));
-    }
-    
-    /**
-     * testDeleteEntry
-     */
-    public function testDeleteEntry()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-        
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-        
-        list($serverId, $syncrotonContact) = $this->testCreateEntry($syncrotonFolder);
-        
-        $controller->deleteEntry($syncrotonFolder->serverId, $serverId, null);
-        
-        try {
-            $syncrotonContact = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
-            $this->fail('should have thrown Syncroton_Exception_NotFound: '
-                . var_export($syncrotonContact, TRUE)
-                . ' tine contact: ' . print_r(Addressbook_Controller_Contact::getInstance()->get($serverId)->toArray(), TRUE));
-        } catch (Syncroton_Exception_NotFound $senf) {
-            $this->assertEquals('Syncroton_Exception_NotFound', get_class($senf));
-        }
-    }
-    
-    public function testGetInvalidEntry()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $this->setExpectedException('Syncroton_Exception_NotFound');
-    
-        $syncrotonContact = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), 'jdszfegd63gfrk');
-    }
-    
-    /**
-     * test get changed entries
-     */
-    public function testGetChangedEntries()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        list($serverId, $syncrotonContact) = $this->testUpdateEntry($syncrotonFolder);
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
-    
-        $changedEntries = $controller->getChangedEntries($syncrotonFolder->serverId, new DateTime('2000-01-01'));
-        
-        $this->assertContains($serverId, $changedEntries, 'did not get changed record id in ' . print_r($changedEntries, TRUE));
-    }
-    
-    /**
-     * test get changed entries for android
-     */
-    public function testGetChangedEntriesAndroid()
-    {
-        $syncrotonFolder = $this->testCreateFolder();
-    
-        list($serverId, $syncrotonContact) = $this->testUpdateEntry($syncrotonFolder);
-    
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_ANDROID), Tinebase_DateTime::now());
-    
-        $changedEntries = $controller->getChangedEntries($this->_specialFolderName, new Tinebase_DateTime('2000-01-01'));
-    
-        $this->assertContains($serverId, $changedEntries, 'did not get changed record id in ' . print_r($changedEntries, TRUE));
-    }
-    
-    /**
-     * test convert from XML to Tine 2.0 model
-     */
-    abstract public function testCreateEntry($syncrotonFolder = null);
-    
-    /**
-     * test xml generation for sync to client
-     */
-    abstract public function testUpdateEntry($syncrotonFolder = null);
     
     /**
      * create container with sync grant
@@ -396,20 +183,6 @@ abstract class ActiveSync_TestCase extends PHPUnit_Framework_TestCase
     }
     
     /**
-     * get application activesync controller
-     * 
-     * @param ActiveSync_Model_Device $_device
-     */
-    protected function _getController(ActiveSync_Model_Device $_device)
-    {
-        if ($this->_controller === null) {
-            $this->_controller = Syncroton_Data_Factory::factory($this->_class, $_device, new Tinebase_DateTime(null, null, 'de_DE'));
-        } 
-        
-        return $this->_controller;
-    }
-    
-    /**
      * returns a test event
      * 
      * @param Tinebase_Model_Container $personalContainer
diff --git a/tine20/ActiveSync/Acl/Rights.php b/tine20/ActiveSync/Acl/Rights.php
new file mode 100644 (file)
index 0000000..adb367b
--- /dev/null
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     ActiveSync
+ * @subpackage  Acl
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * 
+ */
+
+/**
+ * this class handles the rights for the ActiveSync application
+ * 
+ * a right is always specific to an application and not to a record
+ * examples for rights are: admin, run
+ * 
+ * to add a new right you have to do these 3 steps:
+ * - add a constant for the right
+ * - add the constant to the $addRights in getAllApplicationRights() function
+ * . add getText identifier in getTranslatedRightDescriptions() function
+ * 
+ * @package     ActiveSync
+ * @subpackage  Acl
+ */
+class ActiveSync_Acl_Rights extends Tinebase_Acl_Rights_Abstract
+{
+    /**
+     * the right to manage shared contact favorites
+     * 
+     * @staticvar string
+     */
+    const MANAGE_DEVICES = 'manage_devices';
+    
+    /**
+     * holds the instance of the singleton
+     *
+     * @var ActiveSync_Acl_Rights
+     */
+    private static $_instance = NULL;
+    
+    /**
+     * the clone function
+     *
+     * disabled. use the singleton
+     */
+    private function __clone() 
+    {
+    }
+    
+    /**
+     * the constructor
+     *
+     */
+    private function __construct()
+    {
+        
+    }    
+    
+    /**
+     * the singleton pattern
+     *
+     * @return ActiveSync_Acl_Rights
+     */
+    public static function getInstance() 
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new ActiveSync_Acl_Rights;
+        }
+        
+        return self::$_instance;
+    }
+    
+    /**
+     * get all possible application rights
+     *
+     * @return  array   all application rights
+     */
+    public function getAllApplicationRights()
+    {
+        
+        $allRights = parent::getAllApplicationRights();
+        
+        $addRights = array(
+            self::MANAGE_DEVICES,
+        );
+        $allRights = array_merge($allRights, $addRights);
+        
+        return $allRights;
+    }
+
+    /**
+     * get translated right descriptions
+     * 
+     * @return  array with translated descriptions for this applications rights
+     */
+    public static function getTranslatedRightDescriptions()
+    {
+        $translate = Tinebase_Translation::getTranslation('ActiveSync');
+        
+        $rightDescriptions = array(
+            self::MANAGE_DEVICES => array(
+                'text'          => $translate->_('Manage ActiveSync devices'),
+                'description'   => $translate->_('See, edit and delete ActiveSync devices'),
+            ),
+        );
+        
+        $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
+        return $rightDescriptions;
+    }
+}
index 6b97072..d4dcdd2 100644 (file)
         {
           "text": "DeviceStore.js",
           "path": "js/"
+        },
+        {
+          "text": "SyncDevices.js",
+          "path": "js/"
+        },
+        {
+          "text": "EditDialog.js",
+          "path": "js/"
+        },
+        {
+          "text": "SyncDevicesGridPanel.js",
+          "path": "js/"
+        },
+        {
+          "text": "AdminPanel.js",
+          "path": "js/"
         }
       ]
     },
diff --git a/tine20/ActiveSync/Controller/SyncDevices.php b/tine20/ActiveSync/Controller/SyncDevices.php
new file mode 100644 (file)
index 0000000..6a266cb
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     ActiveSync
+ * @subpackage  Controller
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * @copyright   Copyright (c) 2014-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Sync devices controller for ActiveSync application
+ *
+ * @package     ActiveSync
+ * @subpackage  Controller
+ */
+class ActiveSync_Controller_SyncDevices extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * application name (is needed in checkRight())
+     *
+     * @var string
+     */
+    protected $_applicationName = 'ActiveSync';
+    
+    /**
+     * Model name
+     *
+     * @var string
+     */
+    protected $_modelName = 'ActiveSync_Model_Device';
+    
+    /**
+     * check for container ACLs
+     *
+     * @var boolean
+     *
+     * @todo rename to containerACLChecks
+     */
+    protected $_doContainerACLChecks = false;
+    
+    /**
+     * holds the instance of the singleton
+     *
+     * @var ActiveSync_Controller_SyncDevices
+     */
+    private static $_instance = null;
+    
+    /**
+     * the constructor
+     *
+     * don't use the constructor. use the singleton 
+     */
+    private function __construct() 
+    {
+        $this->_backend = new ActiveSync_Backend_Device();
+    }
+
+    /**
+     * don't clone. Use the singleton.
+     *
+     */
+    private function __clone() 
+    {
+    }
+    
+    /**
+     * the singleton pattern
+     *
+     * @return ActiveSync_Controller_SyncDevices
+     */
+    public static function getInstance() 
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new ActiveSync_Controller_SyncDevices;
+        }
+        
+        return self::$_instance;
+    }
+    
+    /**
+     * get list of access log entries
+     *
+     * @param Tinebase_Model_Filter_FilterGroup|optional $_filter
+     * @param Tinebase_Model_Pagination|optional $_pagination
+     * @param boolean $_getRelations
+     * @param boolean $_onlyIds
+     * @param string $_action for right/acl check
+     * @return Tinebase_Record_RecordSet|array
+     */
+    public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
+    {
+        $this->checkRight('MANAGE_DEVICES');
+        
+        return parent::search($_filter, $_pagination, $_getRelations, $_onlyIds, $_action);
+    }
+    
+    /**
+     * returns the total number of access logs
+     * 
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     * @param string $_action for right/acl check
+     * @return int
+     */
+    public function searchCount(Tinebase_Model_Filter_FilterGroup $_filter, $_action = 'get')
+    {
+        $this->checkRight('MANAGE_DEVICES');
+        
+        return parent::searchCount($_filter, $_action);
+    }
+    
+    /**
+     * delete access log entries
+     *
+     * @param   array $_ids list of logIds to delete
+     */
+    public function delete($_ids)
+    {
+        $this->checkRight('MANAGE_DEVICES');
+        
+        return parent::delete($_ids);
+    }
+
+    /**
+     * inspect update of one record (before update)
+     *
+     * @param   Tinebase_Record_Interface $_record      the update record
+     * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
+     * @return  void
+     */
+    protected function _inspectBeforeUpdate($_record, $_oldRecord)
+    {
+        // spoofing protection
+        $fieldsToUnset = array('id', 'deviceid', 'devicetype', 'policy_id', 'acsversion', 'useragent',
+            'model', 'os', 'oslanguage', 'pinglifetime', 'pingfolder', 'remotewipe', 'calendarfilter_id',
+            'contactsfilter_id', 'emailfilter_id', 'tasksfilter_id', 'lastping');
+        
+        foreach ($fieldsToUnset as $field) {
+            $_record->{$field} = $_oldRecord{$field};
+        }
+    }
+}
index c155145..317741e 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -58,4 +58,53 @@ class ActiveSync_Frontend_Json extends Tinebase_Frontend_Json_Abstract
             'userDevices' => $userDevices->toArray()
         );
     }
+    
+    /****************************** SyncDevices ******************************/
+    
+    /**
+     * Search for records matching given arguments
+     *
+     * @param array $filter
+     * @param array $paging
+     * @return array
+     */
+    public function searchSyncDevices($filter, $paging)
+    {
+        $result = $this->_search($filter, $paging, ActiveSync_Controller_SyncDevices::getInstance(), 'ActiveSync_Model_DeviceFilter');
+    
+        return $result;
+    }
+    
+    /**
+     * Return a single record
+     *
+     * @param   string $id
+     * @return  array record data
+     */
+    public function getSyncDevice($id)
+    {
+        return $this->_get($id, ActiveSync_Controller_SyncDevices::getInstance());
+    }
+    
+    /**
+     * creates/updates a record
+     *
+     * @param  array $recordData
+     * @return array created/updated record
+     */
+    public function saveSyncDevice($recordData)
+    {
+        return $this->_save($recordData, ActiveSync_Controller_SyncDevices::getInstance(), 'ActiveSync_Model_Device', 'id');
+    }
+    
+    /**
+     * deletes existing records
+     *
+     * @param  array  $ids
+     * @return string
+     */
+    public function deleteSyncDevices($ids)
+    {
+        return $this->_delete($ids, ActiveSync_Controller_SyncDevices::getInstance());
+    }
 }
index bd36f58..92034cb 100644 (file)
@@ -6,8 +6,7 @@
  * @subpackage  Model
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
- * 
+ * @copyright   Copyright (c) 2014-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -24,7 +23,7 @@
  * @property  string  $policykey          the current policykey
  * @property  string  $tasksfilter_id     the tasks filter id
  */
-class ActiveSync_Model_Device extends Tinebase_Record_Abstract #implements Syncroton_Model_IDevice
+class ActiveSync_Model_Device extends Tinebase_Record_Abstract
 {
     /**
      * key in $_validators/$_properties array for the filed which 
@@ -42,6 +41,20 @@ class ActiveSync_Model_Device extends Tinebase_Record_Abstract #implements Syncr
     protected $_application = 'ActiveSync';
     
     /**
+     * if foreign Id fields should be resolved on search and get from json
+     * should have this format:
+     *     array('Calendar_Model_Contact' => 'contact_id', ...)
+     * or for more fields:
+     *     array('Calendar_Model_Contact' => array('contact_id', 'customer_id), ...)
+     * (e.g. resolves contact_id with the corresponding Model)
+     *
+     * @var array
+     */
+    protected static $_resolveForeignIdFields = array(
+        'Tinebase_Model_User' => array('owner_id'),
+    );
+    
+    /**
      * list of zend inputfilter
      * 
      * this filter get used when validating user generated content with Zend_Input_Filter
@@ -65,7 +78,7 @@ class ActiveSync_Model_Device extends Tinebase_Record_Abstract #implements Syncr
         'devicetype'            => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence'=>'required'),
         'owner_id'              => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence'=>'required'),
         'policy_id'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
-        'policykey'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        //'policykey'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'acsversion'            => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence'=>'required'),
         'useragent'             => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence'=>'required'),
         'model'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
@@ -81,6 +94,16 @@ class ActiveSync_Model_Device extends Tinebase_Record_Abstract #implements Syncr
         'contactsfilter_id'     => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'emailfilter_id'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'tasksfilter_id'        => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'lastping'              => array(Zend_Filter_Input::ALLOW_EMPTY => true)
+    );
+    
+    /**
+     * name of fields containing datetime or or an array of datetime information
+     *
+     * @var array list of datetime fields
+     */
+    protected $_datetimeFields = array(
+        'lastping'
     );
     
     /**
index 8993c3c..1df6340 100644 (file)
@@ -37,7 +37,7 @@ class ActiveSync_Model_DeviceFilter extends Tinebase_Model_Filter_FilterGroup
      */
     protected $_filterModel = array(
         'id'                   => array('filter' => 'Tinebase_Model_Filter_Id'),
-        #'query'                => array('filter' => 'Tinebase_Model_Filter_Query', 'options' => array('fields' => array('n_family', 'n_given', 'org_name', 'email', 'adr_one_locality',))),
+        'query'                => array('filter' => 'Tinebase_Model_Filter_Query', 'options' => array('fields' => array('deviceid', 'devicetype', 'friendlyname'))),
         'deviceid'             => array('filter' => 'Tinebase_Model_Filter_Text'),
         'owner_id'             => array('filter' => 'Tinebase_Model_Filter_Text'),
     );
diff --git a/tine20/ActiveSync/js/AdminPanel.js b/tine20/ActiveSync/js/AdminPanel.js
new file mode 100644 (file)
index 0000000..94017e0
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     ActiveSync
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+Ext.namespace('Tine.ActiveSync');
+
+/**
+ * admin settings panel
+ * 
+ * @namespace   Tine.ActiveSync
+ * @class       Tine.ActiveSync.AdminPanel
+ * @extends     Ext.TabPanel
+ * 
+ * <p>ActiveSync Admin Panel</p>
+ * <p><pre>
+ * </pre></p>
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schuele <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @param       {Object} config
+ * @constructor
+ * Create a new Tine.ActiveSync.AdminPanel
+ */
+Tine.ActiveSync.AdminPanel = Ext.extend(Ext.TabPanel, {
+
+    border: false,
+    activeTab: 0,
+
+    /**
+     * @private
+     */
+    initComponent: function() {
+        
+        this.app = Tine.Tinebase.appMgr.get('ActiveSync');
+        
+        this.items = [new Tine.ActiveSync.SyncDevicesGridPanel({
+            title: this.app.i18n._('Sync Devices'),
+            // TODO make this work
+            disabled: ! Tine.Tinebase.common.hasRight('manage_devices', 'ActiveSync')
+        })];
+        
+        Tine.ActiveSync.AdminPanel.superclass.initComponent.call(this);
+    }
+});
+    
+/**
+ * ActiveSync Admin Panel Popup
+ * 
+ * @param   {Object} config
+ * @return  {Ext.ux.Window}
+ */
+Tine.ActiveSync.AdminPanel.openWindow = function (config) {
+    var window = Tine.WindowFactory.getWindow({
+        width: 700,
+        height: 470,
+        name: 'activesync-manage-syncdevice',
+        contentPanelConstructor: 'Tine.ActiveSync.AdminPanel',
+        contentPanelConstructorConfig: config
+    });
+};
diff --git a/tine20/ActiveSync/js/EditDialog.js b/tine20/ActiveSync/js/EditDialog.js
new file mode 100644 (file)
index 0000000..995bd4b
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * @copyright   Copyright (c) 2014-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+/*global Ext, Tine*/
+
+Ext.ns('Tine.ActiveSync.syncdevices');
+
+/**
+ * @namespace   Tine.ActiveSync.syncdevices
+ * @class       Tine.ActiveSync.SyncDeviceEditDialog
+ * @extends     Tine.widgets.dialog.EditDialog
+ * 
+ * <p>Sync devices edit dialog</p>
+ * <p>
+ * </p>
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * 
+ * @param       {Object} config
+ * @constructor
+ * Create a new Tine.Admin.SyncDeviceEditDialog
+ */
+Tine.ActiveSync.SyncDeviceEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
+    
+    /**
+     * @private
+     */
+    windowNamePrefix: 'syncdeviceEditWindow_',
+    appName: 'ActiveSync',
+    recordClass: Tine.ActiveSync.Model.SyncDevice,
+    recordProxy: Tine.ActiveSync.syncdevicesBackend,
+    evalGrants: false,
+    
+    /**
+     * executed after record got updated from proxy
+     */
+    onRecordLoad: function () {
+        Tine.ActiveSync.SyncDeviceEditDialog.superclass.onRecordLoad.apply(this, arguments);
+    },
+    
+    /**
+     * executed when record gets updated from form
+     */
+    onRecordUpdate: function () {
+        Tine.ActiveSync.SyncDeviceEditDialog.superclass.onRecordUpdate.apply(this, arguments);
+    },
+    
+    /**
+     * returns dialog
+     */
+    getFormItems: function () {
+        return {
+            layout: 'vbox',
+            layoutConfig: {
+                align: 'stretch',
+                pack: 'start'
+            },
+            border: false,
+            items: [{
+                xtype: 'columnform',
+                border: false,
+                autoHeight: true,
+                items: [
+                    [{
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('Device ID'),
+                        name: 'deviceid',
+                        allowBlank: false,
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('Devicetype'),
+                        name: 'devicetype',
+                        allowBlank: false,
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.333,
+                        fieldLabel: this.app.i18n._('Owner'),
+                        name: 'owner_id',
+                        allowBlank: false,
+                        xtype: 'addressbookcontactpicker',
+                        userOnly: true,
+                        useAccountRecord: true,
+                        blurOnSelect: true,
+                        selectOnFocus: true,
+                        maxLength: 40
+                    }], [{
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('Policy'),
+                        name: 'policy_id',
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('AS Version'),
+                        name: 'acsversion',
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.333,
+                        fieldLabel: this.app.i18n._('Useragent'),
+                        name: 'useragent',
+                        readOnly: true,
+                        maxLength: 40
+                    }], [{
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('Model'),
+                        name: 'model',
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('IMEI'),
+                        name: 'imei',
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.333,
+                        fieldLabel: this.app.i18n._('Friendly Name'),
+                        name: 'friendlyname',
+                        maxLength: 40
+                    }], [{
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('OS'),
+                        name: 'os',
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.33,
+                        fieldLabel: this.app.i18n._('OS Language'),
+                        name: 'oslanguage',
+                        readOnly: true,
+                        maxLength: 40
+                    }, {
+                        columnWidth: 0.333,
+                        fieldLabel: this.app.i18n._('Phonenumber'),
+                        name: 'phonenumber',
+                        maxLength: 40
+                    }]
+                ]
+            }]
+        };
+    }
+});
+
+/**
+ * Container Edit Popup
+ * 
+ * @param   {Object} config
+ * @return  {Ext.ux.Window}
+ */
+Tine.ActiveSync.SyncDeviceEditDialog.openWindow = function (config) {
+    var window = Tine.WindowFactory.getWindow({
+        width: 600,
+        height: 400,
+        name: Tine.ActiveSync.SyncDeviceEditDialog.prototype.windowNamePrefix + Ext.id(),
+        contentPanelConstructor: 'Tine.ActiveSync.SyncDeviceEditDialog',
+        contentPanelConstructorConfig: config
+    });
+    return window;
+};
diff --git a/tine20/ActiveSync/js/SyncDevices.js b/tine20/ActiveSync/js/SyncDevices.js
new file mode 100644 (file)
index 0000000..61fd6c3
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * @copyright   Copyright (c) 2014-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/*global Ext, Tine*/ 
+
+Ext.ns('Tine.ActiveSync.syncdevices');
+
+/**
+ * Containers 'mainScreen'
+ * 
+ * @static
+ */
+Tine.ActiveSync.syncdevices.show = function () {
+    var app = Tine.Tinebase.appMgr.get('ActiveSync');
+    if (! Tine.ActiveSync.syncDevicesGridPanel) {
+        Tine.ActiveSync.syncDevicesGridPanel = new Tine.ActiveSync.SyncDevicesGridPanel({
+            app: app,
+            asAdminModule: true
+        });
+    } else {
+        Tine.ActiveSync.syncDevicesGridPanel.loadGridData.defer(100, Tine.ActiveSync.syncDevicesGridPanel, []);
+    }
+    
+    Tine.Tinebase.MainScreen.setActiveContentPanel(Tine.ActiveSync.syncDevicesGridPanel, true);
+    Tine.Tinebase.MainScreen.setActiveToolbar(Tine.ActiveSync.syncDevicesGridPanel.actionToolbar, true);
+};
+
+/************** models *****************/
+Ext.ns('Tine.ActiveSync.Model');
+
+/**
+ * Model of an account
+ */
+Tine.ActiveSync.Model.SyncDeviceArray = [
+    { name: 'id' },
+    { name: 'deviceid' },
+    { name: 'devicetype' },
+    { name: 'owner_id' },
+    { name: 'policy_id' },
+    //{ name: 'policykey' },
+    { name: 'acsversion' },
+    { name: 'useragent' },
+    { name: 'model' },
+    { name: 'imei' },
+    { name: 'friendlyname' },
+    { name: 'os' },
+    { name: 'oslanguage' },
+    { name: 'phonenumber' },
+    { name: 'pinglifetime' },
+    { name: 'pingfolder' },
+    { name: 'remotewipe' },
+    { name: 'calendarfilter_id' },
+    { name: 'contactsfilter_id' },
+    { name: 'emailfilter_id' },
+    { name: 'tasksfilter_id' },
+    { name: 'lastping', type: 'date', dateFormat: Date.patterns.ISO8601Long }
+];
+
+Tine.ActiveSync.Model.SyncDevice = Tine.Tinebase.data.Record.create(Tine.ActiveSync.Model.SyncDeviceArray, {
+    appName: 'ActiveSync',
+    modelName: 'SyncDevice',
+    idProperty: 'id',
+    //titleProperty: 'name',
+    titleProperty: 'deviceid',
+    // ngettext('SyncDevice', 'SyncDevices', n);
+    recordName: 'SyncDevice',
+    recordsName: 'SyncDevices'
+});
+
+/**
+ * returns default account data
+ * 
+ * @namespace Tine.ActiveSync.Model.SyncDevice
+ * @static
+ * @return {Object} default data
+ */
+Tine.ActiveSync.Model.SyncDevice.getDefaultData = function () {
+    return {};
+};
+
+/************** backend *****************/
+
+Tine.ActiveSync.syncdevicesBackend = new Tine.Tinebase.data.RecordProxy({
+    appName: 'ActiveSync',
+    modelName: 'SyncDevice',
+    recordClass: Tine.ActiveSync.Model.SyncDevice,
+    idProperty: 'id'
+});
diff --git a/tine20/ActiveSync/js/SyncDevicesGridPanel.js b/tine20/ActiveSync/js/SyncDevicesGridPanel.js
new file mode 100644 (file)
index 0000000..98c21af
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     ActiveSync
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+Ext.ns('Tine.ActiveSync');
+
+/**
+ * @namespace Tine.ActiveSync
+ * @class     Tine.ActiveSync.SyncDevicesGridPanel
+ * @extends   Tine.widgets.grid.GridPanel
+ * SyncDevicess Grid Panel <br>
+ * 
+ * @author      Cornelius Weiss <c.weiss@metaways.de>
+ */
+Tine.ActiveSync.SyncDevicesGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
+    /**
+     * @cfg
+     */
+    recordClass: Tine.ActiveSync.Model.SyncDevice,
+    recordProxy: Tine.ActiveSync.syncdevicesBackend,
+    defaultSortInfo: {field: 'deviceid', direction: 'ASC'},
+    evalGrants: false,
+    gridConfig: {
+        autoExpandColumn: 'deviceid'
+    },
+    addButton: false,
+    asAdminModule: false,
+    
+    /**
+     * initComponent
+     */
+    initComponent: function() {
+        this.app = Tine.Tinebase.appMgr.get('ActiveSync');
+        this.gridConfig.cm = this.getColumnModel();
+        Tine.ActiveSync.SyncDevicesGridPanel.superclass.initComponent.call(this);
+    },
+    
+    /**
+     * returns column model
+     * 
+     * @return Ext.grid.ColumnModel
+     * @private
+     */
+    getColumnModel: function() {
+        return new Ext.grid.ColumnModel({
+            defaults: {
+                sortable: true,
+                hidden: true,
+                resizable: true
+            },
+            columns: this.getColumns()
+        });
+    },
+    
+    /**
+     * returns columns
+     * @private
+     * @return Array
+     */
+    getColumns: function(){
+        return [
+            { header: this.app.i18n._('ID'),             id: 'id',                dataIndex: 'id',                hidden: true,  width: 50},
+            { header: this.app.i18n._('Device ID'),      id: 'deviceid',          dataIndex: 'deviceid',          hidden: false, width: 200},
+            { header: this.app.i18n._('Devicetype'),     id: 'devicetype',        dataIndex: 'devicetype',        hidden: false, width: 100},
+            { header: this.app.i18n._('Owner'),          id: 'owner_id',          dataIndex: 'owner_id',          hidden: false, width: 80,  renderer: Tine.Tinebase.common.usernameRenderer},
+            { header: this.app.i18n._('Policy'),         id: 'policy_id',         dataIndex: 'policy_id',         hidden: false, width: 200},
+            { header: this.app.i18n._('AS Version'),     id: 'acsversion',        dataIndex: 'acsversion',        hidden: false, width: 100},
+            { header: this.app.i18n._('Useragent'),      id: 'useragent',         dataIndex: 'useragent',         hidden: true,  width: 200},
+            { header: this.app.i18n._('Model'),          id: 'model',             dataIndex: 'model',             hidden: false, width: 200},
+            { header: this.app.i18n._('IMEI'),           id: 'imei',              dataIndex: 'imei',              hidden: true,  width: 200},
+            { header: this.app.i18n._('Friendly Name'),  id: 'friendlyname',      dataIndex: 'friendlyname',      hidden: false, width: 200},
+            { header: this.app.i18n._('OS'),             id: 'os',                dataIndex: 'os',                hidden: false, width: 200},
+            { header: this.app.i18n._('OS Language'),    id: 'oslanguage',        dataIndex: 'oslanguage',        hidden: true,  width: 200},
+            { header: this.app.i18n._('Phonenumber'),    id: 'phonenumber',       dataIndex: 'phonenumber',       hidden: false, width: 200},
+            { header: this.app.i18n._('Ping Lifetime'),  id: 'pinglifetime',      dataIndex: 'pinglifetime',      hidden: true,  width: 200},
+            //{ header: this.app.i18n._('Ping Folder'),    id: 'pingfolder',        dataIndex: 'pingfolder',        hidden: false, width: 200},
+            { header: this.app.i18n._('Remote Wipe'),    id: 'remotewipe',        dataIndex: 'remotewipe',        hidden: false, width: 100},
+            { header: this.app.i18n._('Calendarfilter'), id: 'calendarfilter_id', dataIndex: 'calendarfilter_id', hidden: true,  width: 200},
+            { header: this.app.i18n._('Contactsfilter'), id: 'contactsfilter_id', dataIndex: 'contactsfilter_id', hidden: true,  width: 200},
+            { header: this.app.i18n._('Emailfilter'),    id: 'emailfilter_id',    dataIndex: 'emailfilter_id',    hidden: true,  width: 200},
+            { header: this.app.i18n._('Tasksfilter'),    id: 'tasksfilter_id',    dataIndex: 'tasksfilter_id',    hidden: true,  width: 200},
+            { header: this.app.i18n._('Last Ping'),      id: 'lastping',          dataIndex: 'lastping',          hidden: false, width: 200, renderer: Tine.Tinebase.common.dateTimeRenderer}
+        ];
+    },
+        /**
+     * initialises filter toolbar
+     */
+    initFilterPanel: function() {
+        this.filterToolbar = new Tine.widgets.grid.FilterToolbar({
+            filterModels: [
+                {label: this.app.i18n._('Quicksearch'),     field: 'query',    operators: ['contains']},
+                {label: this.app.i18n._('Device ID'),       field: 'deviceid', operators: ['contains']}
+            ],
+            defaultFilter: 'query',
+            /*filters: [
+                {field: 'deviceid', operator: 'equals', value: 'shared'}
+            ],*/
+            plugins: [
+                new Tine.widgets.grid.FilterToolbarQuickFilterPlugin()
+            ]
+        });
+        this.plugins = this.plugins || [];
+        this.plugins.push(this.filterToolbar);
+    },
+    
+    initLayout: function() {
+        this.supr().initLayout.call(this);
+        
+        if (! this.asAdminModule) {
+            this.items.push({
+                region : 'north',
+                height : 55,
+                border : false,
+                items  : this.actionToolbar
+            });
+        }
+    }
+});
index b08ae22..ceb7895 100644 (file)
     background-image:url(../../images/oxygen/16x16/apps/kexi.png) !important;
 }
 
-
 .x-tree-node-collapsed .x-tree-node-icon.x-tree-node-leaf-checkbox, 
 .x-tree-node-expanded .x-tree-node-icon.x-tree-node-leaf-checkbox, 
 .x-tree-node-leaf .x-tree-node-icon.x-tree-node-leaf-checkbox ,
 .x-tree-node-leaf-checkbox {
-       width:0px;
+    width:0px;
 }
index ee2edae..f2e8864 100644 (file)
@@ -19,7 +19,7 @@ Tine.Admin = function () {
      */
     var getInitialTree = function (translation) {
         
-        return [{
+        var tree = [{
             text: translation.ngettext('User', 'Users', 50),
             cls: 'treemain',
             iconCls: 'admin-node-user',
@@ -146,6 +146,24 @@ Tine.Admin = function () {
             dataPanelType: "serverinfo",
             viewRight: 'serverinfo'
         }];
+        
+        // TODO find a generic hooking mechanism
+        if (Tine.Tinebase.appMgr.get('ActiveSync') && Tine.Tinebase.common.hasRight('manage_devices', 'ActiveSync')) {
+            tree.push({
+                text: translation.gettext('ActiveSync Devices'),
+                cls: "treemain",
+                iconCls: 'activesync-device-standard',
+                allowDrag: false,
+                allowDrop: true,
+                id: "devices",
+                children: [],
+                leaf: null,
+                expanded: true,
+                dataPanelType: "devices"
+            });
+        }
+        
+        return tree;
     };
 
     /**
@@ -268,6 +286,10 @@ Tine.Admin = function () {
                    Tine.Tinebase.MainScreen.setActiveToolbar(this.infoPanelToolbar, true);
                }, this);
                break;
+           // TODO find a generic hooking mechanism
+            case 'devices':
+                Tine.ActiveSync.syncdevices.show();
+                break;
             }
         }, this);
 
@@ -291,13 +313,6 @@ Tine.Admin = function () {
 
         treePanel.on('contextmenu', function (node, event) {
             event.stopEvent();
-            //node.select();
-            //node.getOwnerTree().fireEvent('click', _node);
-            /* switch(node.attributes.contextMenuClass) {
-                case 'ctxMenuContactsTree':
-                    ctxMenuContactsTree.showAt(event.getXY());
-                    break;
-            } */
         });
 
         return treePanel;
index 9b43998..a004ee0 100644 (file)
@@ -287,7 +287,15 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
      * @property splitAddButton
      */
     splitAddButton: true,
-
+    
+    /**
+     * add "create new record" button
+     * 
+     * @type Bool
+     * @property addButton
+     */
+    addButton: false,
+    
     layout: 'border',
     border: false,
     stateful: true,
@@ -542,14 +550,14 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
             scope: this
         });
 
-        this.action_addInNewWindow = new Ext.Action({
+        this.action_addInNewWindow = (this.addButton) ? new Ext.Action({
             requiredGrant: 'addGrant',
             actionType: 'add',
             text: this.i18nAddActionText ? this.app.i18n._hidden(this.i18nAddActionText) : String.format(_('Add {0}'), this.i18nRecordName),
             handler: this.onEditInNewWindow.createDelegate(this, [{actionType: 'add'}]),
             iconCls: (this.newRecordIcon !== null) ? this.newRecordIcon : this.app.appName + 'IconCls',
             scope: this
-        });
+        }) : null;
 
         this.actions_print = new Ext.Action({
             requiredGrant: 'readGrant',