Tinebase Container - make it replicable
authorPaul Mehrer <p.mehrer@metaways.de>
Thu, 1 Jun 2017 15:40:30 +0000 (17:40 +0200)
committerPaul Mehrer <p.mehrer@metaways.de>
Fri, 2 Jun 2017 12:47:42 +0000 (14:47 +0200)
Change-Id: I8eabc08d41cfe342d756932f3d45cb58e1248dad
Reviewed-on: http://gerrit.tine20.com/customers/4809
Reviewed-by: Paul Mehrer <p.mehrer@metaways.de>
Tested-by: Paul Mehrer <p.mehrer@metaways.de>
tests/tine20/Tinebase/Server/WebDAVTests.php
tests/tine20/Tinebase/Timemachine/ModificationLogTest.php
tine20/Tinebase/Container.php
tine20/Tinebase/Model/Container.php
tine20/Tinebase/Timemachine/ModificationLog.php

index 1ac4047..0c8bbff 100644 (file)
@@ -317,6 +317,9 @@ EOS
         $account = $this->getAccountByName($credentials['username']);
         
         $this->assertInstanceOf('Tinebase_Model_FullUser', $account);
+        if (Tinebase_Core::getUser() === null) {
+            Tinebase_Core::set(Tinebase_Core::USER, $account);
+        }
         
         $containerId = $this->getPersonalContainer($account, 'Calendar_Model_Event')
             ->getFirstRecord()
@@ -378,6 +381,10 @@ EOS
         $account = $this->getAccountByName($credentials['username']);
         
         $this->assertInstanceOf('Tinebase_Model_FullUser', $account);
+
+        if (Tinebase_Core::getUser() === null) {
+            Tinebase_Core::set(Tinebase_Core::USER, $account);
+        }
         
         $containerId = $this->getPersonalContainer($account, 'Calendar_Model_Event')
             ->getFirstRecord()
index 805bf66..a50f796 100644 (file)
@@ -381,6 +381,7 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
 
         $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq($instance_seq);
         $roleModifications = $modifications->filter('record_type', 'Tinebase_Model_Role');
+        static::assertEquals(5, $roleModifications->count(), 'should have 5 mod logs to process');
         //$groupModifications = $modifications->filter('record_type', 'Tinebase_Model_Group');
         //$userModifications = $modifications->filter('record_type', '/Tinebase_Model_User.*/', true);
 
@@ -425,6 +426,98 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
         $this->assertEquals(1, $newRole->members->filter('account_id', 'test3')->count(), 'record set diff modified didn\'t work, test3 not found');
     }
 
