0012796: implement replication client
authorPaul Mehrer <p.mehrer@metaways.de>
Tue, 28 Feb 2017 13:38:56 +0000 (14:38 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 15 Mar 2017 11:24:35 +0000 (12:24 +0100)
* add replication client to ModificationLog and adds replication slave config
* refactoring of Roles and Application models (modelconfig)
* remove Pluggable stuff
* refactoring of depended record handling

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

Change-Id: I572f619ef0a58cd938d025947fb4b6c3bcffd3a9
Reviewed-on: http://gerrit.tine20.com/customers/4281
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
43 files changed:
tests/tine20/Tinebase/AllTests.php
tests/tine20/Tinebase/ApplicationTest.php
tests/tine20/Tinebase/Pluggable/ConcreteTest.php [deleted file]
tests/tine20/Tinebase/Pluggable/DummyBackend.php [deleted file]
tests/tine20/Tinebase/Pluggable/DummyController.php [deleted file]
tests/tine20/Tinebase/Pluggable/DummyFrontend.php [deleted file]
tests/tine20/Tinebase/Pluggable/Plugin/DummyPlugin.php [deleted file]
tests/tine20/Tinebase/Timemachine/ModificationLogTest.php
tine20/Admin/Frontend/Json.php
tine20/Admin/Setup/Initialize.php
tine20/Tinebase/Acl/Roles.php
tine20/Tinebase/Backend/Abstract.php
tine20/Tinebase/Backend/Sql/Abstract.php
tine20/Tinebase/Config.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Controller/Record/Abstract.php
tine20/Tinebase/Frontend/Abstract.php
tine20/Tinebase/Frontend/Cli.php
tine20/Tinebase/Frontend/Http/Abstract.php
tine20/Tinebase/Frontend/Json/Abstract.php
tine20/Tinebase/Helper.php
tine20/Tinebase/Model/Application.php
tine20/Tinebase/Model/Filter/FilterGroup.php
tine20/Tinebase/Model/Role.php
tine20/Tinebase/Model/RoleFilter.php
tine20/Tinebase/Model/RoleMember.php [new file with mode: 0644]
tine20/Tinebase/Model/RoleMemberFilter.php [new file with mode: 0644]
tine20/Tinebase/Model/RoleRight.php
tine20/Tinebase/Model/RoleRightFilter.php [new file with mode: 0644]
tine20/Tinebase/ModelConfiguration.php
tine20/Tinebase/Pluggable/Abstract.php [deleted file]
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Record/Interface.php
tine20/Tinebase/Record/RecordSet.php
tine20/Tinebase/Record/RecordSetDiff.php
tine20/Tinebase/Role.php [new file with mode: 0644]
tine20/Tinebase/RoleMember.php [new file with mode: 0644]
tine20/Tinebase/RoleRight.php [new file with mode: 0644]
tine20/Tinebase/Scheduler/Task.php
tine20/Tinebase/Setup/Initialize.php
tine20/Tinebase/Setup/Update/Release10.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Timemachine/ModificationLog.php

index 4643977..8f444b0 100644 (file)
@@ -70,7 +70,6 @@ class Tinebase_AllTests
         $suite->addTestSuite('Tinebase_TagsTest');
         $suite->addTestSuite('Tinebase_Log_AllTests');
         $suite->addTestSuite('Tinebase_Redis_QueueTest');
-        $suite->addTestSuite('Tinebase_Pluggable_ConcreteTest');
         $suite->addTestSuite('Tinebase_TempFileTest');
         $suite->addTestSuite('Tinebase_Server_AllTests');
         $suite->addTestSuite('Tinebase_LockTest');
index 5bb69ed..5efd07e 100644 (file)
@@ -427,8 +427,9 @@ class Tinebase_ApplicationTest extends TestCase
                 'Tinebase_Model_ImportException',
                 'Tinebase_Model_User',
                 'Tinebase_Model_Role',
-                'Tinebase_Model_Note',
                 'Tinebase_Model_RoleRight',
+                'Tinebase_Model_RoleMember',
+                'Tinebase_Model_Note',
                 'Tinebase_Model_Pagination',
                 'Tinebase_Model_TempFile',
                 'Tinebase_Model_ImportExportDefinition',
diff --git a/tests/tine20/Tinebase/Pluggable/ConcreteTest.php b/tests/tine20/Tinebase/Pluggable/ConcreteTest.php
deleted file mode 100644 (file)
index 951907a..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * 
- */
-
-/**
- * Test helper
- */
-require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
- * Test class for Tinebase_Pluggable
- */
-class Tinebase_Pluggable_ConcreteTest extends PHPUnit_Framework_TestCase
-{
-    protected $frontend = NULL;
-    protected $controller = NULL;
-    protected $backend = NULL;
-    
-    /**
-     * Sets up the fixture, for example
-     * This method is called before a test is executed.
-     *
-     * @access protected
-     */
-    protected function setUp()
-    {
-        // creates layer instances
-        $this->frontend = new Tinebase_Pluggable_DummyFrontend();
-        $this->controller = new Tinebase_Pluggable_DummyController();
-        $this->backend = new Tinebase_Pluggable_DummyBackend();
-        
-        // injects plugin into layers
-        Tinebase_Frontend_Abstract::attachPlugin('dummyPluginMethod', 'Tinebase_Pluggable_Plugin_DummyPlugin');
-        Tinebase_Controller_Abstract::attachPlugin('dummyPluginMethod', 'Tinebase_Pluggable_Plugin_DummyPlugin');
-        Tinebase_Backend_Abstract::attachPlugin('dummyPluginMethod', 'Tinebase_Pluggable_Plugin_DummyPlugin');
-    }
-    
-    /**
-     * Verifies if plugin is callable from layers
-     */
-    public function testCallPluginMethod()
-    {
-        $expected = 'dummyPluginReturn';
-        
-        $frontendReturn = $this->frontend->dummyPluginMethod();
-        $controllerReturn = $this->controller->dummyPluginMethod();
-        $backendReturn = $this->backend->dummyPluginMethod();
-        
-        $this->assertEquals($expected, $frontendReturn);
-        $this->assertEquals($expected, $controllerReturn);
-        $this->assertEquals($expected, $backendReturn);
-    }
-}
diff --git a/tests/tine20/Tinebase/Pluggable/DummyBackend.php b/tests/tine20/Tinebase/Pluggable/DummyBackend.php
deleted file mode 100644 (file)
index b18a2dd..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * 
- */
-/**
- * Mock for Backend
- */
-class Tinebase_Pluggable_DummyBackend extends Tinebase_Backend_Sql
-{
-    /**
-     * Table name without prefix
-     *
-     * @var string
-     */
-    protected $_tableName = 'alarm';
-    
-    /**
-     * Model name
-     *
-     * @var string
-     */
-    protected $_modelName = 'Tinebase_Model_Alarm';
-}
\ No newline at end of file
diff --git a/tests/tine20/Tinebase/Pluggable/DummyController.php b/tests/tine20/Tinebase/Pluggable/DummyController.php
deleted file mode 100644 (file)
index d8fbe31..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * 
- */
-/**
- * Mock for Controller
-  */
-class Tinebase_Pluggable_DummyController extends Tinebase_Controller_Abstract
-{
-    
-    /**
-     * Instance of Controller Object.
-     */
-    public static function getInstance()
-    {
-        return null;
-    }
-}
diff --git a/tests/tine20/Tinebase/Pluggable/DummyFrontend.php b/tests/tine20/Tinebase/Pluggable/DummyFrontend.php
deleted file mode 100644 (file)
index b9b0980..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * 
- */
-/**
- * Mock for Frontend
- */
-class Tinebase_Pluggable_DummyFrontend extends Tinebase_Frontend_Abstract
-{
-}
\ No newline at end of file
diff --git a/tests/tine20/Tinebase/Pluggable/Plugin/DummyPlugin.php b/tests/tine20/Tinebase/Pluggable/Plugin/DummyPlugin.php
deleted file mode 100644 (file)
index cdede0d..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- * 
- */
-/**
- * Mock for Plugin
- */
-class Tinebase_Pluggable_Plugin_DummyPlugin
-{
-    public function dummyPluginMethod()
-    {
-        return 'dummyPluginReturn';
-    }
-}
index 5595b28..f4d79ae 100644 (file)
@@ -39,6 +39,7 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
      * @var array holds recordId's we create log entries for
      */
     protected $_recordIds = array();
+
     
     /**
      * Runs the test methods of this class.
@@ -342,4 +343,81 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $this->assertEquals(1, count($modlog));
         $this->assertEquals((string) $task->due, (string)($diff->diff['due']), 'new value mismatch: ' . print_r($modlog->toArray(), TRUE));
     }
+
+    public function testGetReplicationModificationsByInstanceSeq()
+    {
+        $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq(-1, 10000);
+        $instance_seq = $modifications->getLastRecord()->instance_seq;
+
+        /** @var Tinebase_Acl_Roles $roleController */
+        $roleController = Tinebase_Core::getApplicationInstance('Tinebase_Model_Role');
+        $this->assertEquals('Tinebase_Acl_Roles', get_class($roleController));
+
+        $role = new Tinebase_Model_Role(array('name' => 'unittest test role'));
+        $role = $roleController->create($role);
+
+        $roleController->addRoleMember($role->getId(), array(
+            'id' => Tinebase_Core::getUser()->getId(),
+            'type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
+        );
+        $roleController->addRoleMember($role->getId(), array(
+                'id' => 'test1',
+                'type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
+        );
+        $roleController->addRoleMember($role->getId(), array(
+                'id' => 'test2',
+                'type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER)
+        );
+        $roleController->removeRoleMember($role->getId(), array(
+                'id' => 'test2',
+                'type' => Tinebase_Acl_Rights::ACCOUNT_TYPE_USER
+        ));
+
+        $role = $roleController->get($role->getId());
+
+        $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq($instance_seq);
+        $roleModifications = $modifications->filter('record_type', 'Tinebase_Model_Role');
+        //$groupModifications = $modifications->filter('record_type', 'Tinebase_Model_Group');
+        //$userModifications = $modifications->filter('record_type', '/Tinebase_Model_User.*/', true);
+
+        // rollback
+        Tinebase_TransactionManager::getInstance()->rollBack();
+        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+
+        $notFound = false;
+        try {
+            $roleController->get($role->getId());
+        } catch(Tinebase_Exception_NotFound $tenf) {
+            $notFound = true;
+        }
+        $this->assertTrue($notFound, 'roll back did not work...');
+
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs($roleModifications);
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+
+        $newRole = $roleController->get($role->getId());
+
+        $diff = $role->diff($newRole, array('creation_time', 'created_by', 'last_modified_by', 'last_modified_time'));
+
+        $this->assertTrue($diff->isEmpty(), 'diff should be empty: ' . print_r($diff, true));
+
+        $mod = clone ($roleModifications->getByIndex(2));
+        $diff = new Tinebase_Record_Diff(json_decode($mod->new_value, true));
+        $rsDiff = new Tinebase_Record_RecordSetDiff($diff->diff['members']);
+        $modified = $rsDiff->added;
+        $rsDiff->added = array();
+        $modified[0]['account_id'] = 'test3';
+        $rsDiff->modified = $modified;
+        $diffArray = $diff->diff;
+        $diffArray['members'] = $rsDiff;
+        $diff->diff = $diffArray;
+        $mod->new_value = json_encode($diff->toArray());
+
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+
+        /** @var Tinebase_Model_Role $newRole */
+        $newRole = $roleController->get($role->getId());
+        $this->assertEquals(1, $newRole->members->filter('account_id', 'test3')->count(), 'record set diff modified didn\'t work, test3 not found');
+    }
 }
index 5ca0c13..497e315 100644 (file)
@@ -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) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  * @todo        try to split this into smaller parts (record proxy should support 'nested' json frontends first)
  * @todo        use functions from Tinebase_Frontend_Json_Abstract
@@ -873,8 +873,7 @@ class Admin_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     public function getRoles($query, $sort, $dir, $start, $limit)
     {
         $filter = new Tinebase_Model_RoleFilter(array(
-            'name'        => '%' . $query . '%',
-            'description' => '%' . $query . '%'
+            array('field' => 'query', 'operator' => 'contains', 'value' => $query),
         ));
         $paging = new Tinebase_Model_Pagination(array(
             'start' => $start,
index 9220a33..4f60c09 100644 (file)
@@ -26,8 +26,11 @@ class Admin_Setup_Initialize extends Setup_Initialize
     public static function createInitialRights(Tinebase_Model_Application $_application)
     {
         //do not call parent::createInitialRights(); because this app is for admins only
-        
+
         $roles = Tinebase_Acl_Roles::getInstance();
+        $oldNotesValue = $roles->useNotes(false);
+        $oldModLogValue = $roles->modlogActive(false);
+
         $adminRole = $roles->getRoleByName('admin role');
         $allRights = Tinebase_Application::getInstance()->getAllRights($_application->getId());
         foreach ( $allRights as $right ) {
@@ -36,6 +39,9 @@ class Admin_Setup_Initialize extends Setup_Initialize
                 $_application->getId(), 
                 $right
             );
-        }     
+        }
+
+        $roles->useNotes($oldNotesValue);
+        $roles->modlogActive($oldModLogValue);
     }
 }
index 9fe91a3..58397fb 100644 (file)
@@ -17,7 +17,7 @@
  * @package     Tinebase
  * @subpackage  Acl
  */
-class Tinebase_Acl_Roles
+class Tinebase_Acl_Roles extends Tinebase_Controller_Record_Abstract
 {
     /**
      * @var Zend_Db_Adapter_Abstract
@@ -57,6 +57,16 @@ class Tinebase_Acl_Roles
      */
     private function __construct() 
     {
+        $this->_applicationName = 'Tinebase';
+        $this->_modelName = 'Tinebase_Model_Role';
+        $this->_backend = new Tinebase_Backend_Sql(array(
+            'modelName' => 'Tinebase_Model_Role',
+            'tableName' => 'roles',
+        ), $this->_getDb());
+        //$this->_purgeRecords = TRUE;
+        //$this->_resolveCustomFields = FALSE;
+        $this->_updateMultipleValidateEachRecord = TRUE;
+        $this->_doContainerACLChecks = FALSE;
     }
     
     /**
@@ -224,25 +234,7 @@ class Tinebase_Acl_Roles
      */
     public function searchRoles($_filter, $_paging)
     {
-        $select = $_filter->getSelect();
-        
-        $_paging->appendPaginationSql($select);
-        
-        return new Tinebase_Record_RecordSet('Tinebase_Model_Role', $this->_getDb()->fetchAssoc($select));
-    }
-
-    /**
-     * Returns roles count
-     * 
-     * @param Tinebase_Model_RoleFilter $_filter
-     * @return int
-     */
-    public function searchCount($_filter)
-    {
-        $select = $_filter->getSelect();
-        
-        $roles = new Tinebase_Record_RecordSet('Tinebase_Model_Role', $this->_getDb()->fetchAssoc($select));
-        return count($roles);
+        return $this->search($_filter, $_paging);
     }
     
     /**
@@ -298,9 +290,7 @@ class Tinebase_Acl_Roles
      */
     public function createRole(Tinebase_Model_Role $role)
     {
-        Tinebase_Timemachine_ModificationLog::setRecordMetaData($role, 'create');
-        
-        $role = $this->_getRolesBackend()->create($role);
+        $role = $this->create($role);
         
         $this->resetClassCache();
         
@@ -315,9 +305,7 @@ class Tinebase_Acl_Roles
      */
     public function updateRole(Tinebase_Model_Role $role)
     {
-        Tinebase_Timemachine_ModificationLog::setRecordMetaData($role, 'update', $this->getRoleById($role->getId()));
-        
-        $role = $this->_getRolesBackend()->update($role);
+        $role = $this->update($role);
         
         $this->resetClassCache();
         
@@ -334,19 +322,7 @@ class Tinebase_Acl_Roles
     public function deleteRoles($ids)
     {
         try {
-            $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($this->_getDb());
-            
-            // delete role acls/members first
-            $where = array(
-                $this->_getDb()->quoteIdentifier('role_id') . ' in (?)' => (array) $ids
-            );
-            $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_accounts', $where);
-            $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_rights',   $where);
-            
-            // delete role
-            $this->_getRolesBackend()->delete($ids);
-            
-            Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
+            $this->delete($ids);
             
             $this->resetClassCache();
             
@@ -465,6 +441,9 @@ class Tinebase_Acl_Roles
         if ($roleId != $_roleId && $roleId > 0) {
             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
         }
+
+        /** @var Tinebase_Model_Role $oldRole */
+        $oldRole = $this->get($_roleId);
         
         // remove old members
         $where = array(
@@ -487,6 +466,8 @@ class Tinebase_Acl_Roles
             );
             $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_accounts', $data);
         }
+
+        $this->_writeModLogForRole($oldRole);
         
         $this->resetClassCache();
     }
@@ -556,6 +537,9 @@ class Tinebase_Acl_Roles
                 implode(', ', $validTypes) . ' (values given: ' . 
                 print_r($_account, true) . ')');
         }
+
+        /** @var Tinebase_Model_Role $oldRole */
+        $oldRole = $this->get($_roleId);
         
         $data = array(
             'role_id'       => $roleId,
@@ -565,6 +549,8 @@ class Tinebase_Acl_Roles
         
         try {
             $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_accounts', $data);
+
+            $this->_writeModLogForRole($oldRole);
         } catch (Zend_Db_Statement_Exception $e) {
             // account is already member of this group
         }
@@ -572,6 +558,16 @@ class Tinebase_Acl_Roles
         $this->resetClassCache();
     }
 
+    protected function _writeModLogForRole(Tinebase_Model_Role $_oldRole)
+    {
+        $seq = intval($_oldRole->seq);
+        $_oldRole->seq = $seq + 1;
+        $this->_getRolesBackend()->update($_oldRole);
+
+        $newRole = $this->get($_oldRole->getId());
+        $this->_writeModLog($newRole, $_oldRole);
+    }
+
     /**
      * remove one member from the role
      *
@@ -582,9 +578,12 @@ class Tinebase_Acl_Roles
     public function removeRoleMember($_roleId, $_account)
     {
         $roleId = (int)$_roleId;
-        if ($roleId != $_roleId && $roleId > 0) {
+        if ($roleId != $_roleId || $roleId < 1) {
             throw new Tinebase_Exception_InvalidArgument('$_roleId must be integer and greater than 0');
         }
+
+        /** @var Tinebase_Model_Role $oldRole */
+        $oldRole = $this->get($roleId);
         
         $where = array(
             $this->_getDb()->quoteIdentifier('role_id') . ' = ?'      => $roleId,
@@ -593,6 +592,8 @@ class Tinebase_Acl_Roles
         );
         
         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_accounts', $where);
+
+        $this->_writeModLogForRole($oldRole);
         
         $this->resetClassCache();
     }
@@ -694,6 +695,9 @@ class Tinebase_Acl_Roles
      */
     public function addRoleRight($roleId, $applicationId, $right)
     {
+        /** @var Tinebase_Model_Role $oldRole */
+        $oldRole = $this->get($roleId);
+
         $data = array(
             'role_id'        => $roleId,
             'application_id' => $applicationId,
@@ -701,6 +705,8 @@ class Tinebase_Acl_Roles
         );
         
         $this->_getDb()->insert(SQL_TABLE_PREFIX . 'role_rights', $data);
+
+        $this->_writeModLogForRole($oldRole);
         
         $this->resetClassCache();
     }
@@ -714,6 +720,9 @@ class Tinebase_Acl_Roles
      */
     public function deleteRoleRight($roleId, $applicationId, $right)
     {
+        /** @var Tinebase_Model_Role $oldRole */
+        $oldRole = $this->get($roleId);
+
         $where = array(
             $this->_getDb()->quoteIdentifier('role_id') . ' = ?'        => $roleId,
             $this->_getDb()->quoteIdentifier('application_id') . ' = ?' => $applicationId,
@@ -721,6 +730,8 @@ class Tinebase_Acl_Roles
         );
         
         $this->_getDb()->delete(SQL_TABLE_PREFIX . 'role_rights', $where);
+
+        $this->_writeModLogForRole($oldRole);
         
         $this->resetClassCache();
     }
@@ -803,6 +814,11 @@ class Tinebase_Acl_Roles
         $adminGroup         = $groupsBackend->getDefaultAdminGroup();
         $userGroup          = $groupsBackend->getDefaultGroup();
         $replicationGroup   = $groupsBackend->getDefaultReplicationGroup();
+
+        $oldOmitModLog = $this->_omitModLog;
+        $oldSetNotes = $this->_setNotes;
+        $this->_omitModLog = true;
+        $this->_setNotes = false;
         
         // add roles and add the groups to the roles
         $adminRole = new Tinebase_Model_Role(array(
@@ -841,7 +857,9 @@ class Tinebase_Acl_Roles
                 'type'  => Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP,
             )
         ));
