/**
  * record set diff resolving
  * 
  * @param Tinebase_Record_Interface $newRecord
  * @param Tinebase_Model_ModificationLog $diff
  */
 protected function _resolveRecordSetMergeUpdate(Tinebase_Record_Interface $newRecord, Tinebase_Model_ModificationLog $diff)
 {
     $attribute = $diff->modified_attribute;
     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
         Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Try to merge record set changes of record attribute " . $attribute);
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' New record: ' . print_r($newRecord->toArray(), TRUE));
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' Mod log: ' . print_r($diff->toArray(), TRUE));
     }
     $concurrentChangeDiff = new Tinebase_Record_RecordSetDiff(Zend_Json::decode($diff->new_value));
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' RecordSet diff: ' . print_r($concurrentChangeDiff->toArray(), TRUE));
     }
     foreach ($concurrentChangeDiff->added as $added) {
         $addedRecord = new $concurrentChangeDiff->model($added);
         if (!$newRecord->{$attribute}->getById($addedRecord->getId())) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Adding recently added record " . $addedRecord->getId());
             }
             $newRecord->{$attribute}->addRecord($addedRecord);
         }
     }
     foreach ($concurrentChangeDiff->removed as $removed) {
         $removedRecord = new $concurrentChangeDiff->model($removed);
         $recordToRemove = $newRecord->{$attribute}->getById($removedRecord->getId());
         if ($recordToRemove) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Removing record " . $recordToRemove->getId());
             }
             $newRecord->{$attribute}->removeRecord($recordToRemove);
         }
     }
     foreach ($concurrentChangeDiff->modified as $modified) {
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
             Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' modified diff: ' . print_r($modified, TRUE));
         }
         $modifiedRecord = new $concurrentChangeDiff->model(array_merge(array('id' => $modified['id']), $modified['diff']), TRUE);
         $newRecordsRecord = $newRecord->{$attribute}->getById($modifiedRecord->getId());
         if ($newRecordsRecord && ($newRecordsRecord->has('seq') || $newRecordsRecord->has('last_modified_time'))) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Managing updates for ' . get_class($newRecordsRecord) . ' record ' . $newRecordsRecord->getId());
             }
             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' new record: ' . print_r($newRecordsRecord->toArray(), TRUE));
             }
             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' modified record: ' . print_r($modifiedRecord->toArray(), TRUE));
             }
             if (null === $this->_applicationId) {
                 throw new Tinebase_Exception_UnexpectedValue('application_id needs to be set here');
             }
             $this->manageConcurrentUpdates($this->_applicationId, $newRecordsRecord, $modifiedRecord);
         } else {
             throw new Tinebase_Timemachine_Exception_ConcurrencyConflict('concurrency conflict - modified record changes could not be merged!');
         }
     }
     if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
         Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' New record after merge: ' . print_r($newRecord->toArray(), TRUE));
     }
 }
 /**
  * testRelatedModlog
  * 
  * @see 0000996: add changes in relations/linked objects to modlog/history
  */
 public function testRelatedModlog()
 {
     // create lead with tag, customfield and related contacts
     $savedLead = $this->_saveLead();
     // change relations, customfields + tags
     $savedLead['tags'][] = array('name' => 'another tag', 'type' => Tinebase_Model_Tag::TYPE_PERSONAL);
     foreach ($savedLead['relations'] as $key => $value) {
         if ($value['type'] == 'PARTNER') {
             $savedLead['relations'][$key]['type'] = 'CUSTOMER';
         }
         if ($value['type'] == 'TASK') {
             unset($savedLead['relations'][$key]);
         }
     }
     $savedLead['customfields'][$this->_cfcName] = '5678';
     $updatedLead = $this->_instance->saveLead($savedLead);
     // check modlog + history
     $modifications = Tinebase_Timemachine_ModificationLog::getInstance()->getModifications('Crm', $updatedLead['id']);
     //print_r($updatedLead);
     $this->assertEquals(3, count($modifications), 'expected 3 modifications: ' . print_r($modifications->toArray(), TRUE));
     foreach ($modifications as $modification) {
         switch ($modification->modified_attribute) {
             case 'customfields':
                 $this->assertEquals('{"' . $this->_cfcName . '":"5678"}', $modification->new_value);
                 break;
             case 'relations':
                 $diff = new Tinebase_Record_RecordSetDiff(Zend_Json::decode($modification->new_value));
                 $this->assertEquals(0, count($diff->added));
                 $this->assertEquals(1, count($diff->removed));
                 $this->assertEquals(1, count($diff->modified), 'relations modified mismatch: ' . print_r($diff->toArray(), TRUE));
                 $this->assertTrue(isset($diff->modified[0]['diff']['type']));
                 $this->assertEquals('CUSTOMER', $diff->modified[0]['diff']['type'], 'type diff is not correct: ' . print_r($diff->toArray(), TRUE));
                 break;
             case 'tags':
                 $diff = new Tinebase_Record_RecordSetDiff(Zend_Json::decode($modification->new_value));
                 $this->assertEquals(1, count($diff->added));
                 $this->assertEquals(0, count($diff->removed));
                 $this->assertEquals(0, count($diff->modified), 'tags modified mismatch: ' . print_r($diff->toArray(), TRUE));
                 break;
             default:
                 $this->fail('Invalid modification: ' . print_r($modification->toArray(), TRUE));
         }
     }
 }
 /**
  * get system note change text
  * 
  * @param Tinebase_Model_ModificationLog $modification
  * @return string
  */
 protected function _getSystemNoteChangeText(Tinebase_Model_ModificationLog $modification)
 {
     // check if $modification->new_value is json string and record set diff
     // @see 0008546: When edit event, history show "code" ...
     if (Tinebase_Helper::is_json($modification->new_value)) {
         $newValueArray = Zend_Json::decode($modification->new_value);
         if ((isset($newValueArray['model']) || array_key_exists('model', $newValueArray)) && (isset($newValueArray['added']) || array_key_exists('added', $newValueArray))) {
             $diff = new Tinebase_Record_RecordSetDiff($newValueArray);
             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' fetching translated text for diff: ' . print_r($diff->toArray(), true));
             }
             return $diff->getTranslatedDiffText();
         }
     }
     return $modification->old_value . ' -> ' . $modification->new_value;
 }