+    public function testContainerReplication()
+    {
+        $instance_seq = Tinebase_Timemachine_ModificationLog::getInstance()->getMaxInstanceSeq();
+
+        $containerController = Tinebase_Container::getInstance();
+
+        $container = new Tinebase_Model_Container(array(
+            'name' => 'unittest test container',
+            'type' => Tinebase_Model_Container::TYPE_SHARED,
+            'backend' => 'sql',
+            'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId()
+        ));
+        $container = $containerController->addContainer($container);
+
+        $container->color = '#FFFFFF';
+        $containerController->update($container);
+
+        $grants = $containerController->getGrantsOfContainer($container->getId(), true);
+        static::assertEquals(2, $grants->count(), 'should find 2 grant records');
+        /** @var Tinebase_Model_Grants $grant */
+        $grant = $grants->filter('account_type', 'anyone')->getFirstRecord();
+        static::assertNotNull($grant);
+        static::assertEquals(false, $grant->{Tinebase_Model_Grants::GRANT_EDIT}, 'edit grant should be false');
+        $grant->{Tinebase_Model_Grants::GRANT_EDIT} = true;
+        $grant = $grants->filter('account_id', Tinebase_Core::getUser()->getId())->getFirstRecord();
+        static::assertNotNull($grant);
+        static::assertEquals(true, $grant->{Tinebase_Model_Grants::GRANT_EDIT}, 'edit grant should be true');
+        $grant->{Tinebase_Model_Grants::GRANT_EDIT} = false;
+        $containerController->setGrants($container->getId(), $grants, true);
+
+        $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq($instance_seq);
+        $containerModifications = $modifications->filter('record_type', 'Tinebase_Model_Container');
+        static::assertEquals(4, $containerModifications->count(), 'should have 4 mod logs to process');
+
+        // rollback
+        Tinebase_TransactionManager::getInstance()->rollBack();
+        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+
+        $notFound = false;
+        try {
+            // avoid Cache, so use get, not getContainerById
+            $containerController->get($container->getId());
+        } catch (Tinebase_Exception_NotFound $tenf) {
+            $notFound = true;
+        }
+        static::assertTrue($notFound, 'roll back did not work...');
+
+        // create the container
+        $mod = $containerModifications->getFirstRecord();
+        static::assertNotNull($mod);
+        $containerModifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        static::assertTrue($result, 'applyReplactionModLogs failed');
+        // avoid Cache, so use get, not getContainerById
+        /** @var Tinebase_Model_Container $newContainer */
+        $newContainer = $containerController->get($container->getId());
+        static::assertEquals($container->name, $newContainer->name);
+        static::assertTrue(empty($newContainer->color), 'color not empty');
+
+        // set grants from initial container create, nothing changes actually
+        $mod = $containerModifications->getFirstRecord();
+        static::assertNotNull($mod);
+        $containerModifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        static::assertTrue($result, 'applyReplactionModLogs failed');
+
+        // change color
+        $mod = $containerModifications->getFirstRecord();
+        static::assertNotNull($mod);
+        $containerModifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        static::assertTrue($result, 'applyReplactionModLogs failed');
+        // avoid Cache, so use get, not getContainerById
+        $newContainer = $containerController->get($container->getId());
+        static::assertEquals('#FFFFFF', $newContainer->color, 'color not set properly');
+
+        //change grants
+        $mod = $containerModifications->getFirstRecord();
+        static::assertNotNull($mod);
+        $containerModifications->removeRecord($mod);
+        $result = Tinebase_Timemachine_ModificationLog::getInstance()->applyReplicationModLogs(new Tinebase_Record_RecordSet('Tinebase_Model_ModificationLog', array($mod)));
+        $this->assertTrue($result, 'applyReplactionModLogs failed');
+        $grants = $containerController->getGrantsOfContainer($container->getId(), true);
+        static::assertEquals(2, $grants->count(), 'should find 2 grant records');
+        $grant = $grants->filter('account_type', 'anyone')->getFirstRecord();
+        static::assertNotNull($grant);
+        static::assertEquals(true, $grant->{Tinebase_Model_Grants::GRANT_EDIT}, 'edit grant should be true');
+        $grant = $grants->filter('account_id', Tinebase_Core::getUser()->getId())->getFirstRecord();
+        static::assertNotNull($grant);
+        static::assertEquals(false, $grant->{Tinebase_Model_Grants::GRANT_EDIT}, 'edit grant should be false');
+    }
+
     public function testGroupReplication()
     {
         $instance_seq = Tinebase_Timemachine_ModificationLog::getInstance()->getMaxInstanceSeq();
@@ -442,6 +535,7 @@ class Tinebase_Timemachine_ModificationLogTest extends PHPUnit_Framework_TestCas
 
         $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getReplicationModificationsByInstanceSeq($instance_seq);
         $groupModifications = $modifications->filter('record_type', 'Tinebase_Model_Group');
+        static::assertEquals(5, $groupModifications->count(), 'should have 5 mod logs to process');
 
         if ($groupController instanceof Tinebase_Group_Interface_SyncAble) {
             $this->assertEquals(0, $groupModifications->count(), ' for syncables group replication should be turned off!');
index 25c4131..7d51573 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Container
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  * 
  * @todo        refactor that: remove code duplication, remove Zend_Db_Table_Abstract usage, use standard record controller/backend functions
@@ -25,6 +25,8 @@
  */
 class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tinebase_Controller_SearchInterface, Tinebase_Container_Interface
 {
+    use Tinebase_Controller_Record_ModlogTrait;
+
     /**
      * Table name without prefix
      *
@@ -238,6 +240,18 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
 
         return $container;
     }
+
+    /**
+     * do something after creation of record
+     *
+     * @param Tinebase_Record_Interface $_newRecord
+     * @param Tinebase_Record_Interface $_recordToCreate
+     * @return void
+     */
+    protected function _inspectAfterCreate(Tinebase_Record_Interface $_newRecord, Tinebase_Record_Interface $_recordToCreate)
+    {
+        $this->_writeModLog($_newRecord, null);
+    }
     
     /**
      * add grants to container
@@ -290,6 +304,12 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
                 $this->_getContainerAclTable()->insert($data);
             }
         }
+
+        $newGrants = $this->getGrantsOfContainer($containerId, true);
+        $this->_writeModLog(
+            new Tinebase_Model_Container(array('id' => $containerId, 'account_grants' => $newGrants), true),
+            new Tinebase_Model_Container(array('id' => $containerId, 'account_grants' => $containerGrants), true)
+        );
         
         $this->_setRecordMetaDataAndUpdate($containerId, 'update');
         
@@ -1080,6 +1100,8 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
         $myTransactionId = $tm->startTransaction(Tinebase_Core::getDb());
 
         try {
+            $this->_writeModLog(null, $container);
+
             $this->deleteContainerContents($container, $_ignoreAcl);
             $deletedContainer = $this->_setRecordMetaDataAndUpdate($container, 'delete');
 
@@ -1530,6 +1552,12 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
                     }
                 }
             }
+
+            $newGrants = $this->getGrantsOfContainer($containerId, true);
+            $this->_writeModLog(
+                new Tinebase_Model_Container(array('id' => $containerId, 'account_grants' => $newGrants), true),
+                new Tinebase_Model_Container(array('id' => $containerId), true)
+            );
             
             Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
             
@@ -1904,8 +1932,15 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
 
         $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
 
+        //use get (avoids cache) or getContainerById, guess its better to avoid the cache
+        $oldContainer = $this->get($_record->getId());
+
         $result = parent::update($_record);
 
+        unset($result->account_grants);
+        unset($oldContainer->account_grants);
+        $this->_writeModLog($result, $oldContainer);
+
         Tinebase_Record_PersistentObserver::getInstance()->fireEvent($result, 'Tinebase_Event_Record_Update');
 
         Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
@@ -1944,4 +1979,39 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
             . ' Set owner for ' . $count . ' containers.');
     }
+
+    /**
+     * apply modification logs from a replication master locally
+     *
+     * @param Tinebase_Model_ModificationLog $_modification
+     */
+    public function applyReplicationModificationLog(Tinebase_Model_ModificationLog $_modification)
+    {
+        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);
+                $this->addContainer($record, null, true);
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::UPDATED:
+                $diff = new Tinebase_Record_Diff(json_decode($_modification->new_value, true));
+                if (isset($diff->diff['account_grants'])) {
+                    $this->setGrants($_modification->record_id, new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $diff->diff['account_grants']), true, false);
+                } else {
+                    $record = $this->get($_modification->record_id, true);
+                    $record->applyDiff($diff);
+                    $this->update($record);
+                }
+                break;
+
+            case Tinebase_Timemachine_ModificationLog::DELETED:
+                $this->delete($_modification->record_id);
+                break;
+
+            default:
+                throw new Tinebase_Exception('unknown Tinebase_Model_ModificationLog->change_type: ' . $_modification->change_type);
+        }
+    }
 }