-        
+
+        $this->_setNotes = $oldSetNotes;
+        $this->_omitModLog = $oldOmitModLog;
         $this->resetClassCache();
     }
     
@@ -875,4 +893,55 @@ class Tinebase_Acl_Roles
         
         return $this->_rolesBackend;
     }
+
+    /**
+     * inspect creation of one record (after create)
+     *
+     * @param   Tinebase_Record_Interface $_createdRecord
+     * @param   Tinebase_Record_Interface $_record
+     * @return  void
+     */
+    protected function _inspectAfterCreate($_createdRecord, Tinebase_Record_Interface $_record)
+    {
+        $config = $_record::getConfiguration()->recordsFields;
+        foreach (array_keys($config) as $property) {
+            $this->_createDependentRecords($_createdRecord, $_record, $property, $config[$property]['config']);
+        }
+    }
+
+    /**
+     * 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)
+    {
+        $config = $_record::getConfiguration()->recordsFields;
+
+        foreach (array_keys($config) as $p) {
+            $this->_updateDependentRecords($_record, $_oldRecord, $p, $config[$p]['config']);
+        }
+    }
+
+    /**
+     * get by id
+     *
+     * @param string $_id
+     * @param int $_containerId
+     * @param bool         $_getRelatedData
+     * @param bool $_getDeleted
+     * @return Tinebase_Record_Interface
+     * @throws Tinebase_Exception_AccessDenied
+     */
+    public function get($_id, $_containerId = NULL, $_getRelatedData = TRUE, $_getDeleted = FALSE)
+    {
+        $result = parent::get($_id, $_containerId, $_getRelatedData, $_getDeleted);
+        $modelName = $this->_modelName;
+        $modelConf = $modelName::getConfiguration();
+        $rs = new Tinebase_Record_RecordSet($this->_modelName, array($result));
+        Tinebase_ModelConfiguration::resolveRecordsPropertiesForRecordSet($rs, $modelConf);
+        return $rs->getFirstRecord();
+    }
 }
index c7a162f..820d048 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Backend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schuele <p.schuele@metaways.de>
  * 
  */
@@ -16,7 +16,7 @@
  * @package     Tinebase
  * @subpackage  Backend
  */
-abstract class Tinebase_Backend_Abstract extends Tinebase_Pluggable_Abstract implements Tinebase_Backend_Interface
+abstract class Tinebase_Backend_Abstract implements Tinebase_Backend_Interface
 {
     /**
      * backend type constant
index f09b89a..375bc4d 100644 (file)
@@ -954,9 +954,9 @@ abstract class Tinebase_Backend_Sql_Abstract extends Tinebase_Backend_Abstract i
             
             $recordArray = $this->_recordToRawData($_record);
             
-            // unset id if autoincrement & still empty
-            if ($this->_hasAutoIncrementId() || $_record->$identifier == 'NULL' ) {
-                unset($recordArray['id']);
+            // unset id if present and empty
+            if (isset($recordArray[$identifier]) && empty($recordArray[$identifier])) {
+                unset($recordArray[$identifier]);
             }
             
             $recordArray = array_intersect_key($recordArray, $this->getSchema());
index 9625df2..2132842 100644 (file)
@@ -448,9 +448,29 @@ class Tinebase_Config extends Tinebase_Config_Abstract
     /**
      * @var string
      */
+    const REPLICATION_SLAVE = 'replicationSlave';
+
+    /**
+     * @var string
+     */
     const REPLICATION_USER_PASSWORD = 'replicationUserPassword';
 
     /**
+     * @var string
+     */
+    const MASTER_URL = 'masterURL';
+
+    /**
+     * @var string
+     */
+    const MASTER_USERNAME = 'masterUsername';
+
+    /**
+     * @var string
+     */
+    const MASTER_PASSWORD = 'masterPassword';
+
+    /**
      * (non-PHPdoc)
      * @see tine20/Tinebase/Config/Definition::$_properties
      */
@@ -621,6 +641,28 @@ class Tinebase_Config extends Tinebase_Config_Abstract
                 )
             ),
         ),
+        self::REPLICATION_SLAVE => array(
+            //_('Replication slave configuration')
+            'label'                 => 'Replication slave configuration',
+            //_('Replication slave configuration.')
+            'description'           => 'Replication slave configuration.',
+            'type'                  => 'object',
+            'class'                 => 'Tinebase_Config_Struct',
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => FALSE,
+            'setBySetupModule'      => TRUE,
+            'content'               => array(
+                self::MASTER_URL            => array(
+                    'type'                      => Tinebase_Config::TYPE_STRING,
+                ),
+                self::MASTER_USERNAME       => array(
+                    'type'                      => Tinebase_Config::TYPE_STRING,
+                ),
+                self::MASTER_PASSWORD       => array(
+                    'type'                      => Tinebase_Config::TYPE_STRING,
+                )
+            )
+        ),
         self::USERBACKEND => array(
                                    //_('User Configuration')
             'label'                 => 'User Configuration',
index 053f733..152474e 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Controller
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schuele <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  */
 
@@ -16,7 +16,7 @@
  * @package     Tinebase
  * @subpackage  Controller
  */
-abstract class Tinebase_Controller_Abstract extends Tinebase_Pluggable_Abstract implements Tinebase_Controller_Interface
+abstract class Tinebase_Controller_Abstract implements Tinebase_Controller_Interface
 {
     /**
      * default settings
index c2e8366..0465f46 100644 (file)
@@ -2213,76 +2213,61 @@ abstract class Tinebase_Controller_Record_Abstract
         
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
             . ' ' . print_r($_record->{$_property}, TRUE));
-        
-        if (! empty($_record->{$_property}) && $_record->{$_property}) {
-            
-            // legacy - should be already done in frontend json - remove if all record properties are record sets before getting to controller
-            if (is_array($_record->{$_property})) {
-                $rs = new Tinebase_Record_RecordSet($recordClassName);
-                foreach ($_record->{$_property} as $recordArray) {
-                    /** @var Tinebase_Record_Interface $rec */
-                    $rec = new $recordClassName(array(),true);
-                    $rec->setFromJsonInUsersTimezone($recordArray);
-                    $rs->addRecord($rec);
-                }
-                $_record->{$_property} = $rs;
+
+        // legacy - should be already done in frontend json - remove if all record properties are record sets before getting to controller
+        if (is_array($_record->{$_property})) {
+            $rs = new Tinebase_Record_RecordSet($recordClassName);
+            foreach ($_record->{$_property} as $recordArray) {
+                /** @var Tinebase_Record_Interface $rec */
+                $rec = new $recordClassName(array(),true);
+                $rec->setFromJsonInUsersTimezone($recordArray);
+                $rs->addRecord($rec);
             }
-            
+            $_record->{$_property} = $rs;
+        }
+        
+        if (! empty($_record->{$_property}) && $_record->{$_property} && $_record->{$_property}->count() > 0) {
+
             $idProperty = $_record->{$_property}->getFirstRecord()->getIdProperty();
             
             // legacy end
             $oldFilter = new $filterClassName(array(array('field' => $idProperty, 'operator' => 'in', 'value' => $_record->{$_property}->getId())));
             $oldRecords = $controller->search($oldFilter);
-            
+
+            /** @var Tinebase_Record_Abstract $record */
             foreach ($_record->{$_property} as $record) {
                 
                 $record->{$_fieldConfig['refIdField']} = $_record->getId();
-                
-                // update record if ID exists and has a length of 40 (it has a length of 10 if it is a timestamp)
-                if ($record->getId() && strlen($record->getId()) == 40) {
-                    
-                    // do not try to update if the record hasn't changed
-                    $oldRecord = $oldRecords->getById($record->getId());
-                    
-                    if ($oldRecord && ! empty($oldRecord->diff($record)->diff)) {
-                        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
-                            Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__. ' Updating dependent record with id = "' . $record->getId() . '" on property ' . $_property . ' for ' . $this->_applicationName . ' ' . $this->_modelName);
-                        }
-                        $existing->addRecord($controller->update($record));
-                    } else {
-                        $existing->addRecord($record);
-                    }
-                    // create if is not existing already
-                } else {
-                    // try to find if it already exists (with corrupted id)
-                    if ($record->getId() == NULL) {
-                        $crc = $controller->create($record);
-                        $existing->addRecord($crc);
-                        
-                        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
-                            Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__. ' Creating dependent record with id = "' . $crc->getId() . '" on property ' . $_property . ' for ' . $this->_applicationName . ' ' . $this->_modelName);
-                        }
-                    } else {
-                        
-                        try {
-                            
-                            $prevRecord = $controller->get($record->getId());
-    
-                            if (! empty($prevRecord->diff($record)->diff)) {
-                                $existing->addRecord($controller->update($record));
-                            } else {
-                                $existing->addRecord($record);
-                            }
-                            
-                        } catch (Tinebase_Exception_NotFound $e) {
-                            $record->id = NULL;
-                            $crc = $controller->create($record);
-                            $existing->addRecord($crc);
-                            
+
+                $create = false;
+                if (!empty($record->getId())) {
+                    try {
+
+                        $prevRecord = $controller->get($record->getId());
+
+                        if (!empty($prevRecord->diff($record)->diff)) {
                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
-                                Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__. ' Creating dependent record with id = "' . $crc->getId() . '" on property ' . $_property . ' for ' . $this->_applicationName . ' ' . $this->_modelName);
+                                Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Updating dependent record with id = "' . $record->getId() . '" on property ' . $_property . ' for ' . $this->_applicationName . ' ' . $this->_modelName);
                             }
+                            $existing->addRecord($controller->update($record));
+                        } else {
+                            $existing->addRecord($record);
                         }
+
+                    } catch (Tinebase_Exception_NotFound $e) {
+                        $create = true;
+                    }
+                } else {
+                    $create = true;
+                    $record->{$record->getIdProperty()} = NULL;
+                }
+
+                if (true === $create) {
+                    $crc = $controller->create($record);
+                    $existing->addRecord($crc);
+
+                    if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
+                        Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating dependent record with id = "' . $crc->getId() . '" on property ' . $_property . ' for ' . $this->_applicationName . ' ' . $this->_modelName);
                     }
                 }
             }