index a8d5eff..de8efe8 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Tinebase
  * @subpackage  Record
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2017 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
@@ -170,10 +170,14 @@ class Tinebase_Model_Container extends Tinebase_Record_Abstract
         
         return $id;
     }
-    
+
     /**
-     * (non-PHPdoc)
-     * @see Tinebase/Record/Tinebase_Record_Abstract#setFromArray($_data)
+     * sets the record related properties from user generated input.
+     *
+     * Input-filtering and validation by Zend_Filter_Input can enabled and disabled
+     *
+     * @param array $_data            the new data to set
+     * @throws Tinebase_Exception_Record_Validation when content contains invalid or missing data
      */
     public function setFromArray(array $_data)
     {
@@ -242,6 +246,7 @@ class Tinebase_Model_Container extends Tinebase_Record_Abstract
      * checks if container is a personal container of given account
      * 
      * @param mixed $account
+     * @return bool
      */
     public function isPersonalOf($account)
     {
@@ -328,4 +333,12 @@ class Tinebase_Model_Container extends Tinebase_Record_Abstract
     {
         return $this->name;
     }
+
+    /**
+     * @return bool
+     */
+    public function isReplicable()
+    {
+        return true;
+    }
 }
index cd43e55..2c61595 100644 (file)
@@ -218,7 +218,8 @@ class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interf
                     )));
 
 
-                    $existingIds = $backend->search($idFilter, null, true);
+                    // to work around Tinebase_Container, we just send one more true parameter, will be ignored by all real backends, only taken into account by Tinebase_Container
+                    $existingIds = $backend->search($idFilter, null, true, true);
 
                     if (!is_array($existingIds)) {
                         throw new Exception('search for model: ' . $model . ' returned not an array!');
@@ -1288,29 +1289,7 @@ class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interf
                 if (method_exists($controller, 'applyReplicationModificationLog')) {
                     $controller->applyReplicationModificationLog($modification);
                 } else {
-
-                    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);
-                    }
+                    static::defaultApply($modification, $controller);
                 }
 
                 $state = $tinebaseApplication->state;
@@ -1353,6 +1332,37 @@ class Tinebase_Timemachine_ModificationLog implements Tinebase_Controller_Interf
     }
 
     /**
+     * @param Tinebase_Model_ModificationLog $_modification
+     * @param Tinebase_Controller_Record_Abstract $_controller
+     * @throws Tinebase_Exception
+     */
+    public static function defaultApply(Tinebase_Model_ModificationLog $_modification, $_controller)
+    {
+        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->change_type: ' . $_modification->change_type);
+        }
+    }
+
+    /**
      * @param int $count
      */
     public function increaseReplicationMasterId($count = 1)