index 038640c..24a4aa6 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Application
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Cornelius Weiss <c.weiss@metaways.de>
  */
 
@@ -15,7 +15,7 @@
  * @package     Tinebase
  * @subpackage  Application
  */
-abstract class Tinebase_Frontend_Abstract extends Tinebase_Pluggable_Abstract implements Tinebase_Frontend_Interface
+abstract class Tinebase_Frontend_Abstract implements Tinebase_Frontend_Interface
 {
     /**
      * Application name
index bb1233b..96626d4 100644 (file)
@@ -33,6 +33,19 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
     protected $_applicationsToWorkOn = array();
 
     /**
+     * @param Zend_Console_Getopt $_opts
+     * @return boolean success
+     */
+    public function readModifictionLogFromMaster($opts)
+    {
+        if (!$this->_checkAdminRight()) {
+            return -1;
+        }
+
+        Tinebase_Timemachine_ModificationLog::getInstance()->readModificationLogFromMaster();
+    }
+
+    /**
     * @param Zend_Console_Getopt $_opts
     * @return boolean success
     */
index 053e93b..e87382a 100644 (file)
@@ -188,8 +188,5 @@ abstract class Tinebase_Frontend_Http_Abstract extends Tinebase_Frontend_Abstrac
                     break;
             }
         }
-
-        // call plugin method (see Tinebase_Pluggable_Abstract)
-        return parent::__call($method, $args);
     }
 }
index 926dbe9..c6a9bae 100644 (file)
@@ -640,8 +640,5 @@ abstract class Tinebase_Frontend_Json_Abstract extends Tinebase_Frontend_Abstrac
                     break;
             }
         }
-
-        // call plugin method (see Tinebase_Pluggable_Abstract)
-        return parent::__call($method, $args);
     }
 }
index cc9a198..c73bc20 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Cornelius Weiss <c.weiss@metaways.de>
  */
 
@@ -344,7 +344,7 @@ class Tinebase_Helper
         if (is_array($jsonOrArray)) {
             return $jsonOrArray;
         } else if (empty($jsonOrArray) || trim($jsonOrArray) == '') {
-            return Zend_Json::decode("{}");
+            return array();
         } else {
             try {
                 return Zend_Json::decode($jsonOrArray);
@@ -352,7 +352,7 @@ class Tinebase_Helper
                 if (Tinebase_Core::isLogLevel(Zend_Log::ERR)) Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
                     . ' Could not json decode: ' . var_export($jsonOrArray, true) . ' - assuming empty array.');
                 Tinebase_Exception::log($zje, false);
-                return Zend_Json::decode("{}");
+                return array();
             }
         }
     }
index 08b028c..bde69d2 100644 (file)
  * @property    string  $version
  * @property    array   $tables
  * @property    string  $order
+ * @property    array   $state
  */
 class Tinebase_Model_Application extends Tinebase_Record_Abstract
 {
     /**
-     * key in $_validators/$_properties array for the filed which 
-     * represents the identifier
-     * 
-     * @var string
-     */    
-    protected $_identifier = 'id';
-    
-    /**
-     * application the record belongs to
-     *
      * @var string
      */
-    protected $_application = 'Tinebase';
-    
+    const STATE_REPLICATION_MASTER_ID = 'replicationMasterId';
+
     /**
-     * list of zend inputfilter
-     * 
-     * this filter get used when validating user generated content with Zend_Input_Filter
+     * holds the configuration object (must be declared in the concrete class)
      *
-     * @var array
+     * @var Tinebase_ModelConfiguration
      */
-    protected $_filters = array(
-        'name'      => 'StringTrim',
-        'version'   => 'StringTrim'
-    );
-    
+    protected static $_configurationObject = NULL;
+
     /**
-     * list of zend validator
-     * 
-     * this validators get used when validating user generated content with Zend_Input_Filter
+     * Holds the model configuration (must be assigned in the concrete class)
      *
      * @var array
      */
-    protected $_validators = array();
+    protected static $_modelConfiguration = array(
+        'recordName'        => 'RoleMember',
+        'recordsName'       => 'RoleMembers', // ngettext('RoleMember', 'RoleMembers', n)
+        'hasRelations'      => FALSE,
+        'hasCustomFields'   => FALSE,
+        'hasNotes'          => FALSE,
+        'hasTags'           => FALSE,
+        'modlogActive'      => TRUE,
+        'hasAttachments'    => FALSE,
+        'createModule'      => FALSE,
 
-    /**
-     * @see Tinebase_Record_Abstract
-     */
-    public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
-    {
-        $this->_validators = array(
-            'id'        => array('allowEmpty' => true),
-            'name'      => array('presence' => 'required'),
-            'status'    => array(array('InArray', array('enabled', 'disabled'))),
-            'order'     => array('Digits', 'presence' => 'required'),
-            'tables'    => array('allowEmpty' => true),
-            'version'   => array('presence' => 'required')
-        );
-        
-        return parent::__construct($_data, $_bypassFilters, $_convertDates);
-    }
+        'titleProperty'     => 'name',
+        'appName'           => 'Tinebase',
+        'modelName'         => 'RoleMember',
+
+        'fields' => array(
+            'name'              => array(
+                'label'             => 'Name', //_('Name')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+                'inputFilters'      => array('Zend_Filter_StringTrim' => NULL),
+            ),
+            'version'           => array(
+                'label'             => 'Version', //_('Version')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+                'inputFilters'      => array('Zend_Filter_StringTrim' => NULL),
+            ),
+            'status'            => array(
+                'label'             => 'Status', //_('Status')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(array('InArray', array('enabled', 'disabled'))),
+            ),
+            'order'             => array(
+                'label'             => 'Order', //_('Order')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array('Digits', 'presence' => 'required'),
+            ),
+            'tables'            => array(
+                'label'             => 'Tables', //_('Tables')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => true)
+            ),
+            'state'             => array(
+                'label'             => 'State', //_('State')
+                'type'              => 'json',
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => true)
+            ),
+        )
+    );
     
     /**
      * converts a int, string or Tinebase_Model_Application to an accountid
index a3f07d3..dc2a3d5 100644 (file)
@@ -471,7 +471,6 @@ class Tinebase_Model_Filter_FilterGroup implements Iterator
      * @param  string|array $_fieldOrData
      * @param  string $_operator
      * @param  mixed  $_value
-     * @param  array  $_additionalData
      * @return Tinebase_Model_Filter_Abstract
      * 
      * @todo remove legacy code + obsolete params sometimes
index fa9a164..b8d29e8 100644 (file)
@@ -5,10 +5,8 @@
  * @package     Tinebase
  * @subpackage  Acl
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schuele <p.schuele@metaways.de>
- * 
- * @todo        add role members and rights
  */
 
 /**
  * 
  * @package     Tinebase
  * @subpackage  Acl
+ *
+ * @property Tinebase_Record_RecordSet  $members
  *  */
 class Tinebase_Model_Role extends Tinebase_Record_Abstract
 {
     /**
-     * key in $_validators/$_properties array for the filed which 
-     * represents the identifier
-     * 
-     * @var string
-     */    
-    protected $_identifier = 'id';
-    
-    /**
-     * application the record belongs to
+     * holds the configuration object (must be declared in the concrete class)
      *
-     * @var string
+     * @var Tinebase_ModelConfiguration
      */
-    protected $_application = 'Tinebase';
-    
-    /**
-     * list of zend inputfilter
-     * 
-     * this filter get used when validating user generated content with Zend_Filter_Input
-     *
-     * @var array
-     */
-    protected $_filters = array(
-        'name'      => 'StringTrim'
-    );
+    protected static $_configurationObject = NULL;
 
     /**
-     * list of zend validator
-     * 
-     * this validators get used when validating user generated content with Zend_Filter_Input
+     * Holds the model configuration (must be assigned in the concrete class)
      *
      * @var array
      */
-    protected $_validators = array(
-            'id'                    => array('allowEmpty' => true),
-            'name'                  => array('presence' => 'required'),
-            'description'           => array('allowEmpty' => true),
-            'created_by'            => array('allowEmpty' => true),
-            'creation_time'         => array('allowEmpty' => true),
-            'last_modified_by'      => array('allowEmpty' => true),
-            'last_modified_time'    => array('allowEmpty' => true),
-    );
-        
-    /**
-     * @var array
-     */
-    protected $_datetimeFields = array(
-        'creation_time',
-        'last_modified_time',
+    protected static $_modelConfiguration = array(
+        'recordName'        => 'Role',
+        'recordsName'       => 'Roles', // ngettext('Role', 'Roles', n)
+        'hasRelations'      => FALSE,
+        'hasCustomFields'   => FALSE,
+        'hasNotes'          => FALSE,
+        'hasTags'           => FALSE,
+        'modlogActive'      => TRUE,
+        'hasAttachments'    => FALSE,
+        'createModule'      => FALSE,
+
+        'titleProperty'     => 'name',
+        'appName'           => 'Tinebase',
+        'modelName'         => 'Role',
+
+        'fields' => array(
+            'name'              => array(
+                'label'             => 'Name', //_('Name')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+                'inputFilters'      => array('Zend_Filter_StringTrim' => NULL),
+            ),
+            'description'       => array(
+                'label'             => 'Description', //_('Description')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => TRUE),
+            ),
+            'rights'            => array(
+                'label'             => 'Rights', // _('Rights')
+                'type'              => 'records', // be careful: records type has no automatic filter definition!
+                'config'            => array(
+                    'appName'               => 'Tinebase',
+                    'modelName'             => 'RoleRight',
+                    'controllerClassName'   => 'Tinebase_RoleRight',
+                    'refIdField'            => 'role_id',
+                    'dependentRecords'      => TRUE,
+                ),
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => TRUE, Zend_Filter_Input::DEFAULT_VALUE => NULL),
+            ),
+            'members'           => array(
+                'label'             => 'Members', // _('Members')
+                'type'              => 'records', // be careful: records type has no automatic filter definition!
+                'config'            => array(
+                    'appName'               => 'Tinebase',
+                    'modelName'             => 'RoleMember',
+                    'controllerClassName'   => 'Tinebase_RoleMember',
+                    'refIdField'            => 'role_id',
+                    'dependentRecords'      => TRUE,
+                ),
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => TRUE, Zend_Filter_Input::DEFAULT_VALUE => NULL),
+            ),
+        )
     );
     
     /**
@@ -79,5 +96,14 @@ class Tinebase_Model_Role extends Tinebase_Record_Abstract
     {
         return $this->name;
     }
-    
+
+    /**
+     * returns true if this record should be replicated
+     *
+     * @return boolean
+     */
+    public function isReplicable()
+    {
+        return true;
+    }
 }
index 20607e6..fa83ab7 100644 (file)
@@ -6,10 +6,7 @@
  * @subpackage  Acl
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schuele <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
- *
- * @todo        add role members and rights
- * @todo        extend Tinebase_Model_Filter_FilterGroup
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
  * @subpackage  Acl
  * 
  */
-class Tinebase_Model_RoleFilter extends Tinebase_Record_Abstract
+class Tinebase_Model_RoleFilter extends Tinebase_Model_Filter_FilterGroup
 {
     /**
-     * key in $_validators/$_properties array for the field which 
-     * represents the identifier
-     * 
-     * @var string
-     */    
-    protected $_identifier = 'id';
-    
-    /**
-     * application the record belongs to
-     *
-     * @var string
-     * 
-     * @todo    is this needed?
+     * @var string application of this filter group
      */
-    protected $_application = 'Tinebase';
-    
+    protected $_applicationName = 'Tinebase';
+
     /**
-     * filter validators
-     *
-     * @var array
+     * @var string name of model this filter group is designed for
      */
-    protected $_validators = array(
-        'id'                   => array('allowEmpty' => true,  'Alnum'),
+    protected $_modelName = 'Tinebase_Model_Role';
 
-        //'name'                 => array('presence'   => 'required'),
-        'name'                 => array('allowEmpty' => true),
-        'description'          => array('allowEmpty' => true),
-    );
-    
     /**
-     * Returns a select object according to this filter
-     * 
-     * @return Zend_Db_Select
+     * @var array filter model fieldName => definition
      */
-    public function getSelect()
-    {
-        $db = Tinebase_Core::getDb();
-        $select = $db->select()
-            ->from(array('roles' => SQL_TABLE_PREFIX . 'roles'));
-        
-        $orWhere = array();
-        if (!empty($this->name)) {
-            $orWhere[] = $db->quoteInto($db->quoteIdentifier('roles.name') . ' LIKE ?', $this->name);
-        }
-        if (!empty($this->description)) {
-            $orWhere[] = $db->quoteInto($db->quoteIdentifier('roles.description') . ' LIKE ?', $this->description);
-        }
-        if (! empty($orWhere)) {
-            $select->where(implode(' OR ', $orWhere));
-        }
-        return $select;
-    }
-    
+    protected $_filterModel = array(
+        'id'                    => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'query'                 => array(
+            'filter' => 'Tinebase_Model_Filter_Query',
+            'options' => array('fields' => array('name', 'description'))
+        ),
+        'name'                  => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'description'           => array('filter' => 'Tinebase_Model_Filter_Text'),
+    );
 }
diff --git a/tine20/Tinebase/Model/RoleMember.php b/tine20/Tinebase/Model/RoleMember.php
new file mode 100644 (file)
index 0000000..97c230c
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * model to handle role members
+ *
+ * @package     Tinebase
+ * @subpackage  Acl
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ */
+
+/**
+ * defines the data type for role members
+ *
+ * @package     Tinebase
+ * @subpackage  Acl
+ *  */
+class Tinebase_Model_RoleMember extends Tinebase_Record_Abstract
+{
+    /**
+     * holds the configuration object (must be declared in the concrete class)
+     *
+     * @var Tinebase_ModelConfiguration
+     */
+    protected static $_configurationObject = NULL;
+
+    /**
+     * Holds the model configuration (must be assigned in the concrete class)
+     *
+     * @var array
+     */
+    protected static $_modelConfiguration = array(
+        'recordName'        => 'RoleMember',
+        'recordsName'       => 'RoleMembers', // ngettext('RoleMember', 'RoleMembers', n)
+        'hasRelations'      => FALSE,
+        'hasCustomFields'   => FALSE,
+        'hasNotes'          => FALSE,
+        'hasTags'           => FALSE,
+        'modlogActive'      => TRUE,
+        'hasAttachments'    => FALSE,
+        'createModule'      => FALSE,
+
+        'titleProperty'     => 'id',
+        'appName'           => 'Tinebase',
+        'modelName'         => 'RoleMember',
+
+        'fields' => array(
+            'role_id'           => array(
+                'label'             => 'role_id', //_('role_id')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+            ),
+            'account_type'      => array(
+                'label'             => 'account_type', //_('account_type')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+            ),
+            'account_id'        => array(
+                'label'             => 'account_id', //_('account_id')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+            ),
+        )
+    );
+}
diff --git a/tine20/Tinebase/Model/RoleMemberFilter.php b/tine20/Tinebase/Model/RoleMemberFilter.php
new file mode 100644 (file)
index 0000000..2a57f46
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Acl
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Role Filter Class
+ * @package     Tinebase
+ * @subpackage  Acl
+ *
+ */
+class Tinebase_Model_RoleMemberFilter extends Tinebase_Model_Filter_FilterGroup
+{
+    /**
+     * @var string application of this filter group
+     */
+    protected $_applicationName = 'Tinebase';
+
+    /**
+     * @var string name of model this filter group is designed for
+     */
+    protected $_modelName = 'Tinebase_Model_RoleMember';
+
+    /**
+     * @var array filter model fieldName => definition
+     */
+    protected $_filterModel = array(
+        'id'                    => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'role_id'               => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'account_type'          => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'account_id'            => array('filter' => 'Tinebase_Model_Filter_Id'),
+    );
+}
index 0e3d0d5..df3f10a 100644 (file)
@@ -1,16 +1,16 @@
 <?php
 /**
- * model to handle rights
+ * model to handle role rights
  * 
  * @package     Tinebase
  * @subpackage  Acl
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
 /**
- * defines the datatype for rights
+ * defines the datatype for role rights
  * 
  * @package     Tinebase
  * @subpackage  Acl
 class Tinebase_Model_RoleRight extends Tinebase_Record_Abstract
 {
     /**
-     * key in $_validators/$_properties array for the filed which 
-     * represents the identifier
-     * 
-     * @var string
-     */    
-    protected $_identifier = 'id';
-    
-    /**
-     * application the record belongs to
-     *
-     * @var string
-     */
-    protected $_application = 'Tinebase';
-    
-    /**
-     * list of zend inputfilter
-     * 
-     * this filter get used when validating user generated content with Zend_Filter_Input
+     * holds the configuration object (must be declared in the concrete class)
      *
-     * @var array
+     * @var Tinebase_ModelConfiguration
      */
-    protected $_filters = array(
-        //'*'      => 'StringTrim'
-    );
+    protected static $_configurationObject = NULL;
 
     /**
-     * list of zend validator
-     * 
-     * this validators get used when validating user generated content with Zend_Filter_Input
+     * Holds the model configuration (must be assigned in the concrete class)
      *
      * @var array
      */
-    protected $_validators = array();
+    protected static $_modelConfiguration = array(
+        'recordName'        => 'RoleRight',
+        'recordsName'       => 'RoleRights', // ngettext('RoleRight', 'RoleRights', n)
+        'hasRelations'      => FALSE,
+        'hasCustomFields'   => FALSE,
+        'hasNotes'          => FALSE,
+        'hasTags'           => FALSE,
+        'modlogActive'      => TRUE,
+        'hasAttachments'    => FALSE,
+        'createModule'      => FALSE,
 
-    /**
-     * @see Tinebase_Record_Abstract
-     */
-    public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = NULL)
-    {
-        $this->_validators = array(
-            'id'                => array('allowEmpty' => TRUE),
-            'application_id'    => array('Alnum', 'presence' => 'required'),
-            'account_id'        => array('presence' => 'required', 'allowEmpty' => TRUE, 'default' => '0'),
-            'account_type'      => array(
-                new Zend_Validate_InArray(array(Tinebase_Acl_Rights::ACCOUNT_TYPE_USER, Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP, Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE)) 
+        'titleProperty'     => 'id',
+        'appName'           => 'Tinebase',
+        'modelName'         => 'RoleRight',
+
+        'fields' => array(
+            'role_id'           => array(
+                'label'             => 'Name', //_('Name')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
             ),
-            'right'             => array('presence' => 'required'),
-        );
-        
-        return parent::__construct($_data, $_bypassFilters);
-    }
+            'application_id'    => array(
+                'label'             => 'Name', //_('Name')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+            ),
+            'right'             => array(
+                'label'             => 'Name', //_('Name')
+                'type'              => 'string',
+                'queryFilter'       => TRUE,
+                'validators'        => array(Zend_Filter_Input::ALLOW_EMPTY => false, 'presence' => 'required'),
+            ),
+        )
+    );
 }
diff --git a/tine20/Tinebase/Model/RoleRightFilter.php b/tine20/Tinebase/Model/RoleRightFilter.php
new file mode 100644 (file)
index 0000000..9a60359
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Tinebase
+ * @subpackage  Acl
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Role Filter Class
+ * @package     Tinebase
+ * @subpackage  Acl
+ *
+ */
+class Tinebase_Model_RoleRightFilter extends Tinebase_Model_Filter_FilterGroup
+{
+    /**
+     * @var string application of this filter group
+     */
+    protected $_applicationName = 'Tinebase';
+
+    /**
+     * @var string name of model this filter group is designed for
+     */
+    protected $_modelName = 'Tinebase_Model_RoleRight';
+
+    /**
+     * @var array filter model fieldName => definition
+     */
+    protected $_filterModel = array(
+        'id'                    => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'role_id'               => array('filter' => 'Tinebase_Model_Filter_Id'),
+    );
+}
index a9bc450..5b9711c 100644 (file)
@@ -1427,4 +1427,94 @@ class Tinebase_ModelConfiguration {
         }
         return $this->{'_' . $_property};
     }
+
+    public static function resolveRecordsPropertiesForRecordSet(Tinebase_Record_RecordSet $_records, $modelConfiguration, $isSearch = false)
+    {
+        if (0 === $_records->count() || ! ($resolveFields = $modelConfiguration->recordsFields)) {
+            return;
+        }
+
+        $ownIds = $_records->{$modelConfiguration->idProperty};
+
+        // iterate fields to resolve
+        foreach ($resolveFields as $fieldKey => $c) {
+            $config = $c['config'];
+
+            // resolve records, if omitOnSearch is definitively set to FALSE (by default they won't be resolved on search)
+            if ($isSearch && !(isset($config['omitOnSearch']) && $config['omitOnSearch'] === FALSE)) {
+                continue;
+            }
+
+            if (! isset($config['controllerClassName'])) {
+                throw new Tinebase_Exception_UnexpectedValue('Controller class name needed');
+            }
+
+            // fetch the fields by the refIfField
+            /** @var Tinebase_Controller_Record_Interface|Tinebase_Controller_SearchInterface $controller */
+            /** @noinspection PhpUndefinedMethodInspection */
+            $controller = $config['controllerClassName']::getInstance();
+            $filterName = $config['filterClassName'];
+
+            $filterArray = array();
+
+            // addFilters can be added and must be added if the same model resides in more than one records fields
+            if (isset($config['addFilters']) && is_array($config['addFilters'])) {
+                $filterArray = $config['addFilters'];
+            }
+
+            /** @var Tinebase_Model_Filter_FilterGroup $filter */
+            $filter = new $filterName($filterArray);
+            $filter->addFilter(new Tinebase_Model_Filter_Id(array('field' => $config['refIdField'], 'operator' => 'in', 'value' => $ownIds)));
+
+            $paging = NULL;
+            if (isset($config['paging']) && is_array($config['paging'])) {
+                $paging = new Tinebase_Model_Pagination($config['paging']);
+            }
+
+            $foreignRecords = $controller->search($filter, $paging);
+            /** @var Tinebase_Record_Interface $foreignRecordClass */
+            $foreignRecordClass = $foreignRecords->getRecordClassName();
+            $foreignRecordModelConfiguration = $foreignRecordClass::getConfiguration();
+
+            $foreignRecords->setTimezone(Tinebase_Core::getUserTimezone());
+            $foreignRecords->convertDates = true;
+
+            if ($foreignRecords->count() > 0) {
+
+                // @todo: resolve alarms?
+                // @todo: use parts parameter?
+                if ($foreignRecordModelConfiguration->resolveRelated) {
+                    $fr = $foreignRecords->getFirstRecord();
+                    if ($fr->has('notes')) {
+                        Tinebase_Notes::getInstance()->getMultipleNotesOfRecords($foreignRecords);
+                    }
+                    if ($fr->has('tags')) {
+                        Tinebase_Tags::getInstance()->getMultipleTagsOfRecords($foreignRecords);
+                    }
+                    if ($fr->has('relations')) {
+                        $relations = Tinebase_Relations::getInstance()->getMultipleRelations($foreignRecordClass, 'Sql', $foreignRecords->{$fr->getIdProperty()} );
+                        $foreignRecords->setByIndices('relations', $relations);
+                    }
+                    if ($fr->has('customfields')) {
+                        Tinebase_CustomField::getInstance()->resolveMultipleCustomfields($foreignRecords);
+                    }
+                    if ($fr->has('attachments') && Tinebase_Core::isFilesystemAvailable()) {
+                        Tinebase_FileSystem_RecordAttachments::getInstance()->getMultipleAttachmentsOfRecords($foreignRecords);
+                    }
+                }
+
+                /** @var Tinebase_Record_Interface $record */
+                foreach ($_records as $record) {
+                    $filtered = $foreignRecords->filter($config['refIdField'], $record->getId());
+                    $record->{$fieldKey} = $filtered;
+                }
+            }
+
+            foreach ($_records as $record) {
+                if (null === $record->{$fieldKey}) {
+                    $record->{$fieldKey} = new Tinebase_Record_RecordSet($foreignRecordClass);
+                }
+            }
+        }
+    }
 }
diff --git a/tine20/Tinebase/Pluggable/Abstract.php b/tine20/Tinebase/Pluggable/Abstract.php
deleted file mode 100644 (file)
index 16fa6db..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * Tine 2.0
- *
- * @package     Tinebase
- * @subpackage  Pluggable
- * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
- * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
- * @author      Flávio Gomes da Silva Lisboa <flavio.lisboa@serpro.gov.br>
- *
- */
-
-/**
- * Abstract class for allowing dependency injection through a plugin
- * architecture
- * Use the file init_plugins.php for registering plugins for a class
- *
- * @package Tinebase
- * @subpackage Pluggable
- */
-abstract class Tinebase_Pluggable_Abstract
-{
-
-    /**
-     * Plugins for this class family
-     * Contains:
-     * '[method]' => '[Complete_Name_Of_Class]'
-     * 
-     * @var array
-     */
-    protected static $_plugins = array();
-
-    /**
-     * Attaches a plugin
-     * Path for classes must be into include_path
-     * 
-     * @param string $method
-     * @param string $namespace
-     */
-    public static function attachPlugin($method, $namespace)
-    {
-        static::$_plugins[$method] = $namespace;
-        Zend_Loader_Autoloader::getInstance()->registerNamespace(
-            current(explode('_', $namespace)));
-    }
-
-    /**
-     * Attaches several plugins from a same path
-     * $methods must contain:
-     * '[method]' => '[Complete_Name_Of_Class]'
-     *
-     * @param array $methods
-     * @param string $namespace
-     */
-    public static function attachPlugins(array $methods, $namespace)
-    {
-        foreach ($methods as $method => $class) {
-            static::$_plugins[$method] = $class;
-        }
-        Zend_Loader_Autoloader::getInstance()->registerNamespace(
-            current(explode('_', $namespace)));
-    }
-
-    /**
-     * Dependency injection
-     * Calling of plugins
-     * 
-     * @param string $method            
-     * @param array $args            
-     */
-    public function __call($method, array $args)
-    {
-        if (isset(static::$_plugins[$method])) {
-            $class = static::$_plugins[$method];
-            $plugin = new $class();
-            return call_user_func_array(array(
-                $plugin,
-                $method
-            ), $args);
-        } else {
-            throw new Tinebase_Exception(
-                'Plugin ' . $method . ' was not found in haystack of class ' . get_class($this));
-        }
-    }
-}
index 3b06332..dc23d39 100644 (file)
@@ -1302,20 +1302,54 @@ abstract class Tinebase_Record_Abstract implements Tinebase_Record_Interface
     public function undo(Tinebase_Record_Diff $diff)
     {
         /* TODO special treatment? for what? how?
-         * TODO FIX IT!
+         * oldData does not contain RecordSetDiffs. It plainly contains the old data present in the property before it was changed.
          */
 
         if ($this->has('is_deleted')) {
             $this->is_deleted = 0;
         }
 
-        foreach($diff->oldData as $property => $oldValue)
+        foreach((array)($diff->oldData) as $property => $oldValue)
         {
             $this->$property = $oldValue;
         }
     }
 
     /**
+     * applies the change stored in the diff
+     *
+     * @param Tinebase_Record_Diff $diff
+     * @return void
+     */
+    public function applyDiff(Tinebase_Record_Diff $diff)
+    {
+        /* TODO special treatment? for what? how? */
+
+        if ($this->has('is_deleted')) {
+            $this->is_deleted = 0;
+        }
+
+        foreach((array)($diff->diff) as $property => $oldValue)
+        {
+            if (is_array($oldValue) && count($oldValue) === 4 &&
+                    isset($oldValue['model']) && isset($oldValue['added']) &&
+                    isset($oldValue['removed']) && isset($oldValue['modified'])) {
+                // RecordSetDiff
+                $recordSetDiff = new Tinebase_Record_RecordSetDiff($oldValue);
+
+                if (! $this->$property instanceof Tinebase_Record_RecordSet) {
+                    $this->$property = new Tinebase_Record_RecordSet($oldValue['model'],
+                        is_array($this->$property)?$this->$property:array());
+                }
+
+                $this->$property->applyRecordSetDiff($recordSetDiff);
+            } else {
+                $this->$property = $oldValue;
+            }
+        }
+    }
+
+    /**
      * returns true if this record should be replicated
      *
      * @return boolean
index 2de8df4..54f1626 100644 (file)
@@ -243,6 +243,14 @@ interface Tinebase_Record_Interface extends ArrayAccess, IteratorAggregate
     public function undo(Tinebase_Record_Diff $diff);
 
     /**
+     * applies the change stored in the diff
+     *
+     * @param Tinebase_Record_Diff $diff
+     * @return void
+     */
+    public function applyDiff(Tinebase_Record_Diff $diff);
+
+    /**
      * returns true if this record should be replicated
      *
      * @return boolean
index a4cecf9..a980666 100644 (file)
@@ -680,6 +680,49 @@ class Tinebase_Record_RecordSet implements IteratorAggregate, Countable, ArrayAc
         
         return $result;
     }
+
+    public function applyRecordSetDiff(Tinebase_Record_RecordSetDiff $diff)
+    {
+        $model = $diff->model;
+        if ($this->getRecordClassName() !== $model) {
+            throw new Tinebase_Exception_InvalidArgument('try to apply record set diff on a record set of different model!' .
+                'record set model: ' . $this->getRecordClassName() . ', record set diff model: ' . $model);
+        }
+
+        /** @var Tinebase_Record_Abstract $modelInstance */
+        $modelInstance = new $model(array(), true);
+        $idProperty = $modelInstance->getIdProperty();
+
+        foreach($diff->added as $data) {
+            $newRecord = new $model($data);
+            $this->addRecord($newRecord);
+        }
+
+        foreach($diff->removed as $data) {
+            if (!isset($data[$idProperty])) {
+                throw new Tinebase_Exception_InvalidArgument('failed to apply record set diff because removed data contained bad data, id property missing (' . $idProperty . '): ' . print_r($data, true));
+            }
+            if (false === ($record = $this->getById($data[$idProperty]))) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
+                    . ' Did not find the record supposed to be removed with id: ' . $data[$idProperty]);
+            } else {
+                $this->removeRecord($record);
+            }
+        }
+
+        foreach($diff->modified as $data) {
+            $diff = new Tinebase_Record_Diff();
+            $diff->id = $data[$idProperty];
+            $diff->diff = $data;
+            if (false === ($record = $this->getById($diff->getId()))) {
+                Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__
+                    . ' Did not find the record supposed to be modified with id: ' . $data[$idProperty]);
+                throw new Tinebase_Exception_InvalidArgument('Did not find the record supposed to be modified with id: ' . $data[$idProperty]);
+            } else {
+                $record->applyDiff($diff);
+            }
+        }
+    }
     
     /**
      * merges records from given record set
index 708dd06..57fec18 100644 (file)
  * 
  * @package     Tinebase
  * @subpackage  Record
+ *
+ * @property string model
+ * @property array added
+ * @property array removed
+ * @property array modified
  */
 class Tinebase_Record_RecordSetDiff extends Tinebase_Record_Abstract 
 {
diff --git a/tine20/Tinebase/Role.php b/tine20/Tinebase/Role.php
new file mode 100644 (file)
index 0000000..b09ce98
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Tine 2.0 role controller
+ *
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ *
+ */
+
+/**
+ * this class handles the roles
+ *
+ * @package     Tinebase
+ */
+class Tinebase_Role extends Tinebase_Acl_Roles
+{
+}
\ No newline at end of file
diff --git a/tine20/Tinebase/RoleMember.php b/tine20/Tinebase/RoleMember.php
new file mode 100644 (file)
index 0000000..6cb8b6c
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Tine 2.0 role member controller
+ *
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ *
+ */
+
+/**
+ * this class handles the role members
+ *
+ * @package     Tinebase
+ */
+class Tinebase_RoleMember extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * holds the _instance of the singleton
+     *
+     * @var Tinebase_RoleMember
+     */
+    private static $_instance = NULL;
+
+    /**
+     * the clone function
+     *
+     * disabled. use the singleton
+     */
+    private function __clone()
+    {
+    }
+
+    /**
+     * the constructor
+     *
+     * disabled. use the singleton
+     */
+    private function __construct()
+    {
+        $this->_applicationName = 'Tinebase';
+        $this->_modelName = 'Tinebase_Model_RoleMember';
+        $this->_backend = new Tinebase_Backend_Sql(array(
+            'modelName' => 'Tinebase_Model_RoleMember',
+            'tableName' => 'role_accounts',
+        ), Tinebase_Core::getDb());
+        $this->_purgeRecords = TRUE;
+        //$this->_resolveCustomFields = FALSE;
+        $this->_updateMultipleValidateEachRecord = TRUE;
+        $this->_doContainerACLChecks = FALSE;
+        $this->_setNotes = FALSE;
+        $this->_omitModLog = TRUE;
+    }
+
+    /**
+     * the singleton pattern
+     *
+     * @return Tinebase_RoleMember
+     */
+    public static function getInstance()
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new Tinebase_RoleMember;
+        }
+
+        return self::$_instance;
+    }
+}
\ No newline at end of file
diff --git a/tine20/Tinebase/RoleRight.php b/tine20/Tinebase/RoleRight.php
new file mode 100644 (file)
index 0000000..042425e
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * Tine 2.0 role right controller
+ *
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ *
+ */
+
+/**
+ * this class handles the role rights
+ *
+ * @package     Tinebase
+ */
+class Tinebase_RoleRight extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * holds the _instance of the singleton
+     *
+     * @var Tinebase_RoleRight
+     */
+    private static $_instance = NULL;
+
+    /**
+     * the clone function
+     *
+     * disabled. use the singleton
+     */
+    private function __clone()
+    {
+    }
+
+    /**
+     * the constructor
+     *
+     * disabled. use the singleton
+     */
+    private function __construct()
+    {
+        $this->_applicationName = 'Tinebase';
+        $this->_modelName = 'Tinebase_Model_RoleRight';
+        $this->_backend = new Tinebase_Backend_Sql(array(
+            'modelName' => 'Tinebase_Model_RoleRight',
+            'tableName' => 'role_rights',
+        ), Tinebase_Core::getDb());
+        $this->_purgeRecords = TRUE;
+        //$this->_resolveCustomFields = FALSE;
+        $this->_updateMultipleValidateEachRecord = TRUE;
+        $this->_doContainerACLChecks = FALSE;
+        $this->_setNotes = FALSE;
+        $this->_omitModLog = TRUE;
+    }
+
+    /**
+     * the singleton pattern
+     *
+     * @return Tinebase_RoleRight
+     */
+    public static function getInstance()
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new Tinebase_RoleRight;
+        }
+
+        return self::$_instance;
+    }
+}
\ No newline at end of file
index f6913bb..46da1f8 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Scheduler
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2010-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Goekmen Ciyiltepe <g.ciyiltepe@metaways.de>
  */
 
@@ -332,6 +332,27 @@ class Tinebase_Scheduler_Task extends Zend_Scheduler_Task
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
             . ' Saved task Tinebase_User/Group::syncUsers/Groups in scheduler.');
     }
+
+    /**
+     * @param Zend_Scheduler $_scheduler
+     */
+    public static function addReplicationTask(Zend_Scheduler $_scheduler)
+    {
+        if ($_scheduler->hasTask('Tinebase_Timemachine_ModificationLog::readModificationLogFromMaster')) {
+            return;
+        }
+
+        $task = self::getPreparedTask(self::TASK_TYPE_HOURLY, array(
+            'controller'    => 'Tinebase_Timemachine_ModificationLog',
+            'action'        => 'readModificationLogFromMaster'
+        ));
+
+        $_scheduler->addTask('Tinebase_Timemachine_ModificationLog::readModificationLogFromMaster', $task);
+        $_scheduler->saveTask();
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
+            . ' Saved task Tinebase_Timemachine_ModificationLog::readModificationLogFromMaster in scheduler.');
+    }
     
     /**
      * run requests
index 2e37ead..12a3e2b 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Setup
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Jonas Fischer <j.fischer@metaways.de>
- * @copyright   Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -42,9 +42,14 @@ class Tinebase_Setup_Initialize extends Setup_Initialize
         $this->_setupConfigOptions($_options);
         $this->_setupGroups();
 
-        Tinebase_Acl_Roles::getInstance()->createInitialRoles();
+        $roleController = Tinebase_Acl_Roles::getInstance();
+        $roleController->createInitialRoles();
 
+        $oldNotesValue = $roleController->useNotes(false);
+        $oldModLogValue = $roleController->modlogActive(false);
         parent::_initialize($_application, $_options);
+        $roleController->useNotes($oldNotesValue);
+        $roleController->modlogActive($oldModLogValue);
     }
 
     /**
@@ -156,5 +161,6 @@ class Tinebase_Setup_Initialize extends Setup_Initialize
         Tinebase_Scheduler_Task::addAccessLogCleanupTask($scheduler);
         Tinebase_Scheduler_Task::addImportTask($scheduler);
         Tinebase_Scheduler_Task::addAccountSyncTask($scheduler);
+        Tinebase_Scheduler_Task::addReplicationTask($scheduler);
     }
 }
index 87b2ba7..3db0ac1 100644 (file)
@@ -147,4 +147,61 @@ class Tinebase_Setup_Update_Release10 extends Setup_Update_Abstract
 
         $this->setApplicationVersion('Tinebase', '10.7');
     }
+
+    /**
+     * update to 10.8
+     *
+     * update roles and application table
+     */
+    public function update_7()
+    {
+        if (!$this->_backend->columnExists('is_deleted', 'roles')) {
+            $query = $this->_backend->addAddCol(null, 'roles',
+                new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                </field>'), 'last_modified_time'
+            );
+            $query = $this->_backend->addAddCol($query, 'roles',
+                new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>deleted_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>'), 'is_deleted'
+            );
+            $query = $this->_backend->addAddCol($query, 'roles',
+                new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>deleted_time</name>
+                    <type>datetime</type>
+                </field>'), 'deleted_by'
+            );
+            $query = $this->_backend->addAddCol($query, 'roles',
+                new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>seq</name>
+                    <type>integer</type>
+                    <notnull>true</notnull>
+                    <default>0</default>
+                </field>'), 'deleted_time'
+            );
+            $this->_backend->execQueryVoid($query);
+            $this->setTableVersion('roles', '2');
+        }
+
+        if (!$this->_backend->columnExists('state', 'applications')) {
+
+            $this->_backend->addCol('applications',
+                new Setup_Backend_Schema_Field_Xml('<field>
+                    <name>state</name>
+                    <type>text</type>
+                    <length>65535</length>
+                    <notnull>false</notnull>
+                </field>')
+            );
+
+            $this->setTableVersion('applications', '4');
+        }
+
+        $this->setApplicationVersion('Tinebase', '10.8');
+    }
 }
index 8bf546f..8da8ac4 100644 (file)
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Tinebase</name>
-    <version>10.7</version>
+    <version>10.8</version>
     <tables>
         <table>
             <name>applications</name>
-            <version>3</version>
+            <version>4</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <length>20</length>
                     <notnull>true</notnull>
                 </field>
+                <field>
+                    <name>state</name>
+                    <type>text</type>
+                    <length>65535</length>
+                    <notnull>false</notnull>
+                </field>
                 <index>
                     <name>id</name>
                     <primary>true</primary>
 
         <table>
             <name>roles</name>
-            <version>1</version>
+            <version>2</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <name>last_modified_time</name>
                     <type>datetime</type>
                 </field>
-
+                <field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                </field>
+                <field>
+                    <name>deleted_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>deleted_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>seq</name>
+                    <type>integer</type>
+                    <notnull>true</notnull>
+                    <default>0</default>
+                </field>
                 <index>
                     <name>id</name>
                     <primary>true</primary>
index 971f447..fd4a3b3 100644 (file)
@@ -39,7 +39,7 @@
  *       log anymore!
  * @todo refactor this to use generic sql backend + remove Tinebase_Db_Table usage
  */
-class Tinebase_Timemachine_ModificationLog
+class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interface
 {
     const CREATED = 'created';
     const DELETED = 'deleted';
@@ -105,6 +105,13 @@ class Tinebase_Timemachine_ModificationLog
      * @var string
      */
     protected $_applicationId = NULL;
+
+    /**
+     * if set, all newly created modlogs will have this external instance id. this is used during applying replication logs
+     *
+     * @var string
+     */
+    protected $_externalInstanceId = NULL;
     
     /**
      * the singleton pattern
@@ -418,6 +425,12 @@ class Tinebase_Timemachine_ModificationLog
         $id = $modification->generateUID();
         $modification->setId($id);
         $modification->convertDates = true;
+
+        // mainly if we are applying replication modlogs on the slave, we set the masters instance id here
+        if (null !== $this->_externalInstanceId) {
+            $modification->instance_id = $this->_externalInstanceId;
+        }
+
         $modificationArray = $modification->toArray();
         if (is_array($modificationArray['new_value'])) {
             throw new Tinebase_Exception_Record_Validation("New value is an array! \n" . print_r($modificationArray['new_value'], true));
@@ -842,6 +855,8 @@ class Tinebase_Timemachine_ModificationLog
      * @return Tinebase_Record_RecordSet RecordSet of Tinebase_Model_ModificationLog
      * @throws Tinebase_Exception_NotImplemented
      * @internal param array $_oldData
+     *
+     * TODO instance id is never set in this code path! => thus replication doesn't work here!
      */
     public function writeModLogMultiple($_ids, $_currentData, $_newData, $_model, $_backend, $updateMetaData = array())
     {
@@ -1111,7 +1126,7 @@ class Tinebase_Timemachine_ModificationLog
         $result = array();
 
         /** @var Tinebase_Model_ModificationLog $modlog */
-        foreach($modLogs as $modlog) {
+        foreach ($modLogs as $modlog) {
             $modAtrb = $modlog->modified_attribute;
             if (empty($modAtrb)) {
                 $diff = new Tinebase_Record_Diff(json_decode($modlog->new_value, true));
@@ -1123,4 +1138,160 @@ class Tinebase_Timemachine_ModificationLog
 
         return array_keys($result);
     }
+
+    /**
+     * @return bool
+     * @throws Tinebase_Exception_AccessDenied
+     */
+    public function readModificationLogFromMaster()
+    {
+        // check if we are a replication slave
+        if (!($slaveConfiguration = Tinebase_Config::getInstance()->get(Tinebase_Config::REPLICATION_SLAVE)) ||
+            !($slaveConfiguration instanceof Tinebase_Config_Struct)) {
+            return true;
+        }
+
+        $result = Tinebase_Lock::aquireDBSessionLock(__METHOD__);
+        if (false === $result) {
+            // we are already running
+            if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ .
+                ' failed to aquire DB lock, it seems we are already running in a parallel process.');
+            return true;
+        }
+        if (null === $result) {
+            // DB backend does not suppport lock, no way we do replication without proper thread concurrency!
+            Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ .
+                ' failed to aquire DB lock, the DB backend doesn\'t support locks. You should not run a replication on this type of DB backend!');
+            return false;
+        }
+
+        $tine20Url = $slaveConfiguration->{Tinebase_Config::MASTER_URL};
+        $tine20LoginName = $slaveConfiguration->{Tinebase_Config::MASTER_USERNAME};
+        $tine20Password = $slaveConfiguration->{Tinebase_Config::MASTER_PASSWORD};
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+            ' trying to connect to master host: ' . $tine20Url . ' with user: ' . $tine20LoginName);
+
+        $tine20Service = new Zend_Service_Tine20($tine20Url);
+
+        $authResponse = $tine20Service->login($tine20LoginName, $tine20Password);
+        if (!is_array($authResponse) || !isset($authResponse['success']) || $authResponse['success'] !== true) {
+            throw new Tinebase_Exception_AccessDenied('login failed');
+        }
+        unset($authResponse);
+
+        //get replication state:
+        $state = Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->state;
+        if (!is_array($state) || !isset($state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID])) {
+            $masterReplicationId = 0;
+        } else {
+            $masterReplicationId = $state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID];
+        }
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+            ' master replication id: ' . $masterReplicationId);
+
+        $tinebaseProxy = $tine20Service->getProxy('Tinebase');
+        /** @noinspection PhpUndefinedMethodInspection */
+        $result = $tinebaseProxy->getReplicationModificationLogs($masterReplicationId, 100);
+        /* TODO make the amount above configurable  */
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+            ' received ' . count($result['results']) . ' modification logs');
+
+        // memory cleanup
+        unset($tinebaseProxy);
+        unset($tine20Service);
+
+        $modifications = new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', $result['results']);
+        unset($result);
+
+        return $this->applyReplicationModLogs($modifications);
+    }
+
+    /**
+     * apply modification logs from a replication master locally
+     *
+     * @param Tinebase_Record_RecordSet $modifications
+     * @return boolean
+     */
+    public function applyReplicationModLogs(Tinebase_Record_RecordSet $modifications)
+    {
+        $currentRecordType = NULL;
+        $controller = NULL;
+        $controllerCache = array();
+
+        $transactionManager = Tinebase_TransactionManager::getInstance();
+        $db = Tinebase_Core::getDb();
+        $applicationController = Tinebase_Application::getInstance();
+        /** @var Tinebase_Model_Application $tinebaseApplication */
+        $tinebaseApplication = $applicationController->getApplicationByName('Tinebase');
+        if (!is_array($tinebaseApplication->state)) {
+            $tinebaseApplication->state = array();
+        }
+
+        /** @var Tinebase_Model_ModificationLog $modification */
+        foreach($modifications as $modification)
+        {
+            $transactionId = $transactionManager->startTransaction($db);
+
+            $this->_externalInstanceId = $modification->instance_id;
+
+            try {
+                if ($currentRecordType !== $modification->record_type || !isset($controller)) {
+                    $currentRecordType = $modification->record_type;
+                    if (!isset($controllerCache[$modification->record_type])) {
+                        $controller = Tinebase_Core::getApplicationInstance($modification->record_type);
+                        $controllerCache[$modification->record_type] = $controller;
+                    } else {
+                        $controller = $controllerCache[$modification->record_type];
+                    }
+                }
+
+                switch ($modification->change_type) {
+                    case Tinebase_Timemachine_ModificationLog::CREATED:
+                        $diff = new Tinebase_Record_Diff(json_decode($modification->new_value, true));
+                        $model = $modification->record_type;
+                        $record = new $model($diff->diff);
+                        $controller->create($record);
+                        break;
+
+                    case Tinebase_Timemachine_ModificationLog::UPDATED:
+                        $diff = new Tinebase_Record_Diff(json_decode($modification->new_value, true));
+                        $record = $controller->get($modification->record_id, NULL, true, true);
+                        $record->applyDiff($diff);
+                        $controller->update($record);
+                        break;
+
+                    case Tinebase_Timemachine_ModificationLog::DELETED:
+                        $controller->delete($modification->record_id);
+                        break;
+
+                    default:
+                        throw new Tinebase_Exception('unknown Tinebase_Model_ModificationLog->old_value: ' . $modification->old_value);
+                }
+
+                $state = $tinebaseApplication->state;
+                $state[Tinebase_Model_Application::STATE_REPLICATION_MASTER_ID] = $modification->instance_seq;
+                $tinebaseApplication->state = $state;
+                $tinebaseApplication = $applicationController->updateApplication($tinebaseApplication);
+
+                $transactionManager->commitTransaction($transactionId);
+
+            } catch (Exception $e) {
+                $this->_externalInstanceId = null;
+
+                Tinebase_Exception::log($e, false);
+
+                $transactionManager->rollBack();
+
+                // must not happen, continuing pointless!
+                return false;
+            }
+        }
+
+        $this->_externalInstanceId = null;
+
+        return true;
+    }
 }