Merge branch '2015.11' into 2015.11-develop
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 7 Apr 2016 07:42:45 +0000 (09:42 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 7 Apr 2016 07:42:45 +0000 (09:42 +0200)
332 files changed:
tests/tine20/Addressbook/Export/DocTest.php
tests/tine20/Addressbook/Frontend/WebDAV/ContactTest.php
tests/tine20/Addressbook/JsonTest.php
tests/tine20/AllTests.php
tests/tine20/Calendar/AllTests.php
tests/tine20/Calendar/Controller/EventNotificationsTests.php
tests/tine20/Calendar/Controller/EventTests.php
tests/tine20/Calendar/Controller/RecurTest.php
tests/tine20/Calendar/Export/DocTest.php [new file with mode: 0644]
tests/tine20/Calendar/JsonTests.php
tests/tine20/CoreData/AllTests.php [new file with mode: 0644]
tests/tine20/CoreData/JsonTest.php [new file with mode: 0644]
tests/tine20/Crm/ControllerTest.php
tests/tine20/Crm/Export/PdfTest.php
tests/tine20/Crm/JsonTest.php
tests/tine20/Crm/NotificationsTests.php
tests/tine20/Felamimail/Frontend/JsonTest.php
tests/tine20/Felamimail/files/openpgpencrypted.eml [new file with mode: 0644]
tests/tine20/Filemanager/Frontend/JsonTests.php
tests/tine20/Inventory/JsonTest.php
tests/tine20/Projects/JsonTest.php
tests/tine20/Sales/CustomersTest.php
tests/tine20/Sales/InvoiceControllerTests.php
tests/tine20/Sales/InvoiceJsonTests.php
tests/tine20/Sales/InvoiceTestCase.php
tests/tine20/Sales/JsonTest.php
tests/tine20/Sales/PurchaseInvoiceTest.php
tests/tine20/TestCase.php
tests/tine20/Timetracker/FilterTest.php
tests/tine20/Timetracker/JsonTest.php
tests/tine20/Tinebase/AllTests.php
tests/tine20/Tinebase/Frontend/Json/PersistentFilterTest.php
tests/tine20/Tinebase/Record/AllTests.php [new file with mode: 0644]
tests/tine20/Tinebase/Record/AutoRecord.php [deleted file]
tests/tine20/Tinebase/Record/ContainerTest.php [deleted file]
tests/tine20/Tinebase/Record/PathTest.php [new file with mode: 0644]
tests/tine20/Tinebase/Record/PersistentObserverTest.php
tests/tine20/Tinebase/Relation/Backend/SqlTest.php
tests/tine20/Tinebase/Relation/RelationTest.php
tine20/Addressbook/Acl/Rights.php
tine20/Addressbook/Addressbook.jsb2
tine20/Addressbook/Config.php
tine20/Addressbook/Controller.php
tine20/Addressbook/Controller/Contact.php
tine20/Addressbook/Controller/List.php
tine20/Addressbook/Controller/ListRole.php [new file with mode: 0644]
tine20/Addressbook/Convert/Contact/Json.php
tine20/Addressbook/Convert/Contact/VCard/Abstract.php
tine20/Addressbook/Convert/List/Json.php [new file with mode: 0644]
tine20/Addressbook/Event/ChangeList.php [new file with mode: 0644]
tine20/Addressbook/Event/DeleteList.php [new file with mode: 0644]
tine20/Addressbook/Export/Doc.php
tine20/Addressbook/Export/definitions/adb_default_doc.xml
tine20/Addressbook/Export/templates/addressbook_contact_letter.docx
tine20/Addressbook/Frontend/Json.php
tine20/Addressbook/Model/Contact.php
tine20/Addressbook/Model/ContactFilter.php
tine20/Addressbook/Model/List.php
tine20/Addressbook/Model/ListFilter.php
tine20/Addressbook/Model/ListMemberRole.php [new file with mode: 0644]
tine20/Addressbook/Model/ListRole.php [new file with mode: 0644]
tine20/Addressbook/Model/ListRoleFilter.php [new file with mode: 0644]
tine20/Addressbook/Model/ListRoleMemberFilter.php [new file with mode: 0644]
tine20/Addressbook/Setup/Initialize.php
tine20/Addressbook/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Addressbook/Setup/setup.xml
tine20/Addressbook/css/Addressbook.css
tine20/Addressbook/js/Addressbook.js
tine20/Addressbook/js/AdminPanel.js [new file with mode: 0644]
tine20/Addressbook/js/ContactEditDialog.js
tine20/Addressbook/js/ContactGrid.js
tine20/Addressbook/js/ContactSearchCombo.js [moved from tine20/Addressbook/js/SearchCombo.js with 82% similarity]
tine20/Addressbook/js/ListEditDialog.js
tine20/Addressbook/js/ListGrid.js
tine20/Addressbook/js/ListMemberGridPanel.js [deleted file]
tine20/Addressbook/js/ListMemberRoleGridPanel.js [new file with mode: 0644]
tine20/Addressbook/js/ListMemberRoleLayerCombo.js [new file with mode: 0644]
tine20/Addressbook/js/ListRoleEditDialog.js [new file with mode: 0644]
tine20/Addressbook/js/ListRoleGridPanel.js [new file with mode: 0644]
tine20/Addressbook/js/ListRoleMemberFilterModel.js [new file with mode: 0644]
tine20/Addressbook/js/ListSearchCombo.js [new file with mode: 0644]
tine20/Addressbook/js/Model.js
tine20/Addressbook/translations/de.po
tine20/Admin/Frontend/Cli.php
tine20/Admin/js/Admin.js
tine20/Admin/js/config/FieldManager.js
tine20/Admin/js/container/EditDialog.js
tine20/Admin/js/container/GridPanel.js
tine20/Admin/js/customfield/EditDialog.js
tine20/Calendar/Backend/Sql.php
tine20/Calendar/Calendar.jsb2
tine20/Calendar/Config.php
tine20/Calendar/Controller.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Convert/Event/Json.php
tine20/Calendar/Export/DocSheet.php [new file with mode: 0644]
tine20/Calendar/Export/GenericTrait.php [moved from tine20/Calendar/Export/Abstract.php with 81% similarity]
tine20/Calendar/Export/Ods.php
tine20/Calendar/Export/definitions/cal_default_doc_sheet.xml [new file with mode: 0644]
tine20/Calendar/Export/definitions/cal_default_ods.xml
tine20/Calendar/Export/templates/SimpleDocSheet.docx [new file with mode: 0644]
tine20/Calendar/Frontend/Http.php
tine20/Calendar/Model/Event.php
tine20/Calendar/Model/EventFilter.php
tine20/Calendar/Model/PeriodFilter.php
tine20/Calendar/Model/Resource.php
tine20/Calendar/Model/Rrule.php
tine20/Calendar/Preference.php
tine20/Calendar/Scheduler/Task.php [new file with mode: 0644]
tine20/Calendar/Setup/Initialize.php
tine20/Calendar/Setup/Update/Release9.php
tine20/Calendar/Setup/setup.xml
tine20/Calendar/css/Calendar.css
tine20/Calendar/css/daysviewpanel.css
tine20/Calendar/css/print.css
tine20/Calendar/js/AttendeeGridPanel.js
tine20/Calendar/js/Calendar.js
tine20/Calendar/js/CalendarPanelSplitPlugin.js
tine20/Calendar/js/DaysView.js
tine20/Calendar/js/DaysViewEventUI.js [new file with mode: 0644]
tine20/Calendar/js/EventDetailsPanel.js
tine20/Calendar/js/EventEditDialog.js
tine20/Calendar/js/EventUI.js
tine20/Calendar/js/MainScreenCenterPanel.js
tine20/Calendar/js/Model.js
tine20/Calendar/js/MonthViewEventUI.js [new file with mode: 0644]
tine20/Calendar/js/PerspectiveCombo.js
tine20/Calendar/js/Printer/DaysView.js
tine20/Calendar/js/Printer/EventRecord.js [new file with mode: 0644]
tine20/Calendar/js/ResourceEditDialog.js
tine20/Calendar/js/ResourcesGridPanel.js
tine20/Calendar/js/RrulePanel.js
tine20/Calendar/translations/de.po
tine20/CoreData/Config.php [new file with mode: 0644]
tine20/CoreData/Controller.php [new file with mode: 0644]
tine20/CoreData/CoreData.jsb2 [new file with mode: 0644]
tine20/CoreData/Exception.php [new file with mode: 0644]
tine20/CoreData/Frontend/Cli.php [new file with mode: 0644]
tine20/CoreData/Frontend/Http.php [new file with mode: 0644]
tine20/CoreData/Frontend/Json.php [new file with mode: 0644]
tine20/CoreData/Model/CoreData.php [new file with mode: 0644]
tine20/CoreData/Preference.php [new file with mode: 0644]
tine20/CoreData/Setup/Initialize.php [new file with mode: 0644]
tine20/CoreData/Setup/setup.xml [new file with mode: 0644]
tine20/CoreData/css/CoreData.css [new file with mode: 0644]
tine20/CoreData/js/CoreData.js [new file with mode: 0644]
tine20/CoreData/js/Manager.js [new file with mode: 0644]
tine20/CoreData/js/TreePanel.js [new file with mode: 0644]
tine20/CoreData/js/WestPanel.js [new file with mode: 0644]
tine20/CoreData/translations/bg.po [new file with mode: 0644]
tine20/CoreData/translations/ca.po [new file with mode: 0644]
tine20/CoreData/translations/cs.po [new file with mode: 0644]
tine20/CoreData/translations/da.po [new file with mode: 0644]
tine20/CoreData/translations/de.po [new file with mode: 0644]
tine20/CoreData/translations/el_GR.po [new file with mode: 0644]
tine20/CoreData/translations/en.po [new file with mode: 0644]
tine20/CoreData/translations/en_AU.po [new file with mode: 0644]
tine20/CoreData/translations/en_NZ.po [new file with mode: 0644]
tine20/CoreData/translations/es.po [new file with mode: 0644]
tine20/CoreData/translations/es_MX.po [new file with mode: 0644]
tine20/CoreData/translations/et.po [new file with mode: 0644]
tine20/CoreData/translations/fa_IR.po [new file with mode: 0644]
tine20/CoreData/translations/fi.po [new file with mode: 0644]
tine20/CoreData/translations/fr.po [new file with mode: 0644]
tine20/CoreData/translations/hr_HR.po [new file with mode: 0644]
tine20/CoreData/translations/hu.po [new file with mode: 0644]
tine20/CoreData/translations/it.po [new file with mode: 0644]
tine20/CoreData/translations/ja.po [new file with mode: 0644]
tine20/CoreData/translations/ko.po [new file with mode: 0644]
tine20/CoreData/translations/ko_KR.po [new file with mode: 0644]
tine20/CoreData/translations/lt.po [new file with mode: 0644]
tine20/CoreData/translations/nb.po [new file with mode: 0644]
tine20/CoreData/translations/nl_NL.po [new file with mode: 0644]
tine20/CoreData/translations/pl.po [new file with mode: 0644]
tine20/CoreData/translations/pt_BR.po [new file with mode: 0644]
tine20/CoreData/translations/ro_RO.po [new file with mode: 0644]
tine20/CoreData/translations/ru.po [new file with mode: 0644]
tine20/CoreData/translations/sk.po [new file with mode: 0644]
tine20/CoreData/translations/sl.po [new file with mode: 0644]
tine20/CoreData/translations/template.pot [new file with mode: 0644]
tine20/Crm/Backend/Lead.php
tine20/Crm/Config.php
tine20/Crm/Controller.php
tine20/Crm/Controller/Lead.php
tine20/Crm/Crm.jsb2
tine20/Crm/Export/Csv.php
tine20/Crm/Export/Helper.php
tine20/Crm/Export/Pdf.php
tine20/Crm/Frontend/Json.php
tine20/Crm/Import/Csv.php
tine20/Crm/Model/Config.php [deleted file]
tine20/Crm/Model/Lead.php
tine20/Crm/Model/LeadSource.php [new file with mode: 0644]
tine20/Crm/Model/LeadState.php [new file with mode: 0644]
tine20/Crm/Setup/Initialize.php
tine20/Crm/Setup/Update/Release8.php
tine20/Crm/Setup/Update/Release9.php [new file with mode: 0644]
tine20/Crm/Setup/setup.xml
tine20/Crm/js/AdminPanel.js
tine20/Crm/js/Crm.js
tine20/Crm/js/LeadEditDialog.js
tine20/Crm/js/LeadGridDetailsPanel.js
tine20/Crm/js/LeadGridPanel.js
tine20/Crm/js/LeadSource.js [deleted file]
tine20/Crm/js/LeadSourceFilterModel.js [deleted file]
tine20/Crm/js/LeadState.js [deleted file]
tine20/Crm/js/LeadStateFilterModel.js [deleted file]
tine20/Crm/js/LeadType.js [deleted file]
tine20/Crm/js/Model.js
tine20/ExampleApplication/Frontend/Cli.php
tine20/Expressomail/Expressomail.jsb2
tine20/Felamimail/Controller/Message.php
tine20/Felamimail/Controller/Message/Send.php
tine20/Felamimail/Felamimail.jsb2
tine20/Felamimail/Model/Message.php
tine20/Felamimail/css/Felamimail.css
tine20/Felamimail/js/GridDetailsPanel.js
tine20/Felamimail/js/GridPanel.js
tine20/Felamimail/js/MailvelopeHelper.js [new file with mode: 0644]
tine20/Felamimail/js/MessageEditDialog.js
tine20/Felamimail/js/PGPDetailsPanel.js [new file with mode: 0644]
tine20/Inventory/Controller/InventoryItem.php
tine20/Inventory/Model/InventoryItem.php
tine20/Inventory/js/InventoryItemEditDialog.js
tine20/Projects/Model/Project.php
tine20/Projects/js/Model.js
tine20/Projects/js/ProjectEditDialog.js
tine20/Sales/Controller/Invoice.php
tine20/Sales/Controller/PurchaseInvoice.php
tine20/Sales/Frontend/Cli.php
tine20/Sales/Frontend/Json.php
tine20/Sales/Model/PurchaseInvoice.php
tine20/Sales/Setup/DemoData.php
tine20/Sales/Setup/Update/Release2.php
tine20/Sales/js/InvoiceEditDialog.js
tine20/Setup/Backend/Mysql.php
tine20/Setup/Backend/Schema/Index/Mysql.php
tine20/Setup/Backend/Schema/Table/Factory.php
tine20/Setup/Controller.php
tine20/Setup/js/MainScreen.js
tine20/Setup/js/Setup.js
tine20/Setup/js/init.js
tine20/Timetracker/Setup/DemoData.php
tine20/Timetracker/Setup/Import/Egw14.php
tine20/Tinebase/ActionQueue.php
tine20/Tinebase/Config.php
tine20/Tinebase/Config/Abstract.php
tine20/Tinebase/Config/KeyField.php
tine20/Tinebase/Container.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Controller/Record/Abstract.php
tine20/Tinebase/Controller/Record/Grants.php
tine20/Tinebase/Convert/Json.php
tine20/Tinebase/Core.php
tine20/Tinebase/Export/Abstract.php
tine20/Tinebase/Export/Richtext/Doc.php
tine20/Tinebase/Frontend/Cli.php
tine20/Tinebase/Frontend/Http.php
tine20/Tinebase/ImageHelper.php
tine20/Tinebase/Mail.php
tine20/Tinebase/Model/Container.php
tine20/Tinebase/Model/Filter/Path.php [new file with mode: 0644]
tine20/Tinebase/Model/Image.php
tine20/Tinebase/Model/Path.php [new file with mode: 0644]
tine20/Tinebase/Model/PathFilter.php [new file with mode: 0644]
tine20/Tinebase/Model/Relation.php
tine20/Tinebase/Model/RelationFilter.php
tine20/Tinebase/Notification/Backend/Smtp.php
tine20/Tinebase/Path/Backend/Sql.php [new file with mode: 0644]
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Record/Iterator.php
tine20/Tinebase/Record/Path.php [new file with mode: 0644]
tine20/Tinebase/Record/RecordSet.php
tine20/Tinebase/Record/Simple.php [new file with mode: 0644]
tine20/Tinebase/Relation/Backend/Sql.php
tine20/Tinebase/Relations.php
tine20/Tinebase/Setup/DemoData/Abstract.php
tine20/Tinebase/Setup/Update/Release0.php
tine20/Tinebase/Setup/Update/Release5.php
tine20/Tinebase/Setup/Update/Release8.php
tine20/Tinebase/Setup/Update/Release9.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Tinebase.jsb2
tine20/Tinebase/User/Abstract.php
tine20/Tinebase/css/SmallForms.css
tine20/Tinebase/css/Tinebase.css
tine20/Tinebase/js/AppManager.js
tine20/Tinebase/js/AppTabsPanel.js
tine20/Tinebase/js/Application.js
tine20/Tinebase/js/ApplicationStarter.js
tine20/Tinebase/js/MainScreen.js
tine20/Tinebase/js/Models.js
tine20/Tinebase/js/common.js
tine20/Tinebase/js/data/RecordProxy.js
tine20/Tinebase/js/extFixes.js
tine20/Tinebase/js/ux/Date.js [new file with mode: 0644]
tine20/Tinebase/js/ux/Printer/Printer.js
tine20/Tinebase/js/ux/Printer/renderers/Base.js
tine20/Tinebase/js/ux/layout/cardLayoutHelper.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/ContentTypeTreePanel.js
tine20/Tinebase/js/widgets/MainScreen.js
tine20/Tinebase/js/widgets/container/ContainerSelect.js
tine20/Tinebase/js/widgets/container/FilterModel.js
tine20/Tinebase/js/widgets/container/GrantsGrid.js
tine20/Tinebase/js/widgets/customfields/Renderer.js
tine20/Tinebase/js/widgets/dialog/AddRelationsEditDialogPlugin.js
tine20/Tinebase/js/widgets/dialog/EditDialog.js
tine20/Tinebase/js/widgets/dialog/MultiOptionsDialog.js
tine20/Tinebase/js/widgets/dialog/MultipleEditDialogPlugin.js
tine20/Tinebase/js/widgets/dialog/SimpleRecordEditDialog.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/form/RecordPickerComboBox.js
tine20/Tinebase/js/widgets/grid/DetailsPanel.js
tine20/Tinebase/js/widgets/grid/FilterPanel.js
tine20/Tinebase/js/widgets/grid/FilterToolbar.js
tine20/Tinebase/js/widgets/grid/GridPanel.js
tine20/Tinebase/js/widgets/grid/PickerGridLayerCombo.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/grid/PickerGridPanel.js
tine20/Tinebase/js/widgets/keyfield/ConfigGrid.js
tine20/Tinebase/js/widgets/keyfield/Store.js
tine20/Tinebase/js/widgets/path/renderer.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/relation/GenericPickerGridPanel.js
tine20/Tinebase/js/widgets/relation/PickerCombo.js
tine20/Tinebase/js/widgets/tags/TagCombo.js
tine20/Tinebase/js/widgets/tags/TagsMassAttachAction.js
tine20/Tinebase/js/widgets/tree/FilterPlugin.js
tine20/Tinebase/translations/de.po
tine20/Voipmanager/js/MainScreen.js
tine20/Voipmanager/js/Voipmanager.js
tine20/images/list.png [new file with mode: 0644]
tine20/library/addressparser.js/addressparser.js [new file with mode: 0644]
tine20/library/es6-promise/es6-promise.js [new file with mode: 0644]

index cfca71e..fca918c 100644 (file)
@@ -29,11 +29,16 @@ class Addressbook_Export_DocTest extends TestCase
         //  class name in /usr/local/share/tine20.git/tine20/vendor/phpoffice/phpword/src/PhpWord/TemplateProcessor.php
         //  on line 23
         if (PHP_VERSION_ID >= 70000) {
-            $this->markTestSkipped('FIXME in php7');
+            $this->markTestSkipped('FIXME 0011730: fix doc export for php7');
         }
 
+        // make sure definition is imported
+        $definitionFile = __DIR__ . '/../../../../tine20/Addressbook/Export/definitions/adb_default_doc.xml';
+        $app = Tinebase_Application::getInstance()->getApplicationByName('Addressbook');
+        Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($definitionFile, $app);
+
         $filter = new Addressbook_Model_ContactFilter(array(
-            array('field' => 'n_given', 'operator' => 'equals', 'value' => 'Robert')
+            array('field' => 'n_given', 'operator' => 'equals', 'value' => 'James')
         ));
         $doc = new Addressbook_Export_Doc($filter);
         $doc->generate();
@@ -43,4 +48,18 @@ class Addressbook_Export_DocTest extends TestCase
 
         $this->assertGreaterThan(0, filesize($tempfile));
     }
-}
\ No newline at end of file
+
+    // read and write sucks
+    public function _testReadWriteCycleSucks()
+    {
+        PhpWord\Settings::setTempDir(Tinebase_Core::getTempDir());
+
+        $source = str_replace('tests/tine20', 'tine20', __DIR__) . '/templates/addressbook_contact_letter.docx';
+        $phpWord = PhpWord\IOFactory::load($source);
+
+        $tempfile = tempnam(Tinebase_Core::getTempDir(), __METHOD__ . '_') . '.docx';
+        $writer = $phpWord->save($tempfile);
+
+        `open $tempfile`;
+    }
+}
index 17d5a8b..c203823 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
@@ -105,8 +105,15 @@ class Addressbook_Frontend_WebDAV_ContactTest extends PHPUnit_Framework_TestCase
         $record = $contact->getRecord();
 
         $imgBlob = $record->getSmallContactImage();
-        $this->assertTrue(strlen($imgBlob) > 0);
-        $this->assertTrue(strlen($imgBlob) < Addressbook_Model_Contact::SMALL_PHOTO_SIZE);
+        $standardSize = strlen($imgBlob);
+        $this->assertTrue($standardSize > 0);
+        $this->assertTrue($standardSize < Addressbook_Model_Contact::SMALL_PHOTO_SIZE);
+
+        // test custom size
+        $imgBlob = $record->getSmallContactImage(Addressbook_Model_Contact::SMALL_PHOTO_SIZE / 8);
+        $this->assertTrue(strlen($imgBlob) < $standardSize, 'custom size error');
+
+        return $contact;
     }
 
     /**
index 2716db2..fc3bccf 100644 (file)
@@ -27,7 +27,7 @@ class Addressbook_JsonTest extends TestCase
      *
      * @var Addressbook_Frontend_Json
      */
-    protected $_instance;
+    protected $_uit;
 
     /**
      * contacts that should be deleted later
@@ -70,18 +70,6 @@ class Addressbook_JsonTest extends TestCase
     protected $_groupIdsToDelete = NULL;
     
     protected $_originalRoleRights = null;
-    
-    /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook Json Tests');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
 
     /**
      * Sets up the fixture.
@@ -96,7 +84,7 @@ class Addressbook_JsonTest extends TestCase
         // always resolve customfields
         Addressbook_Controller_Contact::getInstance()->resolveCustomfields(TRUE);
         
-        $this->_instance = new Addressbook_Frontend_Json();
+        $this->_uit = new Addressbook_Frontend_Json();
         
         $personalContainer = Tinebase_Container::getInstance()->getPersonalContainer(
             Zend_Registry::get('currentAccount'),
@@ -118,6 +106,8 @@ class Addressbook_JsonTest extends TestCase
             'sort' => 'n_fileas',
             'dir' => 'ASC',
         );
+
+        parent::setUp();
     }
 
     /**
@@ -130,7 +120,7 @@ class Addressbook_JsonTest extends TestCase
     {
         Addressbook_Controller_Contact::getInstance()->setGeoDataForContacts($this->_geodata);
         
-        $this->_instance->deleteContacts($this->_contactIdsToDelete);
+        $this->_uit->deleteContacts($this->_contactIdsToDelete);
 
         foreach ($this->_customfieldIdsToDelete as $cfd) {
             Tinebase_CustomField::getInstance()->deleteCustomField($cfd);
@@ -156,6 +146,8 @@ class Addressbook_JsonTest extends TestCase
         }
         
         $this->_resetOriginalRoleRights();
+
+        parent::tearDown();
     }
     
     protected function _resetOriginalRoleRights()
@@ -179,7 +171,7 @@ class Addressbook_JsonTest extends TestCase
         $filter = array(
             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'all'),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
@@ -195,7 +187,7 @@ class Addressbook_JsonTest extends TestCase
         $filter = array(
             array('field' => 'list', 'operator' => 'equals',   'value' => $adminListId),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThan(0, $contacts['totalcount']);
         // check if user in admin list
@@ -232,7 +224,7 @@ class Addressbook_JsonTest extends TestCase
                         "label": "Kontakte"
                     }
                 ]';
-        $contacts = $this->_instance->searchContacts(Zend_Json::decode($filter), NULL);
+        $contacts = $this->_uit->searchContacts(Zend_Json::decode($filter), NULL);
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
 
@@ -249,7 +241,7 @@ class Addressbook_JsonTest extends TestCase
             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'all'),
         );
     
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
     
@@ -264,7 +256,7 @@ class Addressbook_JsonTest extends TestCase
         $filter = array(
             array('field' => 'container_id', 'operator' => 'equals',   'value' => ''),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
@@ -280,7 +272,7 @@ class Addressbook_JsonTest extends TestCase
         $filter = array(
             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'otherUsers'),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThanOrEqual(0, $contacts['totalcount'], 'getting other peoples contacts failed');
     }
@@ -298,7 +290,7 @@ class Addressbook_JsonTest extends TestCase
         $filter = array(
             array('field' => 'telephone', 'operator' => 'contains', 'value' => '+49TELCELLPRIVATE')
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
         $this->assertEquals(1, $contacts['totalcount']);
     }
 
@@ -316,7 +308,7 @@ class Addressbook_JsonTest extends TestCase
         if ($_tags !== NULL) {
             $newContactData['tags'] = $_tags;
         }
-        $newContact = $this->_instance->saveContact($newContactData, $_forceCreation);
+        $newContact = $this->_uit->saveContact($newContactData, $_forceCreation);
         $this->assertEquals($newContactData['n_family'], $newContact['n_family'], 'Adding contact failed');
 
         $this->_contactIdsToDelete[] = $newContact['id'];
@@ -359,9 +351,17 @@ class Addressbook_JsonTest extends TestCase
         $changes = array(
             array('name' => 'url',                    'value' => "http://www.phpunit.de"),
             array('name' => 'adr_one_region',         'value' => 'PHPUNIT_multipleUpdate'),
-            array('name' => 'customfield_' . $createdCustomField->name, 'value' => 'PHPUNIT_multipleUpdate' )
+            array('name' => 'customfield_' . $createdCustomField->name, 'value' => 'PHPUNIT_multipleUpdate' ),
+            array('name' => '%add', 'value' => json_encode(array(
+                'own_model'         => 'Addressbook_Model_Contact',
+                'own_backend'       => 'Sql',
+                'related_degree'    => 'parent',
+                'related_model'     => 'Addressbook_Model_Contact',
+                'related_backend'   => 'Sql',
+                'related_id'        => Tinebase_Core::getUser()->contact_id,
+                'remark'            => 'some remark'
+            ))),
         );
-
         foreach($companies as $company) {
             $contact = $this->_addContact($company);
             $contactIds[] = $contact['id'];
@@ -377,7 +377,7 @@ class Addressbook_JsonTest extends TestCase
 
         // check if default field adr_one_region value was found
         $sFilter = array(array('field' => 'adr_one_region','operator' => 'equals', 'value' => 'PHPUNIT_multipleUpdate'));
-        $searchResult = $this->_instance->searchContacts($sFilter,$this->objects['paging']);
+        $searchResult = $this->_uit->searchContacts($sFilter,$this->objects['paging']);
 
         // look if all 3 contacts are found again by default field, and check if default field got properly updated
         $this->assertEquals(3, $searchResult['totalcount'],'Could not find the correct number of records by adr_one_region');
@@ -392,8 +392,14 @@ class Addressbook_JsonTest extends TestCase
         $this->assertEquals($record['url'],'http://www.phpunit.de','DefaultField "url" was not updated as expected');
         
         // check 'changed' systemnote
-        $this->_checkChangedNote($record['id'], 'adr_one_region ( -> PHPUNIT_multipleUpdate) url ( -> http://www.phpunit.de) customfields ( -> {');
-        
+        $this->_checkChangedNote($record['id'], 'adr_one_region ( -> PHPUNIT_multipleUpdate) url ( -> http://www.phpunit.de) relations (1 hinzugefügt) customfields ( -> {');
+
+        // check relation
+        $fullRecord = $this->_uit->getContact($record['id']);
+        $this->assertEquals(1, count($fullRecord['relations']), 'relation got not added');
+        $this->assertEquals('some remark', $fullRecord['relations'][0]['remark']);
+        $this->assertEquals('parent', $fullRecord['relations'][0]['related_degree']);
+
         // check invalid data
         $changes = array(
             array('name' => 'type', 'value' => 'Z'),
@@ -432,7 +438,7 @@ class Addressbook_JsonTest extends TestCase
         $cf = $this->_createCustomfield();
         $contact = $this->_addContact();
         $contact['customfields'][$cf->name] = 'changed value';
-        $result = $this->_instance->saveContact($contact);
+        $result = $this->_uit->saveContact($contact);
         
         $this->assertEquals('changed value', $result['customfields'][$cf->name]);
         $this->_checkChangedNote($result['id'], ' -> {"' . $cf->name . '":"changed value"})');
@@ -485,7 +491,7 @@ class Addressbook_JsonTest extends TestCase
         );
         $contact['tags'] = array($tag);
         
-        $result = $this->_instance->saveContact($contact);
+        $result = $this->_uit->saveContact($contact);
         
         $this->assertEquals($tagName, $result['tags'][0]['name']);
         $this->_checkChangedNote($result['id'], array(
@@ -521,7 +527,7 @@ class Addressbook_JsonTest extends TestCase
         $contact = $this->testTagsModlog();
         $contact['tags'] = array();
         sleep(1); // make sure that the second change always gets last when fetching notes
-        $result = $this->_instance->saveContact($contact);
+        $result = $this->_uit->saveContact($contact);
         $this->_checkChangedNote($result['id'], array(
             'tags',
             '1 ' . Tinebase_Translation::getTranslation('Tinebase')->_('removed'),
@@ -555,7 +561,7 @@ class Addressbook_JsonTest extends TestCase
         $contact = $this->testAttachMultipleTagsModlog(Tinebase_Model_Tag::TYPE_PERSONAL);
         $this->_originalRoleRights = $this->_removeRoleRight('Addressbook', Tinebase_Acl_Rights::USE_PERSONAL_TAGS);
         
-        $contact = $this->_instance->getContact($contact['id']);
+        $contact = $this->_uit->getContact($contact['id']);
         
         $this->assertTrue(! isset($contact['tags']) || count($contact['tags'] === 0), 'record should not have any tags');
     }
@@ -574,7 +580,7 @@ class Addressbook_JsonTest extends TestCase
             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'singleContainer'),
             array('field' => 'container', 'operator' => 'equals',   'value' => $this->container->id),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
@@ -593,7 +599,7 @@ class Addressbook_JsonTest extends TestCase
             array('field' => 'containerType', 'operator' => 'equals',   'value' => 'personal'),
             array('field' => 'owner',  'operator' => 'equals',   'value' => Zend_Registry::get('currentAccount')->getId()),
         );
-        $contacts = $this->_instance->searchContacts($filter, $paging);
+        $contacts = $this->_uit->searchContacts($filter, $paging);
 
         $this->assertGreaterThan(0, $contacts['totalcount']);
     }
@@ -606,7 +612,7 @@ class Addressbook_JsonTest extends TestCase
     {
         $contact = $this->_addContact();
 
-        $contact = $this->_instance->getContact($contact['id']);
+        $contact = $this->_uit->getContact($contact['id']);
 
         $this->assertEquals('PHPUNIT', $contact['n_family'], 'getting contact failed');
     }
@@ -623,7 +629,7 @@ class Addressbook_JsonTest extends TestCase
         $contact['n_family'] = 'PHPUNIT UPDATE';
         $contact['adr_one_locality'] = 'Hamburg';
         $contact['adr_one_street'] = 'Pickhuben 2';
-        $updatedContact = $this->_instance->saveContact($contact);
+        $updatedContact = $this->_uit->saveContact($contact);
 
         $this->assertEquals($contact['id'], $updatedContact['id'], 'updated produced a new contact');
         $this->assertEquals('PHPUNIT UPDATE', $updatedContact['n_family'], 'updating data failed');
@@ -638,7 +644,7 @@ class Addressbook_JsonTest extends TestCase
             $updatedContact['adr_one_street']      = 'Blindengasse 52';
             $updatedContact['adr_one_postalcode']  = '1095';
             $updatedContact['adr_one_countryname'] = '';
-            $updatedContact = $this->_instance->saveContact($updatedContact);
+            $updatedContact = $this->_uit->saveContact($updatedContact);
 
             // check geo data
             $this->assertEquals(16,   round($updatedContact['adr_one_lon']), 'wrong geodata (lon): ' . $updatedContact['adr_one_lon']);
@@ -655,10 +661,10 @@ class Addressbook_JsonTest extends TestCase
     {
         $contact = $this->_addContact();
 
-        $this->_instance->deleteContacts($contact['id']);
+        $this->_uit->deleteContacts($contact['id']);
 
         $this->setExpectedException('Tinebase_Exception_NotFound');
-        $contact = $this->_instance->getContact($contact['id']);
+        $contact = $this->_uit->getContact($contact['id']);
     }
 
     /**
@@ -864,9 +870,13 @@ class Addressbook_JsonTest extends TestCase
      * test import
      * 
      * @see 0006226: Data truncated for column 'adr_two_lon'
+     *
+     * TODO move import test to separate test class
      */
     public function testImport()
     {
+        $this->_testNeedsTransaction();
+
         $result = $this->_importHelper();
         $this->assertEquals(2, $result['totalcount'], 'dryrun should detect 2 for import.' . print_r($result, TRUE));
         $this->assertEquals(0, $result['failcount'], 'Import failed for one or more records.');
@@ -904,6 +914,8 @@ class Addressbook_JsonTest extends TestCase
     */
     public function testImportWithResolveStrategyDiscard()
     {
+        $this->_testNeedsTransaction();
+
         $result = $this->_importHelper(array('dryrun' => 0));
         $fritz = $result['results'][1];
 
@@ -925,6 +937,8 @@ class Addressbook_JsonTest extends TestCase
     */
     public function testImportWithResolveStrategyMergeTheirs()
     {
+        $this->_testNeedsTransaction();
+
         $result = $this->_importHelper(array('dryrun' => 0));
         $this->assertEquals(2, count($result['results']), 'no import results');
         $fritz = $result['results'][1];
@@ -977,7 +991,7 @@ class Addressbook_JsonTest extends TestCase
         $options = array_merge($additionalOptions, array(
             'container_id'  => $this->container->getId(),
         ));
-        $result = $this->_instance->importContacts($tempFile->getId(), $definition->getId(), $options, $clientRecords);
+        $result = $this->_uit->importContacts($tempFile->getId(), $definition->getId(), $options, $clientRecords);
         if (isset($additionalOptions['dryrun']) && $additionalOptions['dryrun'] === 0) {
             foreach ($result['results'] as $contact) {
                 $this->_contactIdsToDelete[] = $contact['id'];
@@ -992,6 +1006,8 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportWithTags()
     {
+        $this->_testNeedsTransaction();
+
         $options = array(
             'dryrun'     => 0,
             'autotags'   => array(array(
@@ -1013,7 +1029,7 @@ class Addressbook_JsonTest extends TestCase
             'name'    => 'supi',
             'type'    => Tinebase_Model_Tag::TYPE_PERSONAL,
         ));
-        $fritz = $this->_instance->saveContact($fritz);
+        $fritz = $this->_uit->saveContact($fritz);
         //print_r($fritz);
         
         // once again for duplicates (check if client record has tag)
@@ -1044,6 +1060,8 @@ class Addressbook_JsonTest extends TestCase
     */
     public function testImportWithExistingTag()
     {
+        $this->_testNeedsTransaction();
+
         $tag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
         $tag = Tinebase_Tags::getInstance()->create($tag);
         
@@ -1062,6 +1080,8 @@ class Addressbook_JsonTest extends TestCase
     */
     public function testImportWithNewTag()
     {
+        $this->_testNeedsTransaction();
+
         $tag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
         
         $options = array(
@@ -1081,6 +1101,8 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportKeepExistingWithTag()
     {
+        $this->_testNeedsTransaction();
+
         $klaus = $this->_tagImportHelper('discard');
         $this->assertEquals(2, count($klaus['tags']), 'klaus should have both tags: ' . print_r($klaus['tags'], TRUE));
     }
@@ -1091,6 +1113,8 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportMergeTheirsWithTag()
     {
+        $this->_testNeedsTransaction();
+
         $result = $this->_importHelper(array('dryrun' => 0));
         $this->assertTrue(count($result['results']) > 0, 'no record were imported');
         $klaus = $result['results'][0];
@@ -1112,7 +1136,7 @@ class Addressbook_JsonTest extends TestCase
         $result = $this->_importHelper($options, $clientRecords);
         $this->assertEquals(2, count($result['results'][0]['tags']), 'klaus should have both tags: ' . print_r($result['results'][0], TRUE));
         
-        $klaus = $this->_instance->getContact($klaus['id']);
+        $klaus = $this->_uit->getContact($klaus['id']);
         $this->assertEquals(2, count($klaus['tags']), 'klaus should have both tags: ' . print_r($klaus, TRUE));
         $this->assertEquals('12345', $klaus['adr_one_postalcode']);
     }
@@ -1152,7 +1176,7 @@ class Addressbook_JsonTest extends TestCase
         $this->assertEquals($expectedTotalcount, $result['totalcount'], 'Should discard fritz');
         $this->assertEquals(1, $result['duplicatecount'], 'fritz should still be a duplicate');
         
-        $klaus = $this->_instance->getContact($klausId);
+        $klaus = $this->_uit->getContact($klausId);
         
         return $klaus;
     }
@@ -1164,6 +1188,8 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportKeepBothWithTag()
     {
+        $this->_testNeedsTransaction();
+
         $klaus = $this->_tagImportHelper('keep');
         $this->assertEquals(1, count($klaus['tags']), 'klaus should have only one tag: ' . print_r($klaus['tags'], TRUE));
     }
@@ -1176,6 +1202,8 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportTagWithLongName()
     {
+        $this->_testNeedsTransaction();
+
         // import records with long tag name
         $result = $this->_importHelper(array('dryrun' => 0), array(), dirname(__FILE__) . '/Import/files/adb_tine_import_with_tag.csv');
         
@@ -1203,7 +1231,7 @@ class Addressbook_JsonTest extends TestCase
             $this->markTestSkipped('Projects not installed.');
         }
         
-        $contact = $this->_instance->saveContact($this->_getContactData());
+        $contact = $this->_uit->saveContact($this->_getContactData());
         $project = $this->_getProjectData($contact);
 
         $projectJson = new Projects_Frontend_Json();
@@ -1232,7 +1260,7 @@ class Addressbook_JsonTest extends TestCase
                 'own_model'              => 'Projects_Model_Project',
                 'own_backend'            => 'Sql',
                 'own_id'                 => 0,
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'type'                   => 'COWORKER',
                 'related_backend'        => 'Sql',
                 'related_id'             => $_contact['id'],
@@ -1243,7 +1271,7 @@ class Addressbook_JsonTest extends TestCase
                 'own_model'              => 'Projects_Model_Project',
                 'own_backend'            => 'Sql',
                 'own_id'                 => 0,
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'type'                   => 'RESPONSIBLE',
                 'related_backend'        => 'Sql',
                 'related_id'             => Tinebase_Core::getUser()->contact_id,
@@ -1297,7 +1325,7 @@ class Addressbook_JsonTest extends TestCase
             ),
             array('field' => 'id', 'operator' => 'in', 'value' => array($_contact['id'], Tinebase_Core::getUser()->contact_id)),
         );
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
 
         $this->assertEquals('relation', $result['filter'][0]['value']['linkType']);
         $this->assertTrue(isset($result['filter'][0]['id']), 'id expected');
@@ -1342,7 +1370,7 @@ class Addressbook_JsonTest extends TestCase
             ),
             array('field' => 'id', 'operator' => 'in', 'value' => array(Tinebase_Core::getUser()->contact_id, $contact['id']))
         );
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
         $this->assertEquals('foreignRecord', $result['filter'][0]['field']);
         $this->assertEquals('foreignId', $result['filter'][0]['value']['linkType']);
         $this->assertEquals('ContactAttendeeFilter', $result['filter'][0]['value']['filterName']);
@@ -1365,7 +1393,7 @@ class Addressbook_JsonTest extends TestCase
             $this->_getOrganizerForeignIdFilter(),
             array('field' => 'id', 'operator' => 'in', 'value' => array(Tinebase_Core::getUser()->contact_id, $contact['id']))
         );
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
 
         $this->assertEquals(1, $result['totalcount']);
         $this->assertEquals(Tinebase_Core::getUser()->contact_id, $result['results'][0]['id']);
@@ -1412,7 +1440,7 @@ class Addressbook_JsonTest extends TestCase
                 array('field' => 'id', 'operator' => 'in', 'value' => array($contact['id']))
             )
         ));
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
 
         $this->assertEquals(2, $result['totalcount'], 'expected 2 contacts');
     }
@@ -1485,6 +1513,8 @@ class Addressbook_JsonTest extends TestCase
     */
     public function testDuplicateCheckWithTag()
     {
+        $this->_testNeedsTransaction();
+
         $tagName = Tinebase_Record_Abstract::generateUID();
         $tag = array(
             'type'          => Tinebase_Model_Tag::TYPE_PERSONAL,
@@ -1498,7 +1528,7 @@ class Addressbook_JsonTest extends TestCase
         // replace tag array with single tag id (like the client does)
         $contact['tags'] = array($contact['tags'][0]['id']);
         try {
-            $newContact = $this->_instance->saveContact($contact, TRUE);
+            $newContact = $this->_uit->saveContact($contact, TRUE);
             $this->assertTrue(FALSE, 'duplicate detection failed');
         } catch (Tinebase_Exception_Duplicate $ted) {
             $exceptionData = $ted->toArray();
@@ -1516,12 +1546,12 @@ class Addressbook_JsonTest extends TestCase
     {
         $contact = $this->_getContactData();
         $contact['email'] = 'test@example.org';
-        $contact = $this->_instance->saveContact($contact);
+        $contact = $this->_uit->saveContact($contact);
         $this->_contactIdsToDelete[] = $contact['id'];
         try {
             $contact2 = $this->_getContactData();
             $contact2['email'] = 'test@example.org';
-            $contact2 = $this->_instance->saveContact($contact2);
+            $contact2 = $this->_uit->saveContact($contact2);
             $this->_contactIdsToDelete[] = $contact2['id'];
             $this->assertTrue(FALSE, 'no duplicate exception');
         } catch (Tinebase_Exception_Duplicate $ted) {
@@ -1544,7 +1574,7 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testImportDefinitionsInRegistry()
     {
-        $registryData = $this->_instance->getRegistryData();
+        $registryData = $this->_uit->getRegistryData();
 
         $this->assertEquals('adb_tine_import_csv', $registryData['defaultImportDefinition']['name']);
         $this->assertTrue(is_array($registryData['importDefinitions']['results']));
@@ -1561,7 +1591,7 @@ class Addressbook_JsonTest extends TestCase
      */
     public function testSearchContactsWithTagIsNotFilter()
     {
-        $allContacts = $this->_instance->searchContacts(array(), array());
+        $allContacts = $this->_uit->searchContacts(array(), array());
 
         $filter = new Addressbook_Model_ContactFilter(array(array(
             'field'    => 'n_fileas',
@@ -1582,7 +1612,7 @@ class Addressbook_JsonTest extends TestCase
             'operator' => 'not',
             'value'    => $tag->getId()
         ));
-        $allContactsWithoutTheTag = $this->_instance->searchContacts($filter, array());
+        $allContactsWithoutTheTag = $this->_uit->searchContacts($filter, array());
 
         $this->assertTrue(count($allContactsWithoutTheTag['totalcount']) > 0);
         $this->assertEquals($allContacts['totalcount']-1, $allContactsWithoutTheTag['totalcount']);
@@ -1613,7 +1643,7 @@ class Addressbook_JsonTest extends TestCase
             'operator' => 'in',
             'value'    => array($tag->getId())
         ));
-        $allContactsWithTheTag = $this->_instance->searchContacts($filter, array());
+        $allContactsWithTheTag = $this->_uit->searchContacts($filter, array());
         $this->assertEquals(1, $allContactsWithTheTag['totalcount']);
 
         $filter = array(array(
@@ -1621,7 +1651,7 @@ class Addressbook_JsonTest extends TestCase
             'operator' => 'in',
             'value'    => array()
         ));
-        $emptyResult = $this->_instance->searchContacts($filter, array());
+        $emptyResult = $this->_uit->searchContacts($filter, array());
         $this->assertEquals(0, $emptyResult['totalcount']);
     }
     
@@ -1640,7 +1670,7 @@ Web: http://www.metaways.de
 Tel: +49 (0)40 343244-232
 Fax: +49 (0)40 343244-222";
         
-        $result = $this->_instance->parseAddressData($addressString);
+        $result = $this->_uit->parseAddressData($addressString);
         
         $this->assertTrue((isset($result['contact']) || array_key_exists('contact', $result)));
         $this->assertTrue(is_array($result['contact']));
@@ -1670,7 +1700,7 @@ Fax: +49 (0)40 343244-222";
         $addressString = "Straßenname 25 · 23569 Lübeck
 Steuernummer 33/111/32212";
         
-        $result = $this->_instance->parseAddressData($addressString);
+        $result = $this->_uit->parseAddressData($addressString);
         $this->assertEquals('Straßenname 25', $result['contact']['adr_one_street'], 'wrong street: ' . print_r($result, TRUE));
         $this->assertEquals('Lübeck', $result['contact']['adr_one_locality'], 'wrong street: ' . print_r($result, TRUE));
     }
@@ -1689,11 +1719,11 @@ Steuernummer 33/111/32212";
         
         // search for her with ContactDisabledFilter
         $filter = array(array('field' => 'n_given',      'operator' => 'equals',   'value' => 'Susan'));
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
         $this->assertEquals(0, $result['totalcount'], 'found contacts: ' . print_r($result, true));
         
         $filter[] = array('field' => 'showDisabled', 'operator' => 'equals',   'value' => TRUE);
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
         $this->assertEquals(1, $result['totalcount']);
     }
     
@@ -1725,7 +1755,7 @@ Steuernummer 33/111/32212";
             'value'    => 'hiddengroup'
         ));
         
-        $result = $this->_instance->searchLists($filter, array());
+        $result = $this->_uit->searchLists($filter, array());
         $this->assertEquals(0, $result['totalcount'], 'should not find hidden list: ' . print_r($result, TRUE));
     }
 
@@ -1737,14 +1767,14 @@ Steuernummer 33/111/32212";
         $tag2 = Tinebase_Tags::getInstance()->create($this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL, 'tag2'));
 
         $filter = array(array('field' => 'id','operator' => 'in', 'value' => array($contact1['id'], $contact2['id'])));
-        $json = new Tinebase_Frontend_Json();
+        $tinebaseJson = new Tinebase_Frontend_Json();
 
-        $json->attachMultipleTagsToMultipleRecords($filter,'Addressbook_Model_ContactFilter',array(
+        $tinebaseJson->attachMultipleTagsToMultipleRecords($filter,'Addressbook_Model_ContactFilter',array(
             $tag1->toArray(),
             $tag2->toArray(),
         ));
 
-        $result = $this->_instance->searchContacts($filter, array());
+        $result = $this->_uit->searchContacts($filter, array());
         $this->assertCount(2, $result['results'], 'search count failed');
 
         foreach($result['results'] as $contactData) {
@@ -1753,6 +1783,73 @@ Steuernummer 33/111/32212";
     }
 
     /**
+     * @see 0011584: allow to set group member roles
+     */
+    public function testCeateListWithMemberAndRole($listRoleName = 'my test role')
+    {
+        $contact = $this->_addContact();
+        $listRole = $this->_uit->saveListRole(array(
+            'name'          => $listRoleName,
+            'description'   => 'my test description'
+        ));
+        $memberroles = array(array(
+            'contact_id'   => $contact['id'],
+            'list_role_id' => $listRole['id'],
+        ));
+        $list = $this->_uit->saveList(array(
+            'name'                  => 'my test list',
+            'description'           => '',
+            'members'               => array($contact['id']),
+            'memberroles'           => $memberroles,
+            'type'                  => Addressbook_Model_List::LISTTYPE_LIST,
+        ));
+
+        $this->assertEquals(array($contact['id']), $list['members'], 'members are not saved/returned in list: ' . print_r($list, true));
+        $this->assertTrue(isset($list['memberroles']), 'memberroles missing from list');
+        $this->assertEquals(1, count($list['memberroles']), 'member roles are not saved/returned in list: ' . print_r($list, true));
+        $this->assertTrue(isset($list['memberroles'][0]['list_role_id']['id']), 'list roles should be resolved');
+        $this->assertEquals($listRole['id'], $list['memberroles'][0]['list_role_id']['id'], 'member roles are not saved/returned in list: ' . print_r($list, true));
+
+        return $list;
+    }
+
+    /**
+     * @see 0011584: allow to set group member roles
+     */
+    public function testRemoveListMemberRoles()
+    {
+        $list = $this->testCeateListWithMemberAndRole();
+
+        $list['memberroles'] = array();
+        $updatedList = $this->_uit->saveList($list);
+        $this->assertTrue(empty($updatedList['memberroles']), 'memberroles should be removed: ' . print_r($updatedList, true));
+    }
+
+    /**
+     * @see 0011578: add list roles to CoreData + Addressbook
+     */
+    public function testListRolesApi()
+    {
+        $this->_testSimpleRecordApi('ListRole');
+    }
+
+    /**
+     * @see 0011584: allow to set group member roles
+     */
+    public function testSearchContactByListRole()
+    {
+        $list = $this->testCeateListWithMemberAndRole();
+
+        $filter = array(
+            array('field' => 'list_role_id','operator' => 'in', 'value' => array($list['memberroles'][0]['list_role_id']['id']))
+        );
+
+        $result = $this->_uit->searchContacts($filter, array());
+
+        $this->assertEquals(1, $result['totalcount']);
+    }
+
+    /**
      * @see 0011704: PHP 7 can't decode empty JSON-strings
      */
     public function testEmptyPagingParamJsonDecode()
@@ -1762,7 +1859,7 @@ Steuernummer 33/111/32212";
             'operator' => 'equals',
             'value'    => 'somename'
         ));
-        $result = $this->_instance->searchContacts($filter, '');
+        $result = $this->_uit->searchContacts($filter, '');
         $this->assertEquals(0, $result['totalcount']);
     }
 }
index a63995c..647a774 100644 (file)
@@ -45,6 +45,7 @@ class AllTests
         $suite->addTest(ExampleApplication_AllTests::suite());
         $suite->addTest(Sipgate_AllTests::suite());
         $suite->addTest(SimpleFAQ_AllTests::suite());
+        $suite->addTest(CoreData_AllTests::suite());
         $suite->addTest(Zend_AllTests::suite());
         
         return $suite;
index 2354e4f..e0c1bac 100644 (file)
@@ -43,6 +43,7 @@ class Calendar_AllTests
         $suite->addTestSuite('Calendar_Import_CalDAVTest');
         $suite->addTestSuite('Calendar_Export_ICalTest');
         $suite->addTestSuite('Calendar_Export_OdsTests');
+        $suite->addTestSuite('Calendar_Export_DocTest');
         $suite->addTestSuite('Calendar_Convert_Event_VCalendar_AllTests');
         $suite->addTestSuite('Calendar_Setup_DemoDataTests');
         
index fe8a90e..390b1a9 100644 (file)
@@ -1358,6 +1358,11 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
         } else {
             $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
         }
+
+        // assert user agent
+        // @see 0011498: set user agent header for notification messages
+        $headers = $messages[0]->getHeaders();
+        $this->assertEquals(Tinebase_Core::getTineUserAgent('Notification Service'), $headers['User-Agent'][0]);
     }
 
     /**
index a16c04b..38ccd90 100644 (file)
@@ -338,8 +338,16 @@ class Calendar_Controller_EventTests extends Calendar_TestCase
             array('user_id' => $this->_getPersonasContacts('pwulf')->getId())
         ));
         $persistentEvent = $this->_controller->create($event);
-        
-        $fbinfo = $this->_controller->getFreeBusyInfo(array(array('from' => $persistentEvent->dtstart, 'until' => $persistentEvent->dtend)), $persistentEvent->attendee);
+
+        $period = new Calendar_Model_EventFilter(array(array(
+            'field'     => 'period',
+            'operator'  => 'within',
+            'value'     => array(
+                'from'      => $persistentEvent->dtstart,
+                'until'     => $persistentEvent->dtend
+            ),
+        )));
+        $fbinfo = $this->_controller->getFreeBusyInfo($period, $persistentEvent->attendee);
        
         $this->assertGreaterThanOrEqual(2, count($fbinfo));
         
@@ -348,9 +356,19 @@ class Calendar_Controller_EventTests extends Calendar_TestCase
 
     public function testSearchFreeTime()
     {
+        $this->markTestSkipped();
         $persistentEvent = $this->testGetFreeBusyInfo();
-        
-        $this->_controller->searchFreeTime($persistentEvent->dtstart->setHour(6), $persistentEvent->dtend->setHour(22), $persistentEvent->attendee);
+
+        $period = new Calendar_Model_EventFilter(array(array(
+            'field'     => 'period',
+            'operator'  => 'within',
+            'value'     => array(
+                'from'      => $persistentEvent->dtstart->setHour(6),
+                'until'     => $persistentEvent->dtend->setHour(22)
+            ),
+        )));
+
+        $this->_controller->searchFreeTime($period, $persistentEvent->attendee);
     }
     
     /**
@@ -489,7 +507,35 @@ class Calendar_Controller_EventTests extends Calendar_TestCase
         
         $this->_controller->create($nonConflictEvent, TRUE);
     }
-    
+
+    public function testCreateConflictResourceUnavailable()
+    {
+        $event = $this->_getEvent();
+
+        // create & add resource
+        $rt = new Calendar_Controller_ResourceTest();
+        $rt->setUp();
+        $resource = $rt->testCreateResource();
+        $resource->busy_type = Calendar_Model_FreeBusy::FREEBUSY_BUSY_UNAVAILABLE;
+        Calendar_Controller_Resource::getInstance()->update($resource);
+
+        $event->attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender', array(new Calendar_Model_Attender(array(
+            'user_type' => Calendar_Model_Attender::USERTYPE_RESOURCE,
+            'user_id'   => $resource->getId()
+        ))));
+
+        $conflictEvent = clone $event;
+        $this->_controller->create($event);
+        try {
+            $this->_controller->create($conflictEvent, TRUE);
+            $this->fail('Calendar_Exception_AttendeeBusy was not thrown');
+        } catch (Calendar_Exception_AttendeeBusy $abe) {
+            $fb = $abe->getFreeBusyInfo();
+            $this->assertEquals(Calendar_Model_FreeBusy::FREEBUSY_BUSY_UNAVAILABLE, $fb[0]->type);
+        }
+
+    }
+
     public function testUpdateWithConflictNoTimechange()
     {
         $persitentConflictEvent = $this->testCreateEventWithConflict();
index e6bea9a..a00f6ec 100644 (file)
@@ -1000,6 +1000,27 @@ class Calendar_Controller_RecurTest extends Calendar_TestCase
         $savedEvent = Calendar_Controller_Event::getInstance()->create($newEvent, /* $checkBusyConflicts = */ true);
     }
 
+    public function testRecurEventWithConstrainsBackgroundComputation()
+    {
+        $constrainEvent = $this->_getRecurEvent();
+        $constrainEvent->rrule_constraints = new Calendar_Model_EventFilter(array(
+            array('field' => 'container_id', 'operator' => 'in', 'value' => array($constrainEvent['container_id'])),
+        ));
+        $constrainEvent = Calendar_Controller_Event::getInstance()->create($constrainEvent);
+
+        // create conflicting event
+        $conflictEvent = $this->_getRecurEvent();
+        $conflictEvent->rrule->until = $conflictEvent->dtstart->getClone()->addDay(5);
+        $conflictEvent = Calendar_Controller_Event::getInstance()->create($conflictEvent);
+
+        // run background job
+        Calendar_Controller_Event::getInstance()->updateConstraintsExdates();
+
+        // check exdates
+        $constrainEvent = Calendar_Controller_Event::getInstance()->get($constrainEvent->getId());
+        $this->assertCount(6, $constrainEvent->exdate);
+    }
+
     /**
      * returns a simple recure event
      *
diff --git a/tests/tine20/Calendar/Export/DocTest.php b/tests/tine20/Calendar/Export/DocTest.php
new file mode 100644 (file)
index 0000000..33633cf
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ *
+ * @package     Calendar
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Cornelius Weiß <c.weiss@metaways.de>
+ */
+
+use \PhpOffice\PhpWord;
+
+/**
+ * Calendar Doc generation class tests
+ *
+ * @package     Calendar
+ * @subpackage  Export
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Cornelius Weiß <c.weiss@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+class Calendar_Export_DocTest extends Calendar_TestCase
+{
+    public function testExportSimpleDocSheet()
+    {
+        // skip tests for php7
+        // ERROR: PHP Fatal error:  Cannot use PhpOffice\PhpWord\Shared\String as String because 'String' is a special
+        //  class name in /usr/local/share/tine20.git/tine20/vendor/phpoffice/phpword/src/PhpWord/TemplateProcessor.php
+        //  on line 23
+        if (PHP_VERSION_ID >= 70000) {
+            $this->markTestSkipped('FIXME 0011730: fix doc export for php7');
+        }
+
+        // make sure definition is imported
+        $definitionFile = __DIR__ . '/../../../../tine20/Calendar/Export/definitions/cal_default_doc_sheet.xml';
+        $calendarApp = Tinebase_Application::getInstance()->getApplicationByName('Calendar');
+        Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($definitionFile, $calendarApp, 'cal_default_doc_sheet');
+
+//        Tinebase_TransactionManager::getInstance()->commitTransaction($this->_transactionId);
+
+        // @TODO have some demodata to export here
+        $filter = new Calendar_Model_EventFilter(array(
+//            array('field' => 'period', 'operator' => 'within', 'value' => array(
+//                'from' => '',
+//                'until' => ''
+//            ))
+        ));
+        $doc = new Calendar_Export_DocSheet($filter);
+        $doc->generate();
+
+        $tempfile = tempnam(Tinebase_Core::getTempDir(), __METHOD__ . '_') . '.docx';
+        $doc->save($tempfile);
+
+        $this->assertGreaterThan(0, filesize($tempfile));
+//        `open $tempfile`;
+    }
+}
\ No newline at end of file
index 024fed6..820742a 100644 (file)
@@ -460,7 +460,34 @@ class Calendar_JsonTests extends Calendar_TestCase
         $midnightInUTC = new Tinebase_DateTime($queryResult['rrule_until']);
         $this->assertEquals(Tinebase_DateTime::now()->setTime(23,59,59)->toString(), $midnightInUTC->setTimezone(Tinebase_Core::getUserTimezone(), TRUE)->toString());
     }
-    
+
+    /**
+     * testCreateRecurEventWithConstrains
+     */
+    public function testCreateRecurEventWithConstrains()
+    {
+        $conflictEventData = $this->testCreateEvent();
+
+        $eventData = $this->testCreateEvent();
+        $eventData['rrule'] = array(
+            'freq'       => 'WEEKLY',
+            'interval'   => 1,
+            'byday'      => 'WE',
+        );
+        $eventData['rrule_constraints'] = array(
+            array('field' => 'container_id', 'operator' => 'in', 'value' => array($eventData['container_id'])),
+        );
+
+        $updatedEventData = $this->_uit->saveEvent($eventData);
+
+        $this->assertTrue(is_array($updatedEventData['rrule_constraints']));
+        $this->assertEquals('personal',$updatedEventData['rrule_constraints'][0]['value'][0]['type'], 'filter is not resolved');
+        $this->assertEquals(1, count($updatedEventData['exdate']));
+        $this->assertEquals('2009-03-25 06:00:00', $updatedEventData['exdate'][0]);
+
+        return $updatedEventData;
+    }
+
     /**
     * testSearchRecuringIncludes
     */
@@ -1600,7 +1627,7 @@ class Calendar_JsonTests extends Calendar_TestCase
                 'own_model' => 'Calendar_Model_Event',
                 'own_backend' => 'Sql',
                 'own_id' => 0,
-                'own_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'type' => '',
                 'related_backend' => 'Sql',
                 'related_id' => $contact->getId(),
diff --git a/tests/tine20/CoreData/AllTests.php b/tests/tine20/CoreData/AllTests.php
new file mode 100644 (file)
index 0000000..85d29ed
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     CoreData
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2012-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * 
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+class CoreData_AllTests
+{
+    public static function main()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+    
+    public static function suite()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 CoreData All Tests');
+        
+        $suite->addTestSuite('CoreData_JsonTest');
+        return $suite;
+    }
+}
diff --git a/tests/tine20/CoreData/JsonTest.php b/tests/tine20/CoreData/JsonTest.php
new file mode 100644 (file)
index 0000000..cd9674a
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     CoreData
+ * @subpackage  Record
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+/**
+ * Test class for CoreData_JsonTest
+ */
+class CoreData_JsonTest extends TestCase
+{
+    /**
+     * unit in test
+     *
+     * @var CoreData_Frontend_Json
+     */
+    protected $_uit = null;
+
+    /**
+     * set up tests
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+        $this->_uit = new CoreData_Frontend_Json();
+    }
+
+    /**
+     * testGetCoreData
+     */
+    public function testGetCoreData()
+    {
+        $result = $this->_uit->getCoreData();
+
+        $this->assertGreaterThan(0, $result['totalcount'], print_r($result, true));
+
+        // look for 'lists'
+        $lists = null;
+        foreach ($result['results'] as $coreData) {
+            if ($coreData['id'] === 'adb_lists') {
+                $lists = $coreData;
+            }
+        }
+        $this->assertTrue($lists !== null);
+        $this->assertEquals('Addressbook_Model_List', $lists['model'], print_r($lists, true));
+    }
+}
index a2ddc64..f334f9d 100644 (file)
@@ -293,7 +293,7 @@ class Crm_ControllerTest extends PHPUnit_Framework_TestCase
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'Sql',
             'own_id'                 => $GLOBALS['Addressbook_ControllerTest']['leadId'],
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Tasks_Model_Task',
             'related_backend'        => Tasks_Backend_Factory::SQL,
             'related_id'             => $task->getId(),
index 57625e6..aa09855 100644 (file)
@@ -144,7 +144,7 @@ class Crm_Export_PdfTest extends TestCase
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'Sql',
             'own_id'                 => $lead->getId(),
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => Addressbook_Backend_Factory::SQL,
             'related_id'             => $this->objects['linkedContact']->id,
@@ -177,7 +177,7 @@ class Crm_Export_PdfTest extends TestCase
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'Sql',
             'own_id'                 => $lead->getId(),
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Tasks_Model_Task',
             'related_backend'        => Tasks_Backend_Factory::SQL,
             'related_id'             => $task->getId(),
index 71dcbfc..48e27b3 100644 (file)
@@ -87,86 +87,7 @@ class Crm_JsonTest extends Crm_AbstractTest
         parent::tearDown();
         Crm_Controller_Lead::getInstance()->duplicateCheckFields(array('lead_name'));
     }
-     
-    /**
-     * test get crm registry
-     * 
-     * @return void
-     */
-    public function testGetRegistryData()
-    {
-        $registry = $this->_getUit()->getRegistryData();
-        
-        $types = array('leadtypes', 'leadstates', 'leadsources');
-        
-        // check data
-        foreach ($types as $type) {
-            $this->assertGreaterThan(0, $registry[$type]['totalcount']);
-            $this->assertGreaterThan(0, count($registry[$type]['results']));
-        }
-        
-        // check defaults
-        $this->assertEquals(array(
-            'leadstate_id'  => 1,
-            'leadtype_id'   => 1,
-            'leadsource_id' => 1,
-        ), array(
-            'leadstate_id' => $registry['defaults']['leadstate_id'],
-            'leadtype_id' => $registry['defaults']['leadtype_id'],
-            'leadsource_id' => $registry['defaults']['leadsource_id'],
-        ));
-        $this->assertEquals(
-            Tinebase_Container::getInstance()->getDefaultContainer('Crm')->getId(),
-            $registry['defaults']['container_id']['id']
-        );
-    }
-    
-    /**
-     * test get settings/config
-     * 
-     * @return void
-     */
-    public function testGetSettings()
-    {
-        $result = $this->_getUit()->getSettings();
-        
-        $this->assertArrayHasKey('leadstates',  $result);
-        $this->assertArrayHasKey('leadtypes',   $result);
-        $this->assertArrayHasKey('leadsources', $result);
-        $this->assertArrayHasKey('defaults',    $result);
-        $this->assertEquals(6, count($result[Crm_Model_Config::LEADSTATES]));
-        $this->assertEquals(3, count($result[Crm_Model_Config::LEADTYPES]));
-        $this->assertEquals(4, count($result[Crm_Model_Config::LEADSOURCES]));
-    }
-    
-    /**
-     * test get settings/config
-     * 
-     * @return void
-     */
-    public function testSaveSettings()
-    {
-        $oldSettings = $this->_getUit()->getSettings();
-        
-        // change some settings
-        $newSettings = $oldSettings;
-        $newSettings['defaults']['leadstate_id'] = 2;
-        $newSettings['leadsources'][] = array(
-            'id' => 5,
-            'leadsource' => 'Another Leadsource'
-        );
-        $anotherResult = $this->_getUit()->saveSettings($newSettings);
-        $this->assertEquals($newSettings, $anotherResult, 'new settings have not been saved');
-        
-        // reset original settings
-        $result = $this->_getUit()->saveSettings($oldSettings);
-        $this->assertEquals($result, $oldSettings, 'old settings have not been reset');
-        
-        // test Crm_Model_Config::getOptionById
-        $settings = Crm_Controller::getInstance()->getConfigSettings();
-        $this->assertEquals(array(), $settings->getOptionById(5, 'leadsources'), 'Crm_Model_Config::getOptionById failed');
-    }
-    
+
     /**
      * try to add/search/delete a lead with linked contact, task and product
      * 
@@ -394,7 +315,7 @@ class Crm_JsonTest extends Crm_AbstractTest
                 'type'  => 'TASK',
                 'own_model' => 'Tasks_Model_Task',
                 'own_backend' => 'Sql',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'related_model' => 'Crm_Model_Lead',
                 'related_backend' => 'Sql',
                 'related_id' => $leadData['id'],
@@ -438,7 +359,7 @@ class Crm_JsonTest extends Crm_AbstractTest
                 'type'  => 'TASK',
                 'own_model' => 'Tasks_Model_Task',
                 'own_backend' => 'Sql',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'related_model' => 'Crm_Model_Lead',
                 'related_backend' => 'Sql',
                 'related_id' => $leadData1['id'],
@@ -453,7 +374,7 @@ class Crm_JsonTest extends Crm_AbstractTest
             'type'  => 'TASK',
             'own_model' => 'Tasks_Model_Task',
             'own_backend' => 'Sql',
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'related_model' => 'Crm_Model_Lead',
             'related_backend' => 'Sql',
             'related_id' => $leadData2['id'],
@@ -481,7 +402,7 @@ class Crm_JsonTest extends Crm_AbstractTest
                 'type'  => 'TASK',
                 'own_model' => 'Tasks_Model_Task',
                 'own_backend' => 'Sql',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'related_model' => 'Crm_Model_Lead',
                 'related_backend' => 'Sql',
                 'related_id' => $leadData1['id'],
@@ -498,7 +419,7 @@ class Crm_JsonTest extends Crm_AbstractTest
                 'type'  => 'TASK',
                 'own_model' => 'Crm_Model_Lead',
                 'own_backend' => 'Sql',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'related_model' => 'Tasks_Model_Task',
                 'related_backend' => 'Sql',
                 'related_id' => $taskData['id'],
index 683e36c..518f014 100644 (file)
@@ -51,7 +51,7 @@ class Crm_NotificationsTests extends Crm_AbstractTest
             'related_record'         => $this->_getContact(),
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'Sql',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => Tasks_Backend_Factory::SQL,
         ), TRUE));
@@ -83,7 +83,7 @@ class Crm_NotificationsTests extends Crm_AbstractTest
             'related_record'         => Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId()),
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'Sql',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => Tasks_Backend_Factory::SQL,
         ), TRUE));
@@ -110,7 +110,7 @@ class Crm_NotificationsTests extends Crm_AbstractTest
                 'related_record'         => Addressbook_Controller_Contact::getInstance()->getContactByUserId(Tinebase_Core::getUser()->getId()),
                 'own_model'              => 'Crm_Model_Lead',
                 'own_backend'            => 'Sql',
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'related_model'          => 'Addressbook_Model_Contact',
                 'related_backend'        => Tasks_Backend_Factory::SQL,
         ), TRUE));
index 1f23a4b..217d5ed 100644 (file)
@@ -1137,7 +1137,79 @@ class Felamimail_Frontend_JsonTest extends TestCase
         $fullMessage = $this->_json->getMessage($message['id']);
         $this->assertTrue(empty($fullMessage->preparedParts));
     }
-    
+
+    /**
+     * testSendMailveopeAPIMessage
+     *
+     * - envolpe amored message into PGP MIME structure
+     */
+    public function testSendMailveopeAPIMessage()
+    {
+        $subject = 'testSendMailveopeAPIMessage';
+        $messageData = $this->_getMessageData('', $subject);
+        $messageData['body'] = '-----BEGIN PGP MESSAGE-----
+Version: Mailvelope v1.3.3
+Comment: https://www.mailvelope.com
+
+wcFMA/0LJF28pDbGAQ//YgtsmEZN+pgIJiBDb7iYwPEOchDRIEjGOx543KF6
+5YigW9p39pfcJgvGfT8x9cUIrYGxyw5idPSOEftYXyjjGaOYGaKpRSR4hI83
+OcJSlEHKq72xhg04mNpCjjJ8dLBstPcQ7tDtsA8Nfb4PwkUYB9IhIBnARg+n
+NvrN8mSA2UnY9ElFCvf30sar8EuM5swAjbk64C8TIypMy/Bg4T93zRdxwik6
+7BCcbOpm/2PTsiVYBOTcU4+XdG5eyTENXH58M6UTxTD4/g7Qi5PjN+PxyXqf
+v2Y1k9F49Y1egf2QJ2r4PX0EWS8SaynSHiIoBsp1xb07nLwZwCdMPG1QNPpF
+l2FqlS4dEuQTdkv0deMvd7gtiNynRTAVcJc1ZC6RuWJ+EH2jA49XWkn14eRC
+e5jMtPPudkhubnN9Je5lwatGKbJGyuXh+IaM0E0WQMZ3dm8+ST1l4WpVuGbw
+KozLUiTRJP9UoxWOcwpQOnzcSlc4rHmWdtF0y3usM9u9GPREqpNUWkEyEEuv
+XdZE7rKKj22dJHLCXxAQEh3m29Y2WVaq50YbtEZ+SwwbrHhxP4+FJEru+byh
+fiZ47sVW2KvYGJPvbFoSZHiSvMecxDg8BVwe+naZXww/Rwa/TlaX4bYyzpUG
+KeJUAzWEfFpJ0+yAvMGQEC7psIJ9NCx149C4ujiQmajSwhUB3XANcmCGB0wm
+JjcqC4AHvc7/t4MrQZm0F/W+nrMmNqbZk+gylVrPs9rFEqu7wbjwTmsFA3sS
+LkenvQIxBali6uzCR+nd09REqcYirG9cLti39DW048lhhG/ml+gAxxNEaSpG
+NbIoV/3w8n7sAIM1fjuHne8bX0gWG43TTjU8MwSMryG7tCOG5u+Cebh6TAoY
+NzbX2dpDhOYq5zXdCgKU4P3eh0csSs4UrqFT3TdAxIGrQJ7KrXvB6+N8gRZo
+FcUaR+zrRPJjPUZfi46ecP5SG/tM5ea1hqvkwEnEpqjLmCUxqB+rfxx46USX
+hMZd2ukUv6kEKv3EUDsRYu1SlDLhDLhWNx8RJae5XkMR+eUUMyNNVwbeMQbB
+VAcMcaPITTk84sH7XElr9eF6sCUN4V79OSBRPGY/aNGrcwcoDSD4Hwu+Lw9w
+Q+1n8EQ66gAkbJzCNd5GaYMZR9echkBaD/rdWDS3ktcrMehra+h44MTQONV9
+8W+RI+IT5jaIXtB4jePmGjsJjbC9aEhTRBRkUnPA7phgknc52dD74AY/6lzK
+yd4uZ6S3vhurJW0Vt4iBWJzhFNiSODh5PzteeNzCVAkGMsQvy1IHk0d3uzcE
+0tEuSh8fZOFGB4fvMx9Mk8oAU92wfj4J7AVpSo5oRdxMqAXfaYKqfr2Gn++q
+E5LClhVIBbFXclCoe0RYNz4wtxjeeYbP40Bq5g0JvPutD/dBMp8hz8Qt+yyG
+d8X4/KmQIXyFZ8aP17GMckE5GVVvY9y89eWnWuTUJdwM540hB/EJNeHHTE5y
+N2FSLGcmNkvE+3H7BczQ2ZI1SZDhof+umbUst0qoQW+hHmY3CSma48yGAVox
+52u2t7hosHCfpf631Ve/6fcICo8vJ2Qfufu2BGIMlSfx4WzUuaMQBynuxFSa
+IbVx8ZTO7dJRKrg72aFmWTf0uNla7vicAhpiLWobyNYcZbIjrAGDfg==
+=BaAn
+-----END PGP MESSAGE-----';
+
+        $this->_foldersToClear[] = 'INBOX';
+        $this->_json->saveMessage($messageData);
+
+        $message = $this->_searchForMessageBySubject(Tinebase_Core::filterInputForDatabase($subject));
+        $fullMessage = $this->_json->getMessage($message['id']);
+
+        $this->assertContains('multipart/encrypted', $fullMessage['headers']['content-type']);
+        $this->assertContains('protocol="application/pgp-encrypted"', $fullMessage['headers']['content-type']);
+        $this->assertCount(2, $fullMessage['structure']['parts']);
+        $this->assertEquals('application/pgp-encrypted', $fullMessage['structure']['parts'][1]['contentType']);
+        $this->assertEquals('application/octet-stream', $fullMessage['structure']['parts'][2]['contentType']);
+
+        return $fullMessage;
+    }
+
+    /**
+     * testMessagePGPMime
+     *
+     * - prepare amored part of PGP MIME structure
+     */
+    public function testMessagePGPMime()
+    {
+        $fullMessage = $this->testSendMailveopeAPIMessage();
+
+        $this->assertEquals('application/pgp-encrypted', $fullMessage['preparedParts'][0]['contentType']);
+        $this->assertContains('-----BEGIN PGP MESSAGE-----', $fullMessage['preparedParts'][0]['preparedData']);
+    }
+
     /*********************** sieve tests ****************************/
     
     /**
diff --git a/tests/tine20/Felamimail/files/openpgpencrypted.eml b/tests/tine20/Felamimail/files/openpgpencrypted.eml
new file mode 100644 (file)
index 0000000..eceebf7
--- /dev/null
@@ -0,0 +1,43 @@
+Return-Path: <unittest@tine20.org>\r
+Delivered-To: unittest@tine20.org\r
+Received: from localhost (unknown [192.168.33.1])\r
+       (using TLSv1 with cipher ECDHE-RSA-AES256-SHA (256/256 bits))\r
+       (No client certificate requested)\r
+       (Authenticated sender: unittest@tine20.org)\r
+       by packer-virtualbox-iso-1410946258.hh.metaways.de (Postfix) with ESMTPSA id 7839780069\r
+       for <unittest@tine20.org>; Wed, 14 Oct 2015 21:09:43 +0200 (CEST)\r
+Subject: ENC02\r
+From: "Tine 2.0 Admin Account" <unittest@tine20.org>\r
+To: unittest@tine20.org\r
+User-Agent: Tine 2.0 Email Client (version mailvelope: 901d207b8b6cd30bfca0a8f781a11c97208a129c (2015-10-09 16:18:55) - none)\r
+Message-Id: <aba6dba5a4f6ef4b136cbf955975bb41d6fe69b5@tine20.org>\r
+X-MailGenerator: Tine 2.0\r
+Date: Wed, 14 Oct 2015 19:19:10 +0000\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: quoted-printable\r
+Content-Disposition: inline\r
+MIME-Version: 1.0\r
+\r
+-----BEGIN PGP MESSAGE-----=0D=0AVersion: Mailvelope v1.2.0=0D=0AComment=\r
+: https://www.mailvelope.com=0D=0A=0D=0AwcFMA3VA7DhVUrUPAQ//WFbMOBAy2dWJ=\r
+3+oFFNHktJWVMCZF+2h1awlRNMtv=0AgjJb90QRSmZvn2dDhuYwLS4skCjsguqK313kJvRjj=\r
+MV1Bl5tjAwOvGMczgI3=0AvfQ+R3Kbj8OleQewBlAHvYrJkjI0ll0z/ES5Q7Jf21gql7rRQK=\r
+euvUXuLtgX=0Ag0/8HjzYrnzsOM2Mf3NyaKz5zammvLl9c/DXmy+N39qnLCd+9YLNAdpOadk=\r
+5=0A1qBcWKzO9IIPnZWuTJhAMKn8gzFi8C9SnSboHG9u25+h/aiQOa50P7LaRLGt=0AX02pU=\r
+it9rqMWBb8FBaCJx9JilpHJsp12g4kYdmcxaqcdNWZD+jaQR5OtP4Ex=0At9/JVckzrMkJua=\r
+Hzfk5lLy25eZtYXGAGR2Fs8eG06T5NGUXDCr6Yf46I7D/r=0Al4Kz6aS/AQXi2EZDXsJsYzi=\r
+Se1tEulFgdyQcbcIX6ptBbiJqcd0lTae6a0bm=0AQ1PgSJ4/dXc6izHNiXQGC2JsfaUPPlzH=\r
+9DYg6TKZu+s2Z1uxp1dKgBWatoq7=0AGYBXRqMTGFCw4MFZhqQrAh1uu8Tg/o768xPilSHJL=\r
+1cbaTPEehInTXS6inZ1=0ARmcftWvshPgnfz573NGCRZzIRyO6dvL5osnhy2Yda6H1RO8taa=\r
+X2NUzciqxl=0App4FC3Ylr1MVoewKtUQ/aSK2PwQxFGYeMVUXNLWlb9PSwSoBCaI6DEPNCmx=\r
+s=0AxaRISqaVibLy74+bt6ah+SueKpM7PiVNsW+LtDJi3Szz7GozoIt4TvEyipVX=0Aj7O5K=\r
+gRLU00z003VWre1FYZU2TP/zEd9fkL0cnUCoLb99VL/gM2uhPwrtNtV=0AUASjSm0eCnEH9n=\r
+MKAEY8ZZzbQW9fzL3jJxpIcxT2QsAvdP4m7JO6CpyP3M+K=0AdZSIcVkXNIaOgzV7Hqg90bF=\r
+uU7CW8/GMC0B3c15muSrJTk41Ku72SWzYzO24=0AF7VEAD8tioHkwkv73z0P/GaKX7tm7+JX=\r
+zqBPMhW0lsZDrqcJo0zAtvUEo/ND=0AAQ+YWEDSuT/rO6AgkOWtFQ3JzlnvzY0njmiVc+XaI=\r
+cEaOeMvsgeTCdA7Neuc=0AhHuQx4hWKYGCXVJVyMX/VZnrtDmFHSWunK/Bvo13aTJ5h13DnU=\r
++7TIRlatRV=0AIP/RQfpWIYI5uOi8CtpAmJT69rrPxnvpJbv2ZG6Cb/6LszUiqX/VjCV4cVg=\r
+d=0AXdedRqulQi4GM9xSel335pj9ER8hONUmep9VLhQef4wlcz7yOyrrI54nqunN=0AII52m=\r
+2LdvYRS0yjzXWk34UqRCEVAphEl3J6unSFd7yVXUiW/F/4ZxCEc2YVY=0AddJeIYuxxkrRac=\r
+aODpH762QJqAHL6vUnNZdWYSwN=0D=0A=3D5QzJ=0D=0A-----END PGP MESSAGE-----=\r
+=0D=0A\r
index 19cfeb2..9dafeab 100644 (file)
@@ -1045,7 +1045,7 @@ class Filemanager_Frontend_JsonTests extends TestCase
             'own_model'              => 'Filemanager_Model_Node',
             'own_backend'            => 'Sql',
             'own_id'                 => $node['id'],
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'type'                   => 'FILE',
             'related_backend'        => 'Sql',
             'related_model'          => 'Addressbook_Model_Contact',
index b4a955b..22004dd 100644 (file)
@@ -5,16 +5,11 @@
  * @package     Inventory
  * @subpackage  Record
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2012-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Michael Spahn <m.spahn@metaways.de>
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * Test class for Inventory_JsonTest
  */
 class Inventory_JsonTest extends Inventory_TestCase
@@ -235,4 +230,27 @@ class Inventory_JsonTest extends Inventory_TestCase
         
         return $result;
     }
+
+    /**
+     * saveRecordWithImage
+     */
+    public function testSaveRecordWithImage()
+    {
+        // create TEMPFILE and save in inv item
+        $imageFile = dirname(dirname(dirname(dirname(__FILE__)))) . '/tine20/images/cancel.gif';
+        $tempImage = Tinebase_TempFile::getInstance()->createTempFile($imageFile);
+        $imageUrl = Tinebase_Model_Image::getImageUrl('Tinebase', $tempImage->getId(), 'tempFile');
+
+        $invItem = $this->_getInventoryItem()->toArray();
+        $invItem['image'] = $imageUrl;
+        $savedInvItem = $this->_json->saveInventoryItem($invItem);
+
+        //$savedInvItem = $this->_json->getInventoryItem($savedInvItem['id']);
+        $this->assertTrue(! empty($savedInvItem['image']), 'image url is empty');
+        $this->assertTrue(preg_match('/location=vfs&id=([a-z0-9]*)/', $savedInvItem['image']) == 1, print_r($savedInvItem, true));
+
+        // check if favicon is delivered
+        $image = Tinebase_Model_Image::getImageFromImageURL($savedInvItem['image']);
+        $this->assertEquals(52, $image->width);
+    }
 }
index 0a55a17..e0ce886 100644 (file)
@@ -373,7 +373,7 @@ class Projects_JsonTest extends PHPUnit_Framework_TestCase
                 'own_model'              => 'Projects_Model_Project',
                 'own_backend'            => 'Sql',
                 'own_id'                 => 0,
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'type'                   => 'COWORKER',
                 'related_record'         => NULL,
                 'related_backend'        => 'Sql',
index 4019e95..a50404f 100644 (file)
@@ -70,7 +70,7 @@ class Sales_CustomersTest extends PHPUnit_Framework_TestCase
             'own_model' => 'Sales_Model_Coustomer',
             'own_backend' => 'Sql',
             'own_id' => $ipnet->getId(),
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'remark' => 'phpunit test',
             'related_model' => 'Sales_Model_Contract',
             'related_backend' => 'Sql',
@@ -151,7 +151,7 @@ class Sales_CustomersTest extends PHPUnit_Framework_TestCase
                     array(
                         'own_model' => 'Sales_Model_Address',
                         'own_backend' => 'Sql',
-                        'own_degree' => 'sibling',
+                        'related_degree' => 'sibling',
                         'remark' => 'phpunit test',
                         'related_model' => 'Sales_Model_Contract',
                         'related_backend' => 'Sql',
index 71740d3..31a1eff 100644 (file)
@@ -76,7 +76,7 @@ class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
                 'own_model'              => 'Sales_Model_Contract',
                 'own_backend'            => Tasks_Backend_Factory::SQL,
                 'own_id'                 => NULL,
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'related_model'          => 'Sales_Model_CostCenter',
                 'related_backend'        => Tasks_Backend_Factory::SQL,
                 'related_id'             => $this->_costcenterRecords->getFirstRecord()->getId(),
index d1056cc..432df21 100644 (file)
@@ -326,7 +326,7 @@ class Sales_InvoiceJsonTests extends Sales_InvoiceTestCase
         $c1['relations'] = array(array(
             'related_model' => 'Timetracker_Model_Timeaccount',
             'related_id'    => $ta['id'],
-            'own_degree'    => 'sibling',
+            'related_degree'=> 'sibling',
             'type'          => 'TIME_ACCOUNT',
             'remark'        => 'unittest',
             'related_backend' => 'Sql'
@@ -344,7 +344,7 @@ class Sales_InvoiceJsonTests extends Sales_InvoiceTestCase
         $c2['relations'] = array(array(
             'related_model'   => 'Timetracker_Model_Timeaccount',
             'related_id'      => $ta['id'],
-            'own_degree'      => 'sibling',
+            'related_degree'  => 'sibling',
             'type'            => 'TIME_ACCOUNT',
             'remark'          => 'unittest',
             'related_backend' => 'Sql'
index dcc0741..29be6d4 100644 (file)
@@ -579,7 +579,7 @@ class Sales_InvoiceTestCase extends TestCase
                     'own_model'              => 'Sales_Model_Contract',
                     'own_backend'            => Tasks_Backend_Factory::SQL,
                     'own_id'                 => NULL,
-                    'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                    'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                     'related_model'          => 'Sales_Model_CostCenter',
                     'related_backend'        => Tasks_Backend_Factory::SQL,
                     'related_id'             => $costcenter->getId(),
@@ -589,7 +589,7 @@ class Sales_InvoiceTestCase extends TestCase
                     'own_model'              => 'Sales_Model_Contract',
                     'own_backend'            => Tasks_Backend_Factory::SQL,
                     'own_id'                 => NULL,
-                    'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                    'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                     'related_model'          => 'Sales_Model_Customer',
                     'related_backend'        => Tasks_Backend_Factory::SQL,
                     'related_id'             => $customer->getId(),
@@ -602,7 +602,7 @@ class Sales_InvoiceTestCase extends TestCase
                     'own_model'              => 'Sales_Model_Contract',
                     'own_backend'            => Tasks_Backend_Factory::SQL,
                     'own_id'                 => NULL,
-                    'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                    'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                     'related_model'          => 'Timetracker_Model_Timeaccount',
                     'related_backend'        => Tasks_Backend_Factory::SQL,
                     'related_id'             => $timeaccount->getId(),
index f90a2ee..8755b06 100644 (file)
@@ -208,7 +208,7 @@ class Sales_JsonTest extends TestCase
         $relationData = array();
         foreach ($contacts as $contact) {
             $relationData[] = array(
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'related_degree' => 'sibling',
                 'related_model' => 'Addressbook_Model_Contact',
                 'related_backend' => 'Sql',
@@ -496,7 +496,7 @@ class Sales_JsonTest extends TestCase
                     'container_id'  => $personalContainer[0]->getId(),
                 ),
                 'related_model' => 'Addressbook_Model_Contact',
-                'own_degree'    => 'sibling'
+                'related_degree'=> 'sibling'
             ),
             array(
                 'type'              => Sales_Model_Contract::RELATION_TYPE_RESPONSIBLE,
@@ -505,7 +505,7 @@ class Sales_JsonTest extends TestCase
                     'container_id'  => $personalContainer[0]->getId(),
                 ),
                 'related_model' => 'Addressbook_Model_Contact',
-                'own_degree'    => 'sibling'
+                'related_degree'=> 'sibling'
             ),
         );
     }
@@ -657,7 +657,7 @@ class Sales_JsonTest extends TestCase
         
         // a partner may be added
         $relation = new Tinebase_Model_Relation(array(
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'own_model'  => 'Addressbook_Model_Contact',
             'own_backend' => 'Sql',
             'own_id' => $contact2->getId(),
@@ -676,7 +676,7 @@ class Sales_JsonTest extends TestCase
         
         // a second partner may be added also
         $relation = new Tinebase_Model_Relation(array(
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'own_model'  => 'Addressbook_Model_Contact',
             'own_backend' => 'Sql',
             'own_id' => $contact3->getId(),
@@ -697,7 +697,7 @@ class Sales_JsonTest extends TestCase
 
         // but a second responsible must not be added
         $relation = new Tinebase_Model_Relation(array(
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'own_model'  => 'Addressbook_Model_Contact',
             'own_backend' => 'Sql',
             'own_id' => $contact4->getId(),
index 826f5da..8f7cc5c 100644 (file)
@@ -126,7 +126,7 @@ class Sales_PurchaseInvoiceTest extends TestCase
                 'price_total' => 12.9,
                 'relations' => array(array(
                         'own_model' => 'Sales_Model_PurchaseInvoice',
-                        'own_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
+                        'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
                         'related_model' => 'Sales_Model_Supplier',
                         'related_record' => $customerData,
                         'type' => 'SUPPLIER'
index c82e8fc..78888b6 100644 (file)
@@ -506,6 +506,43 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * test record json api
+     *
+     * @param $modelName
+     */
+    protected function _testSimpleRecordApi($modelName)
+    {
+        $uit = $this->_getUit();
+        if (!$uit instanceof Tinebase_Frontend_Json_Abstract) {
+            throw new Exception('only allowed for json frontend tests suites');
+        }
+
+        $newRecord = array(
+            'name' => 'my test ' . $modelName,
+            'description' => 'my test description'
+        );
+        $savedRecord = call_user_func(array($uit, 'save' . $modelName), $newRecord);
+
+        $this->assertEquals('my test ' . $modelName, $savedRecord['name'], print_r($savedRecord, true));
+        $savedRecord['description'] = 'my updated description';
+
+        $updatedRecord = call_user_func(array($uit, 'save' . $modelName), $savedRecord);
+        $this->assertEquals('my updated description', $updatedRecord['description']);
+
+        $filter = array(array('field' => 'id', 'operator' => 'equals', 'value' => $updatedRecord['id']));
+        $result = call_user_func(array($uit, 'search' . $modelName . 's'), $filter, array());
+        $this->assertEquals(1, $result['totalcount']);
+
+        call_user_func(array($uit, 'delete' . $modelName . 's'), array($updatedRecord['id']));
+        try {
+            call_user_func(array($uit, 'get' . $modelName), $updatedRecord['id']);
+            $this->fail('should delete Record');
+        } catch (Tinebase_Exception_NotFound $tenf) {
+            $this->assertTrue($tenf instanceof Tinebase_Exception_NotFound);
+        }
+    }
+
+    /**
      * returns true if main db adapter is postgresql
      *
      * @return bool
index 53ce03c..0ff0a5c 100644 (file)
@@ -137,7 +137,7 @@ class Timetracker_FilterTest extends Timetracker_AbstractTest
             'own_model' => 'Timetracker_Model_Timeaccount',
             'own_backend' => 'Sql',
             'own_id' => $timeaccount->getId(),
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'remark' => 'phpunit test',
             'related_model' => 'Sales_Model_Contract',
             'related_backend' => 'Sql',
index d4cfbfc..de31ad6 100644 (file)
@@ -870,7 +870,7 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
         $r = new Tinebase_Model_Relation(array(
             'own_model' => 'Timetracker_Model_Timeaccount',
             'own_backend' => 'Sql',
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'own_id' => $ta->getId(),
             'remark' => 'PHP UNITTEST',
             'related_model' => 'Sales_Model_Contract',
@@ -985,8 +985,8 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
         $contract = $contractController->create(new Sales_Model_Contract(array('number' => '123', 'title' => 'UnitTest')));
         
         Tinebase_Relations::getInstance()->setRelations('Timetracker_Model_Timeaccount', 'Sql', $ta['id'], array(
-            array('related_backend' => 'Sql', 'type' => 'RESPONSIBLE', 'related_model' => 'Addressbook_Model_Contact', 'related_id' => $contact->getId(), 'own_degree' => 'sibling'),
-            array('related_backend' => 'Sql', 'type' => 'TIME_ACCOUNT', 'related_model' => 'Sales_Model_Contract', 'related_id' => $contract->getId(), 'own_degree' => 'sibling'),
+            array('related_backend' => 'Sql', 'type' => 'RESPONSIBLE', 'related_model' => 'Addressbook_Model_Contact', 'related_id' => $contact->getId(), 'related_degree' => 'sibling'),
+            array('related_backend' => 'Sql', 'type' => 'TIME_ACCOUNT', 'related_model' => 'Sales_Model_Contract', 'related_id' => $contract->getId(), 'related_degree' => 'sibling'),
         ));
         
         // add 2 relations
@@ -1031,7 +1031,7 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
         $bday = $contact['bday'];
         
         Tinebase_Relations::getInstance()->setRelations('Timetracker_Model_Timeaccount', 'Sql', $ta['id'], array(
-            array('related_backend' => 'Sql', 'type' => 'RESPONSIBLE', 'related_model' => 'Addressbook_Model_Contact', 'related_id' => $contact->getId(), 'own_degree' => 'sibling'),
+            array('related_backend' => 'Sql', 'type' => 'RESPONSIBLE', 'related_model' => 'Addressbook_Model_Contact', 'related_id' => $contact->getId(), 'related_degree' => 'sibling'),
         ));
         
         // update a few times, bday of contract should not change
@@ -1096,7 +1096,7 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
                 'own_backend' => 'Sql',
                 'own_id' => $contract->getId(),
                 'own_model' => 'Sales_Model_Contract',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'remark' => 'PHP UNITTEST',
                 'related_model' => 'Addressbook_Model_Contact',
                 'related_backend' => 'Sql',
@@ -1109,7 +1109,7 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
         $taToFind->relations = array(
             new Tinebase_Model_Relation(array(
                 'own_backend' => 'Sql',
-                'own_degree' => 'sibling',
+                'related_degree' => 'sibling',
                 'own_id' => $taToFind->getId(),
                 'own_model' => 'Timetracker_Model_Timeaccount',
                 'remark' => 'PHP UNITTEST',
@@ -1189,7 +1189,7 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
             'related_id' => $ta->id,
             'related_model' => 'Timetracker_Model_Timeaccount',
             'related_record' => $ta,
-            'own_degree' => 'sibling',
+            'related_degree' => 'sibling',
             'type' => 'INVOICE'
         )));
         
index 7986d37..a409349 100644 (file)
@@ -40,8 +40,6 @@ class Tinebase_AllTests
         $suite->addTestSuite('Tinebase_ModelConfigurationTest');
         $suite->addTestSuite('Tinebase_DateTimeTest');
         $suite->addTestSuite('Tinebase_ExceptionTest');
-        $suite->addTestSuite('Tinebase_Record_RecordTest');
-        $suite->addTestSuite('Tinebase_Record_RecordSetTest');
         $suite->addTestSuite('Tinebase_AuthTest');
         $suite->addTestSuite('Tinebase_UserTest');
         $suite->addTestSuite('Tinebase_GroupTest');
@@ -81,6 +79,7 @@ class Tinebase_AllTests
         $suite->addTest(Tinebase_Frontend_AllTests::suite());
         $suite->addTest(Tinebase_Acl_AllTests::suite());
         $suite->addTest(Tinebase_Tree_AllTests::suite());
+        $suite->addTest(Tinebase_Record_AllTests::suite());
         $suite->addTest(Tinebase_Scheduler_AllTests::suite());
         $suite->addTest(Tinebase_WebDav_AllTests::suite());
         $suite->addTest(OpenDocument_AllTests::suite());
index 1955d80..ca535aa 100644 (file)
@@ -395,7 +395,7 @@ class Tinebase_Frontend_Json_PersistentFilterTest extends TestCase
                 'own_model'              => 'Addressbook_Model_Contact',
                 'own_backend'            => 'Sql',
                 'own_id'                 => 0,
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'type'                   => '',
                 'related_backend'        => 'Sql',
                 'related_id'             => $contact1->getId(),
diff --git a/tests/tine20/Tinebase/Record/AllTests.php b/tests/tine20/Tinebase/Record/AllTests.php
new file mode 100644 (file)
index 0000000..dee52d8
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+class Tinebase_Record_AllTests
+{
+    public static function main ()
+    {
+        PHPUnit_TextUI_TestRunner::run(self::suite());
+    }
+    
+    public static function suite ()
+    {
+        $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 All Record Tests');
+
+        $suite->addTestSuite('Tinebase_Record_RecordTest');
+        $suite->addTestSuite('Tinebase_Record_RecordSetTest');
+        $suite->addTestSuite('Tinebase_Record_PathTest');
+
+        return $suite;
+    }
+}
diff --git a/tests/tine20/Tinebase/Record/AutoRecord.php b/tests/tine20/Tinebase/Record/AutoRecord.php
deleted file mode 100644 (file)
index 857a4bf..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-/**
- * class for testing auto model creation
- *
- * @package     Tinebase
- * @subpackage  Record
- * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @author      Alexander Stintzing <a.stintzing@metaways.de>
- * @copyright   Copyright (c) 2012 Metaways Infosystems GmbH (http://www.metaways.de)
- *
- */
-
-/**
- * class to hold Test data
- * @package Test
- */
-class Tinebase_Record_AutoRecord extends Tinebase_Record_Abstract
-{
-    /**
-     * application the record belongs to
-     * @var string
-     */
-    protected $_application = 'Addressbook';
-
-    /**
-     * array with meta information about the model (like models.js)
-     * @var array
-     */
-    protected static $_meta = array(
-        'idProperty'        => 'id',
-        'titleProperty'     => 'text',
-        'recordName'        => 'Record',
-        'recordsName'       => 'Records',
-        'containerProperty' => NULL,
-        'containerName'     => 'Containers',
-        'containersName'    => 'Containers',
-        'defaultFilter'     => 'text',
-        'hasRelations'       => true,
-        'hasCustomFields'   => true,
-        'hasNotes'          => true,
-        'hasTags'           => true,
-        'modlogActive'      => true,
-    );
-
-    /**
-     * fields for auto bootstrapping
-     * @var array
-     */
-    protected static $_fields = array(
-        'id' => array(
-            'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => false),
-            'label' => null,
-        ),
-        'text' => array(
-            'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => false),
-            'label' => 'Text',
-        ),
-        'date' => array(
-            'type' => 'date', 'validators' => array(Zend_Filter_Input::ALLOW_EMPTY => false),
-            'label' => 'Date', // _('Start Date')
-        ),
-    );
-}
\ No newline at end of file
diff --git a/tests/tine20/Tinebase/Record/ContainerTest.php b/tests/tine20/Tinebase/Record/ContainerTest.php
deleted file mode 100644 (file)
index 677f254..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<?php
-/**
- * Tine 2.0 - http://www.tine20.org
- * 
- * @package     Tinebase
- * @subpackage  Record
- * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
- * @author      Matthias Greiling <m.greiling@metaways.de>
- */
-
-/**
- * Test helper
- */
-require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-if (!defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Tinebase_Record_ContainerTest::main');
-}
-
-/**
- * Test class for Tinebase_Record_Container.
- * Generated by PHPUnit on 2008-02-14 at 12:25:04.
- */
-class Tinebase_Record_ContainerTest extends Tinebase_Record_AbstractTest
-{
-    /**
-     * @var    Tinebase_Record_Container
-     * @access protected
-     */
-    protected $objects;
-
-    /**
-     * Sets up the fixture, for example, opens a network connection.
-     * This method is called before a test is executed.
-     *
-     * @access protected
-     */
-    public function setUp()
-    {
-        
-       $this->objects['TestRecord'] = new Tinebase_Record_Container(array(), true);
-
-         $this->objects['TestRecord']->setFromArray(array(
-            'container_id'      => 200,
-                'container_name'    => 'test',
-                'container_type'    => 'shared',
-            'container_backend' => 1,
-                'application_id'    => 20,
-               'account_grants'    => 31,
-            )
-        , true);
-        
-        $this->expectFailure['TestRecord']['testSetId'][] = array('2','3');
-        $this->expectSuccess['TestRecord']['testSetId'][] = array('2','2');
-        
-        
-        }
-
-    /**
-     * Tears down the fixture, for example, closes a network connection.
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
-    protected function tearDown()
-    {
-    }
-}
-
-// Call Tinebase_Record_ContainerTest::main() if this source file is executed directly.
-if (PHPUnit_MAIN_METHOD == 'Tinebase_Record_AbstractRecordTest::main') {
-    Tinebase_Record_AbstractRecordTest::main();
-}
-?>
diff --git a/tests/tine20/Tinebase/Record/PathTest.php b/tests/tine20/Tinebase/Record/PathTest.php
new file mode 100644 (file)
index 0000000..7b448a2
--- /dev/null
@@ -0,0 +1,323 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Tinebase
+ * @subpackage  Record
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Record path test class
+ */
+class Tinebase_Record_PathTest extends TestCase
+{
+    /**
+     * @var Addressbook_Model_Contact
+     */
+    protected $_fatherRecord = null;
+
+    protected function setUp()
+    {
+        $this->_uit = Tinebase_Record_Path::getInstance();
+        
+        parent::setUp();
+    }
+
+    /**
+     * testBuildRelationPathForRecord
+     */
+    public function testBuildRelationPathForRecord()
+    {
+        $contact = $this->_createFatherMotherChild();
+        $result = $this->_uit->generatePathForRecord($contact);
+        $this->assertTrue($result instanceof Tinebase_Record_RecordSet);
+        $this->assertEquals(2, count($result), 'should find 2 paths for record. paths:' . print_r($result->toArray(), true));
+
+        // check both paths
+        $expectedPaths = array('/grandparent/father/tester', '/mother/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $result->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($result->toArray(), true));
+        }
+
+        $result = $this->_uit->generatePathForRecord($this->_fatherRecord);
+        $this->assertEquals(1, count($result), 'should find 1 path for record. paths:' . print_r($result->toArray(), true));
+        $this->assertEquals('/grandparent/father', $result->getFirstRecord()->path);
+    }
+
+    protected function _createFatherMotherChild()
+    {
+        // create some parent / child relations for record
+        $this->_fatherRecord = $this->_getFatherWithGrandfather();
+        $motherRecord = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'mother',
+        )));
+        $relation1 = $this->_getParentRelationArray($this->_fatherRecord);
+        $relation2 = $this->_getParentRelationArray($motherRecord);
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'tester',
+            'relations' => array($relation1, $relation2)
+        )));
+
+        return $contact;
+    }
+
+    /**
+     * @return Tinebase_Record_Interface
+     */
+    protected function _getFatherWithGrandfather()
+    {
+        $grandParentRecord = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'grandparent'
+        )));
+        $relation = $this->_getParentRelationArray($grandParentRecord);
+        $this->_fatherRecord = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'father',
+            'relations' => array($relation)
+        )));
+
+        return $this->_fatherRecord;
+    }
+
+    /**
+     * @param $record
+     * @return array
+     */
+    protected function _getParentRelationArray($record)
+    {
+        return array(
+            'own_model'              => 'Addressbook_Model_Contact',
+            'own_backend'            => 'Sql',
+            'own_id'                 => 0,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
+            'type'                   => '',
+            'related_backend'        => 'Sql',
+            'related_id'             => $record->getId(),
+            'related_model'          => 'Addressbook_Model_Contact',
+            'remark'                 => NULL,
+        );
+    }
+
+    /**
+     * testBuildGroupMemberPathForContact
+     */
+    public function testBuildGroupMemberPathForContact()
+    {
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'tester',
+            'email'    => 'somemail@example.ru',
+        )));
+        $adbJson = new Addressbook_Frontend_Json();
+        $listRole = $adbJson->saveListRole(array(
+            'name'          => 'my role',
+            'description'   => 'my test description'
+        ));
+        $listRole2 = $adbJson->saveListRole(array(
+            'name'          => 'my second role',
+            'description'   => 'my test description'
+        ));
+        $memberroles = array(array(
+            'contact_id'   => $contact->getId(),
+            'list_role_id' => $listRole['id'],
+        ), array(
+            'contact_id'   => $contact->getId(),
+            'list_role_id' => $listRole2['id'],
+        ));
+        $adbJson->saveList(array(
+            'name'                  => 'my test group',
+            'description'           => '',
+            'members'               => array($contact->getId()),
+            'memberroles'           => $memberroles,
+            'type'                  => Addressbook_Model_List::LISTTYPE_LIST,
+            'relations'             => array($this->_getParentRelationArray($this->_getFatherWithGrandfather()))
+        ));
+
+        $recordPaths = $this->_uit->generatePathForRecord($contact);
+        $this->assertTrue($recordPaths instanceof Tinebase_Record_RecordSet);
+        $this->assertEquals(2, count($recordPaths), 'should find 2 path for record. paths:' . print_r($recordPaths->toArray(), true));
+        $expectedPaths = array('/grandparent/father/my test group/my role/tester', '/grandparent/father/my test group/my second role/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $recordPaths->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($recordPaths->toArray(), true));
+        }
+
+        return $contact;
+    }
+
+    /**
+     * testRebuildPathForRecords
+     */
+    public function testTriggerRebuildPathForRecords()
+    {
+        $this->_fatherRecord = $this->_getFatherWithGrandfather();
+        $relation1 = $this->_getParentRelationArray($this->_fatherRecord);
+        $contact = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'tester',
+            'relations' => array($relation1)
+        )));
+
+        $recordPaths = $this->_uit->getPathsForRecords($contact);
+        $this->assertEquals(1, count($recordPaths));
+
+        $motherRecord = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
+            'n_family' => 'mother',
+        )));
+        $relations = $contact->relations->toArray();
+        $relation2 = $this->_getParentRelationArray($motherRecord);
+        $relation2['own_id'] = $contact->getId();
+        $relations[] = $relation2;
+        $contact->relations = $relations;
+        Addressbook_Controller_Contact::getInstance()->update($contact);
+
+        $recordPaths = $this->_uit->getPathsForRecords($contact);
+        $this->assertEquals(2, count($recordPaths));
+
+        // check both paths
+        $expectedPaths = array('/grandparent/father/tester', '/mother/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $recordPaths->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($recordPaths->toArray(), true));
+        }
+
+        return $contact;
+    }
+
+    /**
+     * testTriggerRebuildIfFatherChanged
+     */
+    public function testTriggerRebuildIfFatherChanged()
+    {
+        $contact = $this->testTriggerRebuildPathForRecords();
+
+        // change contact name and check path in related records
+        $this->_fatherRecord->n_family = 'stepfather';
+        Addressbook_Controller_Contact::getInstance()->update($this->_fatherRecord);
+
+        $recordPaths = $this->_uit->getPathsForRecords($contact);
+        $this->assertEquals(2, count($recordPaths));
+
+        // check both paths again
+        $expectedPaths = array('/grandparent/stepfather/tester', '/mother/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $recordPaths->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($recordPaths->toArray(), true));
+        }
+    }
+
+    /**
+     * testTriggerRebuildIfFatherRemovedChild
+     */
+    public function testTriggerRebuildIfFatherRemovedChild()
+    {
+        $contact = $this->testTriggerRebuildPathForRecords();
+
+        // remove child relation from father and check paths of child records
+        $father = Addressbook_Controller_Contact::getInstance()->get($this->_fatherRecord->getId());
+
+        foreach($father->relations as $relation) {
+            if ($relation->related_degree === Tinebase_Model_Relation::DEGREE_CHILD) {
+                $father->relations->removeRecord($relation);
+                break;
+            }
+        }
+
+        //workaround as _setRelatedData expects an array!?!
+        $father->relations = $father->relations->toArray();
+
+        Addressbook_Controller_Contact::getInstance()->update($father);
+
+        $recordPaths = $this->_uit->getPathsForRecords($contact);
+        $this->assertEquals(1, count($recordPaths));
+
+        // check remaining path again
+        $expectedPaths = array('/mother/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $recordPaths->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($recordPaths->toArray(), true));
+        }
+    }
+
+    /**
+     * testPathFilter
+     */
+    public function testPathFilter()
+    {
+        $this->testBuildGroupMemberPathForContact();
+
+        $filterValues = array(
+            'father' => 2,
+            'grandparent' => 3,
+            'my test group' => 1,
+            'my role' => 1,
+            'somemail@example.ru' => 1
+        );
+        foreach ($filterValues as $value => $expectedCount) {
+
+            $filter = new Addressbook_Model_ContactFilter($this->_getPathFilterArray($value));
+            $result = Addressbook_Controller_Contact::getInstance()->search($filter);
+            $this->assertEquals($expectedCount, count($result),
+                'search string: ' . $value . ' / result: ' .
+                    print_r($result->toArray(), true));
+        }
+    }
+
+    protected function _getPathFilterArray($value)
+    {
+        return array(
+            array(
+                'condition' => 'OR',
+                'filters' => array(
+                    array('field' => 'query', 'operator' => 'contains', 'value' => $value),
+                    array('field' => 'path', 'operator' => 'contains', 'value' => $value)
+                )
+            )
+        );
+    }
+
+    public function testPathResolvingForContacts()
+    {
+        $this->testBuildGroupMemberPathForContact();
+
+        $adbJson = new Addressbook_Frontend_Json();
+        $filter = $this->_getPathFilterArray('father');
+
+        $result = $adbJson->searchContacts($filter, array());
+
+        $this->assertEquals(2, $result['totalcount'], print_r($result['results'], true));
+        $firstRecord = $result['results'][0];
+        $this->assertTrue(isset($firstRecord['paths']), 'paths should be set in record' . print_r($firstRecord, true));
+        // sometimes only 1 path is resolved. this is a little bit strange ...
+        $this->assertGreaterThan(0, count($firstRecord['paths']), print_r($firstRecord['paths'], true));
+        $this->assertContains('/grandparent', $firstRecord['paths'][0]['path'], 'could not find grandparent in paths of record' . print_r($firstRecord, true));
+    }
+
+    public function testPathWithDifferentTypeRelations()
+    {
+        $contact = $this->_createFatherMotherChild();
+
+        // add another relation to same record with different type
+        $relations = $contact->relations->toArray();
+        $relation2 = $this->_getParentRelationArray($this->_fatherRecord);
+        $relation2['own_id'] = $contact->getId();
+        $relation2['type'] = 'type';
+        $relations[] = $relation2;
+        $contact->relations = $relations;
+
+        $updatedContact = Addressbook_Controller_Contact::getInstance()->update($contact);
+
+        $this->assertEquals(3, count($updatedContact->relations), print_r($updatedContact->relations->toArray(), true));
+
+        $recordPaths = $this->_uit->getPathsForRecords($contact);
+
+        // check the 3 paths
+        $this->assertEquals(3, count($recordPaths), 'paths: ' . print_r($recordPaths->toArray(), true));
+        $expectedPaths = array('/grandparent/father/tester', '/mother/tester', '/grandparent/father{type}/tester');
+        foreach ($expectedPaths as $expectedPath) {
+            $this->assertTrue(in_array($expectedPath, $recordPaths->path), 'could not find path ' . $expectedPath . ' in '
+                . print_r($recordPaths->toArray(), true));
+        }
+    }
+}
index 9f7a38f..aa7589d 100644 (file)
@@ -7,6 +7,8 @@
  * @license     http://www.gnu.org/licenses/agpl.html
  * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Cornelius Weiss <c.weiss@metaways.de>
+ *
+ * @deprecated  remove me
  */
 
 /**
index fee60a5..b701f6b 100644 (file)
@@ -38,7 +38,7 @@ class Tinebase_Relation_Backend_SqlTest extends PHPUnit_Framework_TestCase
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'SQL',
             'own_id'                 => '268d586e46aad336de8fa2530b5b8faf921e494d',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_PARENT,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
             'related_model'          => 'Tasks_Model_Task',
             'related_backend'        => Tasks_Backend_Factory::SQL,
             'related_id'             => '8a572723e867dd73dd68d1740dd94f586eff5432',
@@ -48,7 +48,7 @@ class Tinebase_Relation_Backend_SqlTest extends PHPUnit_Framework_TestCase
             'own_model'              => 'Crm_Model_Lead',
             'own_backend'            => 'SQL',
             'own_id'                 => '268d586e46aad336de8fa2530b5b8faf921e494d',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_PARENT,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => Addressbook_Backend_Factory::SQL,
             'related_id'             => 'ad59dd6d2e75aa0aca0abf2ab46b55bdcb0d6b18',
@@ -58,7 +58,7 @@ class Tinebase_Relation_Backend_SqlTest extends PHPUnit_Framework_TestCase
             'own_model'              => 'Tasks_Model_Task',
             'own_backend'            => Tasks_Backend_Factory::SQL,
             'own_id'                 => '8a572723e867dd73dd68d1740dd94f586eff5432',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => Addressbook_Backend_Factory::SQL,
             'related_id'             => 'ad59dd6d2e75aa0aca0abf2ab46b55bdcb0d6b18',
index 063fd4b..5f2991b 100644 (file)
@@ -90,7 +90,7 @@ class Tinebase_Relation_RelationTest extends TestCase
                 'own_model'              => 'Crm_Model_Lead',
                 'own_backend'            => 'SQL',
                 'own_id'                 => $this->_crmId['id'],
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_SIBLING,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_SIBLING,
                 'related_model'          => 'Tasks_Model_Task',
                 'related_backend'        => Tasks_Backend_Factory::SQL,
                 'related_id'             => Tinebase_Record_Abstract::generateUID(),//'8a572723e867dd73dd68d1740dd94f586eff5432',
@@ -100,7 +100,7 @@ class Tinebase_Relation_RelationTest extends TestCase
                 'own_model'              => 'Crm_Model_Lead',
                 'own_backend'            => 'SQL',
                 'own_id'                 => $this->_crmId['id'],
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_PARENT,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
                 'related_model'          => 'Tasks_Model_Task',
                 'related_backend'        => '',
                 'related_id'             => '',
@@ -115,7 +115,7 @@ class Tinebase_Relation_RelationTest extends TestCase
                 'own_model'              => '',
                 'own_backend'            => '',
                 'own_id'                 => '',
-                'own_degree'             => Tinebase_Model_Relation::DEGREE_PARENT,
+                'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
                 'related_model'          => 'Addressbook_Model_Contact',
                 'related_backend'        => '',
                 'related_id'             => '',
@@ -250,7 +250,7 @@ class Tinebase_Relation_RelationTest extends TestCase
             'own_model'              => '',
             'own_backend'            => '',
             'own_id'                 => '',
-            'own_degree'             => Tinebase_Model_Relation::DEGREE_PARENT,
+            'related_degree'         => Tinebase_Model_Relation::DEGREE_PARENT,
             'related_model'          => 'Addressbook_Model_Contact',
             'related_backend'        => '',
             'related_id'             => '',
@@ -343,7 +343,7 @@ class Tinebase_Relation_RelationTest extends TestCase
         
         $contractJson = $contract->toArray();
         $contractJson['relations'][] = array(
-            'own_degree'     => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'  => 'Addressbook_Model_Contact',
             'related_record' => $sclever->toArray(),
             'type'           => 'CUSTOMER',
@@ -353,13 +353,13 @@ class Tinebase_Relation_RelationTest extends TestCase
         
         $contract2Json = $contract2->toArray();
         $contract2Json['relations'][] = array(
-            'own_degree'     => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'  => 'Addressbook_Model_Contact',
             'related_record' => $sclever->toArray(),
             'type'           => 'PARTNER',
         );
         $contract2Json['relations'][] = array(
-            'own_degree'     => Tinebase_Model_Relation::DEGREE_SIBLING,
+            'related_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
             'related_model'  => 'Addressbook_Model_Contact',
             'related_record' => $pwulf->toArray(),
             'type'           => 'PARTNER',
index 2e1f808..0b191e2 100644 (file)
@@ -32,7 +32,21 @@ class Addressbook_Acl_Rights extends Tinebase_Acl_Rights_Abstract
      * @staticvar string
      */
     const MANAGE_SHARED_CONTACT_FAVORITES = 'manage_shared_contact_favorites';
-    
+
+    /**
+     * the right to manage lists in core data
+     *
+     * @staticvar string
+     */
+    const MANAGE_CORE_DATA_LISTS = 'manage_core_data_lists';
+
+    /**
+     * the right to manage list roles in core data
+     *
+     * @staticvar string
+     */
+    const MANAGE_CORE_DATA_LIST_ROLES = 'manage_core_data_list_roles';
+
     /**
      * holds the instance of the singleton
      *
@@ -86,6 +100,8 @@ class Addressbook_Acl_Rights extends Tinebase_Acl_Rights_Abstract
             Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS,
             Tinebase_Acl_Rights::USE_PERSONAL_TAGS,
             self::MANAGE_SHARED_CONTACT_FAVORITES,
+            self::MANAGE_CORE_DATA_LISTS,
+            self::MANAGE_CORE_DATA_LIST_ROLES,
         );
         $allRights = array_merge($allRights, $addRights);
         
@@ -110,6 +126,14 @@ class Addressbook_Acl_Rights extends Tinebase_Acl_Rights_Abstract
                 'text'          => $translate->_('manage shared addressbook favorites'),
                 'description'   => $translate->_('Create or update shared addressbook favorites'),
             ),
+            self::MANAGE_CORE_DATA_LISTS => array(
+                'text'          => $translate->_('Manage lists in CoreData'),
+                'description'   => $translate->_('View, create, delete or update lists in CoreData application'),
+            ),
+            self::MANAGE_CORE_DATA_LIST_ROLES => array(
+                'text'          => $translate->_('Manage list roles in CoreData'),
+                'description'   => $translate->_('View, create, delete or update list roles in CoreData application'),
+            ),
         );
         
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
index aa91782..233028c 100644 (file)
           "path": "js/"
         },
         {
+          "text": "ListRoleMemberFilterModel.js",
+          "path": "js/"
+        },
+        {
           "text": "Model.js",
           "path": "js/"
         },
           "path": "js/"
         },
         {
+          "text": "ListRoleEditDialog.js",
+          "path": "js/"
+        },
+        {
           "text": "ListGrid.js",
           "path": "js/"
         },
           "path": "js/"
         },
         {
-          "text": "ListMemberGridPanel.js",
+          "text": "ListMemberRoleLayerCombo.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListMemberRoleGridPanel.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListRoleGridPanel.js",
           "path": "js/"
         },
         {
           "path": "js/"
         },
         {
-          "text": "SearchCombo.js",
+          "text": "ContactSearchCombo.js",
+          "path": "js/"
+        },
+        {
+          "text": "ListSearchCombo.js",
           "path": "js/"
         },
         {
         {
           "text": "CardDAVContainerPropertiesHookField.js",
           "path": "js/"
-        }
+        },
+        {
+          "text": "AdminPanel.js",
+          "path": "js/"
+          }
       ]
     },
     {
index 17064db..6a5ccae 100644 (file)
@@ -30,6 +30,13 @@ class Addressbook_Config extends Tinebase_Config_Abstract
     const CONTACT_SALUTATION = 'contactSalutation';
     
     /**
+     * fields for list type
+     *
+     * @var string
+     */
+    const LIST_TYPE = 'listType';
+    
+    /**
      * config for address parsing rules file
      * 
      * @var string
@@ -108,6 +115,21 @@ class Addressbook_Config extends Tinebase_Config_Abstract
             'setByAdminModule'      => FALSE,
             'setBySetupModule'      => FALSE,
         ),
+        self::LIST_TYPE => array(
+                //_('List types available')
+                'label'                 => 'List types available',
+                //_('List types available.')
+                'description'           => 'List types available.',
+                'type'                  => 'keyFieldConfig',
+                'clientRegistryInclude' => TRUE,
+                'setByAdminModule'      => true,
+                'default'               => array(
+                    'records' => array(
+                        array('id' => 'DEPARTMENT',    'value' => 'Department'), //_('Department')
+                        array('id' => 'MAILINGLIST',    'value' => 'Mailing list'), //_('Mailing list')
+                    ),
+            )
+        ),
     );
     
     /**
index ad35cda..92350fe 100644 (file)
@@ -32,6 +32,15 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
      * @var string
      */
     protected static $_defaultModel = 'Addressbook_Model_Contact';
+
+    /**
+     * Models of this application that make use of Tinebase_Record_Path
+     *
+     * @var array|null
+     */
+    protected $_modelsUsingPath = array(
+        'Addressbook_Model_Contact'
+    );
     
     /**
      * constructor (get current user)
@@ -155,4 +164,36 @@ class Addressbook_Controller extends Tinebase_Controller_Event implements Tineba
             'data'         => $image
         ));
     }
+
+    /**
+     * get core data for this application
+     *
+     * @return Tinebase_Record_RecordSet
+     */
+    public function getCoreDataForApplication()
+    {
+        $result = parent::getCoreDataForApplication();
+
+        $application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
+
+        if (Tinebase_Core::getUser()->hasRight($application, Addressbook_Acl_Rights::MANAGE_CORE_DATA_LISTS)) {
+            $result->addRecord(new CoreData_Model_CoreData(array(
+                'id' => 'adb_lists',
+                'application_id' => $application,
+                'model' => 'Addressbook_Model_List',
+                'label' => 'Lists' // _('Lists')
+            )));
+        }
+
+        if (Tinebase_Core::getUser()->hasRight($application, Addressbook_Acl_Rights::MANAGE_CORE_DATA_LIST_ROLES)) {
+            $result->addRecord(new CoreData_Model_CoreData(array(
+                'id' => 'adb_list_roles',
+                'application_id' => $application,
+                'model' => 'Addressbook_Model_ListRole',
+                'label' => 'List Roles' // _('List Roles')
+            )));
+        }
+
+        return $result;
+    }
 }
index 4063f3a..2ce8756 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Controller
  * @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-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  * 
  */
 
@@ -24,13 +24,14 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
      * @var boolean
      */
     protected $_setGeoDataForContacts = FALSE;
-    
+
     /**
      * the constructor
      *
      * don't use the constructor. use the singleton 
      */
-    private function __construct() {
+    private function __construct()
+    {
         $this->_applicationName = 'Addressbook';
         $this->_modelName = 'Addressbook_Model_Contact';
         $this->_backend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
@@ -41,6 +42,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             array('n_given', 'n_family', 'org_name'),
             array('email'),
         ));
+        $this->_useRecordPaths = true;
         
         // fields used for private and company address
         $this->_addressFields = array('locality', 'postalcode', 'street', 'countryname');
@@ -203,6 +205,10 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             $oldRecordArray = $currentRecord->toArray();
             $data = array_merge($oldRecordArray, $_data);
 
+            if ($this->_newRelations || $this->_removeRelations) {
+                $data['relations'] = $this->_iterateRelations($currentRecord);
+            }
+
             try {
                 $record = new $this->_modelName($data);
                 $record->__set('jpegphoto', NULL);
@@ -257,7 +263,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
     
     /**
      * inspect update of one record (after update)
-     * 
+     *
      * @param   Tinebase_Record_Interface $updatedRecord   the just updated record
      * @param   Tinebase_Record_Interface $record          the update record
      * @param   Tinebase_Record_Interface $currentRecord   the current record (before update)
@@ -269,7 +275,7 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             Tinebase_User::getInstance()->updateContact($updatedRecord);
         }
     }
-    
+
     /**
      * delete one record
      * - don't delete if it belongs to an user account
@@ -547,4 +553,60 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
                     
         return $result;
     }
+
+    /**
+     * generates path for the contact
+     *
+     * - we add to the path:
+     *      - lists contact is member of
+     *      - we add list role memberships
+     *
+     * @param Tinebase_Record_Abstract $record
+     * @return Tinebase_Record_RecordSet
+     */
+    public function generatePathForRecord($record)
+    {
+        $result = new Tinebase_Record_RecordSet('Tinebase_Model_Path');
+
+        // fetch all groups and role memberships and add to path
+        $listIds = Addressbook_Controller_List::getInstance()->getMemberships($record);
+        foreach ($listIds as $listId) {
+            $list = Addressbook_Controller_List::getInstance()->get($listId);
+            $listPaths = $this->_getPathsOfRecord($list);
+            if (count($listPaths) === 0) {
+                // add self
+                $listPaths->addRecord(new Tinebase_Model_Path(array(
+                    'path'          => $this->_getPathPart($list),
+                    'shadow_path'   => '/' . $list->getId(),
+                    'record_id'     => $list->getId(),
+                    'creation_time' => Tinebase_DateTime::now(),
+                )));
+            }
+
+            foreach ($listPaths as $listPath) {
+                if (count($list->memberroles) > 0) {
+                    foreach ($list->memberroles as $role) {
+                        $rolePath = clone($listPath);
+                        if ($role->contact_id === $record->getId()) {
+                            $role = Addressbook_Controller_ListRole::getInstance()->get($role->list_role_id);
+                            $rolePath->path .= $this->_getPathPart($role);
+                            $rolePath->shadow_path .= '/' . $role->getId();
+                            $rolePath->record_id = $role->getId();
+                            $result->addRecord($rolePath);
+                        }
+                    }
+                } else {
+                    $result->addRecord($listPath);
+                }
+            }
+        }
+
+        foreach ($result as $listPath) {
+            $listPath->path .= $this->_getPathPart($record);
+            $listPath->shadow_path .= '/' . $record->getId();
+            $listPath->record_id = $record->getId();
+        }
+
+        return $result;
+    }
 }
index e156aaf..ac09ba6 100644 (file)
@@ -24,55 +24,74 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
      * @var string
      */
     protected $_applicationName = 'Addressbook';
-    
+
     /**
      * Model name
      *
      * @var string
      */
     protected $_modelName = 'Addressbook_Model_List';
-    
+
+    /**
+     * @var null|Tinebase_Backend_Sql
+     */
+    protected $_memberRolesBackend = null;
+
     /**
      * the constructor
      *
-     * don't use the constructor. use the singleton 
+     * don't use the constructor. use the singleton
      */
     private function __construct()
     {
+        $this->_resolveCustomFields = true;
         $this->_backend = new Addressbook_Backend_List();
     }
-    
+
     /**
      * don't clone. Use the singleton.
      *
      */
-    private function __clone() 
+    private function __clone()
     {
     }
-    
+
     /**
      * holds the instance of the singleton
      *
      * @var Addressbook_Controller_List
      */
     private static $_instance = NULL;
-    
+
+    protected function _getMemberRolesBackend()
+    {
+        if ($this->_memberRolesBackend === null) {
+            $this->_memberRolesBackend = new Tinebase_Backend_Sql(array(
+                'tableName' => 'adb_list_m_role',
+                'modelName' => 'Addressbook_Model_ListMemberRole',
+            ));
+        }
+
+        return $this->_memberRolesBackend;
+    }
+
     /**
      * the singleton pattern
      *
      * @return Addressbook_Controller_List
      */
-    public static function getInstance() 
+    public static function getInstance()
     {
         if (self::$_instance === NULL) {
             self::$_instance = new Addressbook_Controller_List();
         }
-        
+
         return self::$_instance;
     }
-    
+
     /**
      * (non-PHPdoc)
+     *
      * @see Tinebase_Controller_Record_Abstract::get()
      */
     public function get($_id, $_containerId = NULL)
@@ -81,10 +100,10 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
         $this->_removeHiddenListMembers($result);
         return $result->getFirstRecord();
     }
-    
+
     /**
      * use contact search to remove hidden list members
-     * 
+     *
      * @param Tinebase_Record_RecordSet $lists
      */
     protected function _removeHiddenListMembers($lists)
@@ -92,54 +111,56 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
         if (count($lists) === 0) {
             return;
         }
-        
+
         $allMemberIds = array();
         foreach ($lists as $list) {
             $allMemberIds = array_merge($list->members, $allMemberIds);
         }
         $allMemberIds = array_unique($allMemberIds);
-        
+
         if (empty($allMemberIds)) {
-            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                 . ' No members found.');
             return;
         }
-        
+
         $allVisibleMemberIds = Addressbook_Controller_Contact::getInstance()->search(new Addressbook_Model_ContactFilter(array(array(
-            'field'    => 'id',
+            'field' => 'id',
             'operator' => 'in',
-            'value'    => $allMemberIds
+            'value' => $allMemberIds
         ))), NULL, FALSE, TRUE);
-        
+
         $hiddenMemberids = array_diff($allMemberIds, $allVisibleMemberIds);
-        
-        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
             . ' Found ' . count($hiddenMemberids) . ' hidden members, removing them');
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
             . print_r($hiddenMemberids, TRUE));
-        
+
         foreach ($lists as $list) {
             $list->members = array_diff($list->members, $hiddenMemberids);
         }
     }
-    
+
     /**
      * (non-PHPdoc)
+     *
      * @see Tinebase_Controller_Record_Abstract::search()
      */
     public function search(Tinebase_Model_Filter_FilterGroup $_filter = NULL, Tinebase_Record_Interface $_pagination = NULL, $_getRelations = FALSE, $_onlyIds = FALSE, $_action = 'get')
     {
         $result = parent::search($_filter, $_pagination, $_getRelations, $_onlyIds, $_action);
-        
+
         if ($_onlyIds !== true) {
             $this->_removeHiddenListMembers($result);
         }
-        
+
         return $result;
     }
-    
+
     /**
      * (non-PHPdoc)
+     *
      * @see Tinebase_Controller_Record_Abstract::getMultiple()
      */
     public function getMultiple($_ids, $_ignoreACL = FALSE)
@@ -148,12 +169,12 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
         $this->_removeHiddenListMembers($result);
         return $result;
     }
-    
+
     /**
      * add new members to list
-     * 
-     * @param  mixed  $_listId
-     * @param  mixed  $_newMembers
+     *
+     * @param  mixed $_listId
+     * @param  mixed $_newMembers
      * @return Addressbook_Model_List
      */
     public function addListMember($_listId, $_newMembers)
@@ -164,70 +185,70 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
             $list = $this->_fixEmptyContainerId($_listId);
             $list = $this->get($_listId);
         }
-        
+
         $this->_checkGrant($list, 'update', TRUE, 'No permission to add list member.');
-        
+
         $list = $this->_backend->addListMember($_listId, $_newMembers);
-        
+
         return $this->get($list->getId());
     }
-    
+
     /**
      * fixes empty container ids / perhaps this can be removed later as all lists should have a container id!
-     * 
-     * @param  mixed  $_listId
+     *
+     * @param  mixed $_listId
      * @return Addressbook_Model_List
      */
     protected function _fixEmptyContainerId($_listId)
     {
         $list = $this->_backend->get($_listId);
-        
+
         if (empty($list->container_id)) {
             $list->container_id = $this->_getDefaultInternalAddressbook();
             $list = $this->_backend->update($list);
         }
-        
+
         return $list;
     }
-    
+
     /**
      * get default internal adb id
-     * 
+     *
      * @return string
      */
     protected function _getDefaultInternalAddressbook()
     {
         $appConfigDefaults = Admin_Controller::getInstance()->getConfigSettings();
         $result = (isset($appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK])) ? $appConfigDefaults[Admin_Model_Config::DEFAULTINTERNALADDRESSBOOK] : NULL;
-        
+
         if (empty($result)) {
-            if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ 
+            if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
                 . ' Default internal addressbook not found. Creating new config setting.');
             $result = Addressbook_Setup_Initialize::setDefaultInternalAddressbook()->getId();
         }
         return $result;
     }
-    
+
     /**
      * remove members from list
-     * 
-     * @param  mixed  $_listId
-     * @param  mixed  $_newMembers
+     *
+     * @param  mixed $_listId
+     * @param  mixed $_newMembers
      * @return Addressbook_Model_List
      */
     public function removeListMember($_listId, $_newMembers)
     {
         $list = $this->get($_listId);
-        
+
         $this->_checkGrant($list, 'update', TRUE, 'No permission to remove list member.');
         $list = $this->_backend->removeListMember($_listId, $_newMembers);
-        
+
         return $this->get($list->getId());
     }
-    
+
     /**
      * inspect creation of one record
-     * 
+     *
      * @param   Tinebase_Record_Interface $_record
      * @return  void
      */
@@ -237,35 +258,90 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
             throw new Addressbook_Exception_InvalidArgument('can not add list of type ' . Addressbook_Model_List::LISTTYPE_GROUP);
         }
     }
-    
+
+    /**
+     * 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)
+    {
+        $this->_fireChangeListeEvent($_createdRecord);
+    }
+
     /**
      * inspect update of one record
-     * 
-     * @param   Tinebase_Record_Interface $_record      the update record
-     * @param   Tinebase_Record_Interface $_oldRecord   the current persistent record
+     *
+     * @param   Tinebase_Record_Interface $_record the update record
+     * @param   Tinebase_Record_Interface $_oldRecord the current persistent record
      * @return  void
      */
     protected function _inspectBeforeUpdate($_record, $_oldRecord)
     {
-        if (isset($record->type) &&  $record->type == Addressbook_Model_List::LISTTYPE_GROUP) {
-            throw new Addressbook_Exception_InvalidArgument('can not add list of type ' . Addressbook_Model_List::LISTTYPE_GROUP);
+        if (isset($record->type) && $record->type == Addressbook_Model_List::LISTTYPE_GROUP) {
+            throw new Addressbook_Exception_InvalidArgument('can not update list of type ' . Addressbook_Model_List::LISTTYPE_GROUP);
+        }
+    }
+
+    /**
+     * inspect update of one record (after update)
+     *
+     * @param   Tinebase_Record_Interface $updatedRecord   the just updated record
+     * @param   Tinebase_Record_Interface $record          the update record
+     * @param   Tinebase_Record_Interface $currentRecord   the current record (before update)
+     * @return  void
+     */
+    protected function _inspectAfterUpdate($updatedRecord, $record, $currentRecord)
+    {
+        $this->_fireChangeListeEvent($updatedRecord);
+    }
+
+    /**
+     * fireChangeListeEvent
+     *
+     * @param Addressbook_Model_List $list
+     */
+    protected function _fireChangeListeEvent(Addressbook_Model_List $list)
+    {
+        $event = new Addressbook_Event_ChangeList();
+        $event->list = $list;
+        Tinebase_Event::fireEvent($event);
+    }
+
+    /**
+     * inspects delete action
+     *
+     * @param array $_ids
+     * @return array of ids to actually delete
+     */
+    protected function _inspectDelete(array $_ids)
+    {
+        $lists = $this->getMultiple($_ids);
+        foreach ($lists as $list) {
+            $event = new Addressbook_Event_DeleteList();
+            $event->list = $list;
+            Tinebase_Event::fireEvent($event);
         }
+
+        return $_ids;
     }
-    
+
     /**
      * create or update list in addressbook sql backend
-     * 
-     * @param  Tinebase_Model_Group  $group
+     *
+     * @param  Tinebase_Model_Group $group
      * @return Addressbook_Model_List
      */
     public function createOrUpdateByGroup(Tinebase_Model_Group $group)
     {
         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($group->toArray(), TRUE));
-        
+
         try {
             if (empty($group->list_id)) {
                 $list = $this->_backend->getByGroupName($group->name);
-                if (! $list) {
+                if (!$list) {
                     // jump to catch block => no list_id provided and no existing list for group found
                     throw new Tinebase_Exception_NotFound('list_id is empty');
                 }
@@ -273,95 +349,173 @@ class Addressbook_Controller_List extends Tinebase_Controller_Record_Abstract
             } else {
                 $list = $this->_backend->get($group->list_id);
             }
-        
+
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
                 . ' Update list ' . $group->name);
-        
-            $list->name         = $group->name;
-            $list->description  = $group->description;
-            $list->email        = $group->email;
-            $list->type         = Addressbook_Model_List::LISTTYPE_GROUP;
+
+            $list->name = $group->name;
+            $list->description = $group->description;
+            $list->email = $group->email;
+            $list->type = Addressbook_Model_List::LISTTYPE_GROUP;
             $list->container_id = (empty($group->container_id)) ? $this->_getDefaultInternalAddressbook() : $group->container_id;
-            $list->members      = (isset($group->members)) ? $this->_getContactIds($group->members) : array();
-        
+            $list->members = (isset($group->members)) ? $this->_getContactIds($group->members) : array();
+
             // add modlog info
             Tinebase_Timemachine_ModificationLog::setRecordMetaData($list, 'update');
-        
+
             $list = $this->_backend->update($list);
             $list = $this->get($list->getId());
-        
+
         } catch (Tinebase_Exception_NotFound $tenf) {
             $list = $this->createByGroup($group);
         }
-        
+
         return $list;
     }
-    
+
     /**
      * create new list by group
-     * 
+     *
      * @param Tinebase_Model_Group $group
      * @return Addressbook_Model_List
      */
     public function createByGroup($group)
     {
         $list = new Addressbook_Model_List(array(
-            'name'          => $group->name,
-            'description'   => $group->description,
-            'email'         => $group->email,
-            'type'          => Addressbook_Model_List::LISTTYPE_GROUP,
-            'container_id'  => (empty($group->container_id)) ? $this->_getDefaultInternalAddressbook() : $group->container_id,
-            'members'       => (isset($group->members)) ? $this->_getContactIds($group->members) : array(),
+            'name' => $group->name,
+            'description' => $group->description,
+            'email' => $group->email,
+            'type' => Addressbook_Model_List::LISTTYPE_GROUP,
+            'container_id' => (empty($group->container_id)) ? $this->_getDefaultInternalAddressbook() : $group->container_id,
+            'members' => (isset($group->members)) ? $this->_getContactIds($group->members) : array(),
         ));
-        
+
         // add modlog info
         Tinebase_Timemachine_ModificationLog::setRecordMetaData($list, 'create');
-        
+
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
             . ' Add new list ' . $group->name);
-        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
             . ' ' . print_r($list->toArray(), TRUE));
-        
+
         $list = $this->_backend->create($list);
-        
+
         return $list;
     }
-    
+
     /**
-    * get contact_ids of users
-    *
-    * @param  array  $_userIds
-    * @return array
-    */
+     * get contact_ids of users
+     *
+     * @param  array $_userIds
+     * @return array
+     */
     protected function _getContactIds($_userIds)
     {
         $contactIds = array();
-    
+
         if (empty($_userIds)) {
             return $contactIds;
         }
-    
+
         foreach ($_userIds as $userId) {
             $user = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $userId);
-            if (! empty($user->contact_id)) {
+            if (!empty($user->contact_id)) {
                 $contactIds[] = $user->contact_id;
             }
         }
-    
+
         return $contactIds;
     }
-    
+
     /**
-    * you can define default filters here
-    *
-    * @param Tinebase_Model_Filter_FilterGroup $_filter
-    */
+     * you can define default filters here
+     *
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     */
     protected function _addDefaultFilter(Tinebase_Model_Filter_FilterGroup $_filter = NULL)
     {
-        if (! $_filter->isFilterSet('showHidden')) {
+        if (!$_filter->isFilterSet('showHidden')) {
             $hiddenFilter = $_filter->createFilter('showHidden', 'equals', FALSE);
             $hiddenFilter->setIsImplicit(TRUE);
             $_filter->addFilter($hiddenFilter);
         }
     }
+
+    /**
+     * set relations / tags / alarms
+     *
+     * @param   Tinebase_Record_Interface $updatedRecord the just updated record
+     * @param   Tinebase_Record_Interface $record the update record
+     * @param   Tinebase_Record_Interface $currentRecord   the original record if one exists
+     * @param   boolean                   $returnUpdatedRelatedData
+     * @return  Tinebase_Record_Interface
+     */
+    protected function _setRelatedData(Tinebase_Record_Interface $updatedRecord, Tinebase_Record_Interface $record, Tinebase_Record_Interface $currentRecord = null, $returnUpdatedRelatedData = FALSE)
+    {
+        if (isset($record->memberroles)) {
+            // get migration
+            // TODO add generic helper fn for this?
+            $memberrolesToSet = (!$record->memberroles instanceof Tinebase_Record_RecordSet)
+                ? new Tinebase_Record_RecordSet(
+                    'Addressbook_Model_ListMemberRole',
+                    $record->memberroles,
+                    /* $_bypassFilters */ true
+                ) : $record->memberroles;
+
+            foreach ($memberrolesToSet as $memberrole) {
+                foreach (array('contact_id', 'list_role_id', 'list_id') as $field) {
+                    if (isset($memberrole[$field]['id'])) {
+                        $memberrole[$field] = $memberrole[$field]['id'];
+                    }
+                }
+            }
+
+            $currentMemberroles = $this->_getMemberRoles($record);
+            $diff = $currentMemberroles->diff($memberrolesToSet);
+            if (count($diff['added']) > 0) {
+                $diff['added']->list_id = $updatedRecord->getId();
+                foreach ($diff['added'] as $memberrole) {
+                    $this->_getMemberRolesBackend()->create($memberrole);
+                }
+            }
+            if (count($diff['removed']) > 0) {
+                $this->_getMemberRolesBackend()->delete($diff['removed']->getArrayOfIds());
+            }
+        }
+
+        $result = parent::_setRelatedData($updatedRecord, $record, $currentRecord, $returnUpdatedRelatedData);
+
+        return $result;
+    }
+
+    /**
+     * add related data to record
+     *
+     * @param Tinebase_Record_Interface $record
+     */
+    protected function _getRelatedData($record)
+    {
+        $memberRoles = $this->_getMemberRoles($record);
+        if (count($memberRoles) > 0) {
+            $record->memberroles = $memberRoles;
+        }
+        parent::_getRelatedData($record);
+    }
+
+    protected function _getMemberRoles($record)
+    {
+        $result = $this->_getMemberRolesBackend()->getMultipleByProperty($record->getId(), 'list_id');
+        return $result;
+    }
+
+    /**
+     * get all lists given contact is member of
+     *
+     * @param $contact
+     * @return array
+     */
+    public function getMemberships($contact)
+    {
+        return $this->_backend->getMemberships($contact);
+    }
 }
diff --git a/tine20/Addressbook/Controller/ListRole.php b/tine20/Addressbook/Controller/ListRole.php
new file mode 100644 (file)
index 0000000..ad8654d
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @subpackage  Controller
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ */
+
+/**
+ * ListRole controller for Addressbook
+ *
+ * @package     Addressbook
+ * @subpackage  Controller
+ */
+class Addressbook_Controller_ListRole extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * the constructor
+     *
+     * don't use the constructor. use the singleton 
+     */
+    private function __construct()
+    {
+        $this->_doContainerACLChecks = false;
+        $this->_applicationName = 'Addressbook';
+        $this->_modelName = 'Addressbook_Model_ListRole';
+        $this->_backend = new Tinebase_Backend_Sql(array(
+            'modelName'     => 'Addressbook_Model_ListRole',
+            'tableName'     => 'addressbook_list_role',
+            'modlogActive'  => true
+        ));
+        $this->_purgeRecords = FALSE;
+    }
+    
+    /**
+     * don't clone. Use the singleton.
+     *
+     */
+    private function __clone() 
+    {
+    }
+    
+    /**
+     * holds the instance of the singleton
+     *
+     * @var Addressbook_Controller_ListRole
+     */
+    private static $_instance = NULL;
+    
+    /**
+     * the singleton pattern
+     *
+     * @return Addressbook_Controller_ListRole
+     */
+    public static function getInstance() 
+    {
+        if (self::$_instance === NULL) {
+            self::$_instance = new Addressbook_Controller_ListRole();
+        }
+        
+        return self::$_instance;
+    }
+}
index c219428..477930c 100644 (file)
@@ -20,8 +20,7 @@ class Addressbook_Convert_Contact_Json extends Tinebase_Convert_Json
    /**
     * parent converts Tinebase_Record_RecordSet to external format
     * this resolves Image Paths
-    * @TODO: Can be removed when "0000284: modlog of contact images / move images to vfs" is resolved.
-    * 
+    *
     * @param Tinebase_Record_RecordSet  $_records
     * @param Tinebase_Model_Filter_FilterGroup $_filter
     * @param Tinebase_Model_Pagination $_pagination
@@ -32,9 +31,32 @@ class Addressbook_Convert_Contact_Json extends Tinebase_Convert_Json
         if (count($_records) == 0) {
             return array();
         }
-        
+
+        // TODO: Can be removed when "0000284: modlog of contact images / move images to vfs" is resolved.
         Addressbook_Frontend_Json::resolveImages($_records);
-        
-        return parent::fromTine20RecordSet($_records, $_filter, $_pagination);
+
+        $this->_appendRecordPaths($_records, $_filter);
+
+        $result = parent::fromTine20RecordSet($_records, $_filter, $_pagination);
+
+        return $result;
+    }
+
+    /**
+     * append record paths (if path filter is set)
+     *
+     * @param Tinebase_Record_RecordSet $_records
+     * @param Tinebase_Model_Filter_FilterGroup $_filter
+     *
+     * TODO move to generic json converter
+     */
+    protected function _appendRecordPaths($_records, $_filter)
+    {
+        if ($_filter && $_filter->getFilter('path', /* $_getAll = */ false, /* $_recursive = */ true) !== null) {
+            $recordPaths = Tinebase_Record_Path::getInstance()->getPathsForRecords($_records);
+            foreach ($_records as $record) {
+                $record->paths = $recordPaths->filter('record_id', $record->getId());
+            }
+        }
     }
 }
index 7d37e46..47afd88 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Convert
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2011-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -23,6 +23,13 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
      * where the etag is checked
      */
     const OPTION_USE_SERVER_MODLOG = 'useServerModlog';
+
+    /**
+     * photo size
+     *
+     * @var integer
+     */
+    protected $_maxPhotoSize = Addressbook_Model_Contact::SMALL_PHOTO_SIZE;
     
     /**
      * the version string
@@ -37,6 +44,13 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
     public function __construct($_version = null)
     {
         $this->_version = $_version;
+
+        if (isset($_REQUEST['max_photo_size'])) {
+            $this->_maxPhotoSize = (int) $_REQUEST['max_photo_size'];
+
+            if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
+                Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' maxPhotoSize set to ' . $this->_maxPhotoSize);
+        }
     }
     
     /**
@@ -216,7 +230,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
         $contact->setFromArray($data);
 
         if (isset($jpegphoto)) {
-            $contact->setSmallContactImage($jpegphoto);
+            $contact->setSmallContactImage($jpegphoto, $this->_maxPhotoSize);
         }
         
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
@@ -406,7 +420,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
     {
         if (! empty($record->jpegphoto)) {Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__);
             try {
-                $jpegData = $record->getSmallContactImage();
+                $jpegData = $record->getSmallContactImage($this->_maxPhotoSize);
                 $card->add('PHOTO',  $jpegData, array('TYPE' => 'JPEG', 'ENCODING' => 'b'));
             } catch (Exception $e) {
                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) 
diff --git a/tine20/Addressbook/Convert/List/Json.php b/tine20/Addressbook/Convert/List/Json.php
new file mode 100644 (file)
index 0000000..0a896da
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ * convert functions for records from/to json (array) format
+ * 
+ * @package     Addressbook
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Alexander Stintzing <a.stintzing@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * convert functions for records from/to json (array) format
+ *
+ * @package     Addressbook
+ * @subpackage  Convert
+ */
+class Addressbook_Convert_List_Json extends Tinebase_Convert_Json
+{
+    /**
+     * resolves child records before converting the record set to an array
+     *
+     * @param Tinebase_Record_RecordSet $records
+     * @param Tinebase_ModelConfiguration $modelConfiguration
+     * @param boolean $multiple
+     */
+    protected function _resolveBeforeToArray($records, $modelConfiguration, $multiple = false)
+    {
+        $this->_resolveMemberroles($records);
+
+        parent::_resolveBeforeToArray($records, $modelConfiguration, $multiple);
+    }
+
+    /**
+     * resolve memberroles
+     *
+     * @param $records
+     */
+    protected function _resolveMemberroles($records)
+    {
+        $listRoles = Addressbook_Controller_ListRole::getInstance()->getAll();
+        $contactIds = array();
+        foreach ($records as $record) {
+            if (isset($record->memberroles)) {
+                $contactIds = array_merge($contactIds, $record->memberroles->contact_id);
+            }
+        }
+        if (count($contactIds) > 0) {
+            $contacts = Addressbook_Controller_Contact::getInstance()->getMultiple($contactIds);
+        }
+        foreach ($records as $list) {
+            if (isset($record->memberroles)) {
+                foreach ($list->memberroles as $memberrole) {
+                    $contact = $contacts->getById($memberrole->contact_id);
+                    if ($contact) {
+                        $memberrole->contact_id = $contact;
+                    }
+                    $listRole = $listRoles->getById($memberrole->list_role_id);
+                    if ($listRole) {
+                        $memberrole->list_role_id = $listRole;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tine20/Addressbook/Event/ChangeList.php b/tine20/Addressbook/Event/ChangeList.php
new file mode 100644 (file)
index 0000000..3bb8d38
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * event class for changed list
+ *
+ * @package     Admin
+ */
+class Addressbook_Event_ChangeList extends Tinebase_Event_Abstract
+{
+    /**
+     * the list object
+     *
+     * @var Addressbook_Model_List
+     */
+    public $list;
+}
diff --git a/tine20/Addressbook/Event/DeleteList.php b/tine20/Addressbook/Event/DeleteList.php
new file mode 100644 (file)
index 0000000..5c7627c
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * event class for changed list
+ *
+ * @package     Admin
+ */
+class Addressbook_Event_DeleteList extends Tinebase_Event_Abstract
+{
+    /**
+     * the list object
+     *
+     * @var Addressbook_Model_List
+     */
+    public $list;
+}
index 4489548..fe2a779 100644 (file)
@@ -31,17 +31,7 @@ class Addressbook_Export_Doc extends Tinebase_Export_Richtext_Doc {
     protected $_modelName = 'Contact';
     
     protected $_defaultExportname = 'adb_default_doc';
-    
-    
-    protected function _onAfterExportRecords($result)
-    {
-        $user = Tinebase_Core::getUser();
-        
-        $this->_docTemplate->setValue('date', Tinebase_DateTime::now()->format('Y-m-d'));
-        $this->_docTemplate->setValue('account_n_given', $user->accountFirstName);
-        $this->_docTemplate->setValue('account_n_family', $user->accountLastName);
-    }
-    
+
     public function processIteration($_records)
     {
         $record = $_records->getFirstRecord();
index f351f01..9aae97b 100644 (file)
@@ -6,12 +6,25 @@
     <plugin>Addressbook_Export_Doc</plugin>
     <template>addressbook_contact_letter.docx</template>
     <description>default doc letter definition</description>
-    <properties>
-        <prop>org_name</prop>
-        <prop>adr_one_locality</prop>
-        <prop>adr_one_street</prop>
-        <prop>adr_one_postalcode</prop>
-        <prop>n_given</prop>
-        <prop>n_family</prop>
-    </properties>
+    <dateformat>YYYY-MM-dd</dateformat>
+    <timeformat>HH:mm</timeformat>
+    <!-- NOTE in template export we generally add all fields with their internal name
+     it depends on the template to use it or not.
+
+     Columns can be defined to export fields in a non standard way
+
+     NOTE: you need to define two columns at minimum as Zend_Config can't cope with just one and
+           we use the first column as the field to expand rows with
+-->
+    <columns>
+        <column>
+            <identifier>bday</identifier>
+            <header>Birthday</header>
+            <format>YYYY-MM-dd</format>
+        </column>
+        <column>
+            <identifier>someother</identifier>
+        </column>
+    </columns>
+
 </config>
index aeb3f2e..70442f4 100644 (file)
Binary files a/tine20/Addressbook/Export/templates/addressbook_contact_letter.docx and b/tine20/Addressbook/Export/templates/addressbook_contact_letter.docx differ
index 0eaf219..37ea7ab 100755 (executable)
@@ -196,7 +196,52 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     {
         return $this->_delete($ids, Addressbook_Controller_Contact::getInstance());
     }
-    
+
+    /**
+     * get one list role identified by $id
+     *
+     * @param string $id
+     * @return array
+     */
+    public function getListRole($id)
+    {
+        return $this->_get($id, Addressbook_Controller_ListRole::getInstance());
+    }
+
+    /**
+     * Search for lists roles matching given arguments
+     *
+     * @param  array $filter
+     * @param  array $paging
+     * @return array
+     */
+    public function searchListRoles($filter, $paging)
+    {
+        return $this->_search($filter, $paging, Addressbook_Controller_ListRole::getInstance(), 'Addressbook_Model_ListRoleFilter');
+    }
+
+    /**
+     * delete multiple list roles
+     *
+     * @param array $ids list of listId's to delete
+     * @return array
+     */
+    public function deleteListRoles($ids)
+    {
+        return $this->_delete($ids, Addressbook_Controller_ListRole::getInstance());
+    }
+
+    /**
+     * save list role
+     *
+     * @param $recordData
+     * @return array
+     */
+    public function saveListRole($recordData)
+    {
+        return $this->_save($recordData, Addressbook_Controller_ListRole::getInstance(), 'ListRole');
+    }
+
     /**
      * save one contact
      *
index 7ca307e..98b2760 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Model
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -217,7 +217,8 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
             Zend_Filter_Input::ALLOW_EMPTY => true,
             Zend_Filter_Input::DEFAULT_VALUE => self::CONTACTTYPE_CONTACT,
             array('InArray', array(self::CONTACTTYPE_USER, self::CONTACTTYPE_CONTACT)),
-        )
+        ),
+        'paths'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
     );
     
     /**
@@ -397,12 +398,13 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
      * set small contact image
      *
      * @param $newPhotoBlob
+     * @param $maxSize
      */
-    public function setSmallContactImage($newPhotoBlob)
+    public function setSmallContactImage($newPhotoBlob, $maxSize = self::SMALL_PHOTO_SIZE)
     {
         if ($this->getId()) {
             try {
-                $currentPhoto = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId())->getBlob('image/jpeg', self::SMALL_PHOTO_SIZE);
+                $currentPhoto = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId())->getBlob('image/jpeg', $maxSize);
             } catch (Exception $e) {
                 // no current photo
             }
@@ -421,13 +423,25 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
     /**
      * return small contact image for sync
      *
+     * @param $maxSize
+     *
      * @return string
      * @throws Tinebase_Exception_InvalidArgument
      * @throws Tinebase_Exception_NotFound
      */
-    public function getSmallContactImage()
+    public function getSmallContactImage($maxSize = self::SMALL_PHOTO_SIZE)
     {
         $image = Tinebase_Controller::getInstance()->getImage('Addressbook', $this->getId());
-        return $image->getBlob('image/jpeg', self::SMALL_PHOTO_SIZE);
+        return $image->getBlob('image/jpeg', $maxSize);
+    }
+
+    /**
+     * get title
+     *
+     * @return string
+     */
+    public function getTitle()
+    {
+        return $this->n_fn;
     }
 }
index 0c5ba58..6fb55fa 100644 (file)
@@ -44,7 +44,12 @@ class Addressbook_Model_ContactFilter extends Tinebase_Model_Filter_FilterGroup
             'filter' => 'Tinebase_Model_Filter_Query', 
             'options' => array('fields' => array('n_family', 'n_given', 'org_name', 'org_unit', 'email', 'email_home', 'adr_one_locality'))
         ),
+        'path'                => array(
+            'filter' => 'Tinebase_Model_Filter_Path',
+            'options' => array()
+        ),
         'list'                 => array('filter' => 'Addressbook_Model_ListMemberFilter'),
+        'list_role_id'         => array('filter' => 'Addressbook_Model_ListRoleMemberFilter'),
         'telephone'            => array(
             'filter' => 'Tinebase_Model_Filter_Query', 
             'options' => array('fields' => array(
index e24792c..4a77e0e 100644 (file)
@@ -80,9 +80,16 @@ class Addressbook_Model_List extends Tinebase_Record_Abstract
             Zend_Filter_Input::DEFAULT_VALUE => self::LISTTYPE_LIST,
             array('InArray', array(self::LISTTYPE_LIST, self::LISTTYPE_GROUP)),
         ),
+        'list_type'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'group_id'              => array(Zend_Filter_Input::ALLOW_EMPTY => true),
         'tags'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
-        'emails'                => array(Zend_Filter_Input::ALLOW_EMPTY => true)
+        'emails'                => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'memberroles'           => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        // tine 2.0 generic fields
+        'tags'                  => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'notes'                 => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'relations'             => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'customfields'          => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => array()),
     );
     
     /**
index 4e107d8..1d4f310 100644 (file)
@@ -55,6 +55,7 @@ class Addressbook_Model_ListFilter extends Tinebase_Model_Filter_FilterGroup
         'created_by'           => array('filter' => 'Tinebase_Model_Filter_User'),
         'container_id'         => array('filter' => 'Tinebase_Model_Filter_Container', 'options' => array('applicationName' => 'Addressbook')),
         'type'                 => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'list_type'            => array('filter' => 'Tinebase_Model_Filter_Text'),
         'showHidden'           => array('filter' => 'Addressbook_Model_ListHiddenFilter'),
     );
 }
diff --git a/tine20/Addressbook/Model/ListMemberRole.php b/tine20/Addressbook/Model/ListMemberRole.php
new file mode 100644 (file)
index 0000000..959c769
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @subpackage  Model
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+class Addressbook_Model_ListMemberRole extends Tinebase_Record_Abstract
+{
+    /**
+     * application the record belongs to
+     *
+     * @var string
+     */
+    protected $_application = 'Addressbook';
+
+    /**
+     * key in $_validators/$_properties array for the field which
+     * represents the identifier
+     *
+     * @var string
+     */
+    protected $_identifier = 'id';
+
+    /**
+     * (non-PHPdoc)
+     * @see tine20/Tinebase/Record/Abstract::$_validators
+     */
+    protected $_validators = array(
+        // tine record fields
+        'id'                   => array('allowEmpty' => true,         ),
+
+        // record specific
+        'list_id'              => array('allowEmpty' => false         ),
+        'list_role_id'         => array('allowEmpty' => false         ),
+        'contact_id'           => array('allowEmpty' => false         ),
+    );
+}
diff --git a/tine20/Addressbook/Model/ListRole.php b/tine20/Addressbook/Model/ListRole.php
new file mode 100644 (file)
index 0000000..7d4d1c9
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @subpackage  Model
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+class Addressbook_Model_ListRole extends Tinebase_Record_Simple
+{
+    /**
+     * application the record belongs to
+     *
+     * @var string
+     */
+    protected $_application = 'Addressbook';
+}
diff --git a/tine20/Addressbook/Model/ListRoleFilter.php b/tine20/Addressbook/Model/ListRoleFilter.php
new file mode 100644 (file)
index 0000000..6d3ef30
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Addressbook_Model_ListRoleFilter
+ * 
+ * @package     Addressbook
+ * @subpackage  Filter
+ */
+class Addressbook_Model_ListRoleFilter extends Tinebase_Model_Filter_FilterGroup
+{
+    /**
+     * @var string class name of this filter group
+     *      this is needed to overcome the static late binding
+     *      limitation in php < 5.3
+     */
+    protected $_className = 'Addressbook_Model_ListRoleFilter';
+    
+    /**
+     * @var string application of this filter group
+     */
+    protected $_applicationName = 'Addressbook';
+    
+    /**
+     * @var string name of model this filter group is designed for
+     */
+    protected $_modelName = 'Addressbook_Model_ListRole';
+    
+    /**
+     * @var array filter model fieldName => definition
+     */
+    protected $_filterModel = array(
+        'id'                    => array(
+            'filter' => 'Tinebase_Model_Filter_Id'
+        ),
+        'query'                => array(
+            'filter' => 'Tinebase_Model_Filter_Query', 
+            'options' => array('fields' => array('name'))
+        ),
+    );
+}
diff --git a/tine20/Addressbook/Model/ListRoleMemberFilter.php b/tine20/Addressbook/Model/ListRoleMemberFilter.php
new file mode 100644 (file)
index 0000000..97461a0
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @subpackage  Model
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * filters for contacts that have a certain list role
+ * 
+ * @package     Addressbook
+ * @subpackage  Model
+ */
+class Addressbook_Model_ListRoleMemberFilter extends Tinebase_Model_Filter_Abstract 
+{
+    /**
+     * @var array list of allowed operators
+     */
+    protected $_operators = array(
+        0 => 'equals',
+        1 => 'in',
+    );
+
+    /**
+     * appends sql to given select statement
+     *
+     * @param  Zend_Db_Select                    $_select
+     * @param  Tinebase_Backend_Sql_Abstract     $_backend
+     */
+    public function appendFilterSql($_select, $_backend)
+    {
+        $correlationName = Tinebase_Record_Abstract::generateUID(30);
+        $db = $_backend->getAdapter();
+        $_select->joinLeft(
+            /* table  */ array($correlationName => $db->table_prefix . 'adb_list_m_role'),
+            /* on     */ $db->quoteIdentifier($correlationName . '.contact_id') . ' = ' . $db->quoteIdentifier('addressbook.id'),
+            /* select */ array()
+        );
+        $_select->where($db->quoteIdentifier($correlationName . '.list_role_id') . ' IN (?)', (array) $this->_value);
+    }
+    
+    /**
+     * returns array with the filter settings of this filter group
+     *
+     * @param  bool $_valueToJson resolve value for json api?
+     * @return array
+     */
+    public function toArray($_valueToJson = false)
+    {
+        if (is_string($this->_value)) {
+            $this->_value = Addressbook_Controller_ListRole::getInstance()->get($this->_value)->toArray();
+        }
+        
+        return parent::toArray($_valueToJson);
+    }
+}
index 0f7c422..6509a9b 100644 (file)
@@ -168,7 +168,7 @@ class Addressbook_Setup_Initialize extends Setup_Initialize
         $adminGroup = $groupsBackend->getDefaultAdminGroup();
         
         // give anyone read rights to the internal addressbook
-        // give Adminstrators group read/edit/admin rights to the internal addressbook
+        // give Administrators group read/edit/admin rights to the internal addressbook
         Tinebase_Container::getInstance()->addGrants($this->_getInternalAddressbook(), Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE, '0', array(
             Tinebase_Model_Grants::GRANT_READ
         ), TRUE);
diff --git a/tine20/Addressbook/Setup/Update/Release9.php b/tine20/Addressbook/Setup/Update/Release9.php
new file mode 100644 (file)
index 0000000..cfa3e0b
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @subpackage  Setup
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL3
+ * @copyright   Copyright (c) 2014-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+class Addressbook_Setup_Update_Release9 extends Setup_Update_Abstract
+{
+    /**
+     * update to 9.1: add list_roles table
+     *
+     * @return void
+     */
+    public function update_0()
+    {
+        $table = Setup_Backend_Schema_Table_Factory::getSimpleRecordTable('addressbook_list_role');
+        $this->_backend->createTable($table, 'Addressbook', 'addressbook_list_role');
+        $this->setApplicationVersion('Addressbook', '9.1');
+    }
+
+    /**
+     * update to 9.2: just a placeholder
+     *
+     * @return void
+     */
+    public function update_1()
+    {
+        // do nothing
+    }
+
+    /**
+     * drop index 'name' from lists table
+     *
+     * @return void
+     */
+    public function update_2()
+    {
+        $this->_backend->dropIndex('addressbook_lists', 'name');
+        $this->setTableVersion('addressbook_lists', 3);
+        $this->setApplicationVersion('Addressbook', '9.3');
+    }
+
+    /**
+     * add addressbook_list_member_role table
+     *
+     * @return void
+     */
+    public function update_3()
+    {
+        $table = Setup_Backend_Schema_Table_Factory::factory('String', '
+        <table>
+            <name>adb_list_m_role</name>
+            <engine>InnoDB</engine>
+            <charset>utf8</charset>
+            <version>1</version>
+            <declaration>
+                <field>
+                    <name>id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>list_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>list_role_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>contact_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <index>
+                    <name>adb_list_m_role::list_id--addressbook_lists::id</name>
+                    <field>
+                        <name>list_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook_lists</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+                <index>
+                    <name>adb_list_m_role::contact_id--addressbook::id</name>
+                    <field>
+                        <name>contact_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+                <index>
+                    <name>adb_list_m_role::list_role_id--addressbook_list_role::id</name>
+                    <field>
+                        <name>list_role_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook_list_role</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+            </declaration>
+        </table>
+        ');
+        $this->_backend->createTable($table, 'Addressbook', 'addressbook_list_member_role');
+        $this->setApplicationVersion('Addressbook', '9.4');
+    }
+    
+    /**
+     * update to 9.5
+     *
+     * @return void
+     */
+    public function update_4()
+    {
+        $declaration = new Setup_Backend_Schema_Field_Xml('
+            <field>
+                <name>list_type</name>
+                <type>text</type>
+                <length>40</length>
+                <notnull>false</notnull>
+            </field>');
+        $this->_backend->addCol('addressbook_lists', $declaration);
+        
+        $this->setTableVersion('addressbook_lists', 4);
+        $this->setApplicationVersion('Addressbook', '9.5');
+    }
+}
index 79cd9eb..bec5128 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <application>
     <name>Addressbook</name>
-    <version>9.0</version>
+    <version>9.5</version>
     <order>10</order>
     <depends>
         <application>Admin</application>
         </table>
         <table>
             <name>addressbook_lists</name>
-            <version>2</version>
+            <version>4</version>
             <declaration>
                 <field>
                     <name>id</name>
                     <default>list</default>
                 </field>
                 <field>
+                    <name>list_type</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
                     <name>created_by</name>
                     <type>text</type>
                     <length>40</length>
                         <name>id</name>
                     </field>
                 </index>
-                <index>
-                    <name>name</name>
-                    <unique>true</unique>
-                    <field>
-                        <name>name</name>
-                    </field>
-                </index>
             </declaration>
         </table>
         <table>
                     <length>40</length> 
                     <notnull>true</notnull>
                 </field>
-
                 <index>
                     <name>list_id-contact_id</name>
                     <primary>true</primary>
                 </index>
             </declaration>
         </table>
-    </tables>    
+        <table>
+            <name>addressbook_list_role</name>
+            <engine>InnoDB</engine>
+            <charset>utf8</charset>
+            <version>1</version>
+            <declaration>
+                <field>
+                    <name>id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>name</name>
+                    <type>text</type>
+                    <length>256</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
+                    <name>description</name>
+                    <type>text</type>
+                    <notnull>false</notnull>
+                </field>
+                <field>
+                    <name>created_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>creation_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>last_modified_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <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>
+                    <field>
+                        <name>id</name>
+                    </field>
+                </index>
+            </declaration>
+        </table>
+        <table>
+            <!-- addressbook list member roles / NOTE: had to use a very short name because of index length restrictions :( -->
+            <name>adb_list_m_role</name>
+            <engine>InnoDB</engine>
+            <charset>utf8</charset>
+            <version>1</version>
+            <declaration>
+                <field>
+                    <name>id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>list_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>list_role_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>contact_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <index>
+                    <name>adb_list_m_role::list_id--addressbook_lists::id</name>
+                    <field>
+                        <name>list_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook_lists</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+                <index>
+                    <name>adb_list_m_role::contact_id--addressbook::id</name>
+                    <field>
+                        <name>contact_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+                <index>
+                    <name>adb_list_m_role::list_role_id--addressbook_list_role::id</name>
+                    <field>
+                        <name>list_role_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>addressbook_list_role</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                        <onupdate>CASCADE</onupdate>
+                    </reference>
+                </index>
+            </declaration>
+        </table>
+    </tables>
     <defaultRecords>
         <record>
             <table>
index c71906e..3d1c011 100644 (file)
     height: 16px;
 }
 
+.renderer_typeGroupIcon, .AddressbookList{
+    background-image:url(../../images/oxygen/16x16/actions/users.png) !important;
+    background-repeat: no-repeat;
+    /*background-position: 2px 2px;*/
+    /*height: 16px;*/
+}
+
+.renderer_typeListIcon {
+    background-image:url(../../images/list.png) !important;
+    background-repeat: no-repeat;
+    /*background-position: 2px 2px;*/
+    /*height: 16px;*/
+}
+
 .x-grid3-cell-inner .renderer_typeAccountIcon,
 .x-grid3-cell-inner .renderer_typeContactIcon {
     margin-bottom: -2px;
index 960f034..89b7f0d 100755 (executable)
@@ -45,6 +45,48 @@ Tine.Addressbook.Application = Ext.extend(Tine.Tinebase.Application, {
 
         return mainscreen;
     },
+
+    registerCoreData: function() {
+        Tine.CoreData.Manager.registerGrid('adb_lists', Tine.Addressbook.ListGridPanel, {
+            app: this,
+            initialLoadAfterRender: false
+        });
+
+        Tine.CoreData.Manager.registerGrid(
+            'adb_list_roles',
+            Tine.widgets.grid.GridPanel,
+            {
+                recordClass: Tine.Addressbook.Model.ListRole,
+                app: this,
+                initialLoadAfterRender: false,
+                // TODO move this to a generic place
+                gridConfig: {
+                    autoExpandColumn: 'name',
+                    columns: [{
+                        id: 'id',
+                        header: this.i18n._("ID"),
+                        width: 150,
+                        sortable: true,
+                        dataIndex: 'id',
+                        hidden: true
+                    }, {
+                        id: 'name',
+                        header: this.i18n._("Name"),
+                        width: 300,
+                        sortable: true,
+                        dataIndex: 'name'
+                    }, {
+                        id: 'description',
+                        header: this.i18n._("Description"),
+                        width: 300,
+                        sortable: true,
+                        dataIndex: 'description',
+                        hidden: true
+                    }]
+                }
+            }
+        );
+    }
 });
 
 /**
@@ -55,7 +97,7 @@ Tine.Addressbook.Application = Ext.extend(Tine.Tinebase.Application, {
  * 
  * @author      Cornelius Weiss <c.weiss@metaways.de>
  */
- Tine.Addressbook.MainScreen = Ext.extend(Tine.widgets.MainScreen, {
+Tine.Addressbook.MainScreen = Ext.extend(Tine.widgets.MainScreen, {
     activeContentType: 'Contact',
     contentTypes: [
         {model: 'Contact',  requiredRight: null, singularContainerMode: false},
@@ -102,4 +144,4 @@ Tine.Addressbook.ListFilterPanel = function(config) {
 
 Ext.extend(Tine.Addressbook.ListFilterPanel, Tine.widgets.persistentfilter.PickerPanel, {
     filter: [{field: 'model', operator: 'equals', value: 'Addressbook_Model_ListFilter'}]
-});
\ No newline at end of file
+});
diff --git a/tine20/Addressbook/js/AdminPanel.js b/tine20/Addressbook/js/AdminPanel.js
new file mode 100644 (file)
index 0000000..9416b39
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Tine 2.0
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+Ext.namespace('Tine.Addressbook');
+
+/**
+ * admin settings panel
+ * 
+ * @namespace   Tine.Addressbook
+ * @class       Tine.Addressbook.AdminPanel
+ * @extends     Ext.TabPanel
+ * 
+ * <p>Addressbook Admin Panel</p>
+ * <p><pre>
+ * </pre></p>
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @param       {Object} config
+ * @constructor
+ * Create a new Tine.Addressbook.AdminPanel
+ */
+Tine.Addressbook.AdminPanel = Ext.extend(Ext.TabPanel, {
+
+    border: false,
+    activeTab: 0,
+
+    /**
+     * @private
+     */
+    initComponent: function() {
+        
+        this.app = Tine.Tinebase.appMgr.get('Addressbook');
+        
+        this.items = [
+            new Tine.Admin.config.GridPanel({
+                configApp: this.app
+            })
+        ];
+        
+        Tine.Addressbook.AdminPanel.superclass.initComponent.call(this);
+    }
+});
+    
+/**
+ * Addressbook Admin Panel Popup
+ * 
+ * @param   {Object} config
+ * @return  {Ext.ux.Window}
+ */
+Tine.Addressbook.AdminPanel.openWindow = function (config) {
+    var window = Tine.WindowFactory.getWindow({
+        width: 600,
+        height: 470,
+        name: 'addressbook-admin-panel',
+        contentPanelConstructor: 'Tine.Addressbook.AdminPanel',
+        contentPanelConstructorConfig: config
+    });
+};
index 4c33082..d237035 100644 (file)
@@ -89,20 +89,20 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
                                 style:'padding-right: 5px;',
                                 items: [[
                                     new Tine.Tinebase.widgets.keyfield.ComboBox({
-                                    fieldLabel: this.app.i18n._('Salutation'),
-                                    name: 'salutation',
-                                    app: 'Addressbook',
-                                    keyFieldName: 'contactSalutation',
-                                    value: '',
-                                    columnWidth: 0.35,
-                                    listeners: {
-                                        scope: this,
-                                        'select': function (combo, record, index) {
-                                            var jpegphoto = this.getForm().findField('jpegphoto');
-                                            // set new empty photo depending on chosen salutation only if user doesn't have own image
-                                            jpegphoto.setDefaultImage(record.json.image);
+                                        fieldLabel: this.app.i18n._('Salutation'),
+                                        name: 'salutation',
+                                        app: 'Addressbook',
+                                        keyFieldName: 'contactSalutation',
+                                        value: '',
+                                        columnWidth: 0.35,
+                                        listeners: {
+                                            scope: this,
+                                            'select': function (combo, record, index) {
+                                                var jpegphoto = this.getForm().findField('jpegphoto');
+                                                // set new empty photo depending on chosen salutation only if user doesn't have own image
+                                                jpegphoto.setDefaultImage(record.json.image);
+                                            }
                                         }
-                                    }
                                 }), {
                                     columnWidth: 0.65,
                                     fieldLabel: this.app.i18n._('Title'),
index fb0221d..3812bc5 100644 (file)
@@ -268,8 +268,7 @@ Tine.Addressbook.ContactGridPanel.countryRenderer = function(data) {
 };
 
 /**
- * Statically constructs the columns used to represent a contact. Reused by ListMemberGridPanel
- *
+ * Statically constructs the columns used to represent a contact. Reused by ListMemberGridPanel + ListMemberRoleGridPanel
  */
 Tine.Addressbook.ContactGridPanel.getBaseColumns = function(i18n) {
     return [
@@ -319,6 +318,28 @@ Tine.Addressbook.ContactGridPanel.getBaseColumns = function(i18n) {
         { id: 'note', header: i18n._('Note'), dataIndex: 'note' },
         { id: 'tz', header: i18n._('Timezone'), dataIndex: 'tz' },
         { id: 'geo', header: i18n._('Geo'), dataIndex: 'geo' },
-        { id: 'bday', header: i18n._('Birthday'), dataIndex: 'bday', renderer: Tine.Tinebase.common.dateRenderer }
+        { id: 'bday', header: i18n._('Birthday'), dataIndex: 'bday', renderer: Tine.Tinebase.common.dateRenderer },
+        { id: 'memberroles', header: i18n._('List Roles'), dataIndex: 'memberroles', renderer: Tine.Addressbook.ListMemberRoleRenderer }
     ]
 };
+
+/**
+ *list member role render
+ *
+ * @param value
+ * @returns {string}
+ * @constructor
+ */
+Tine.Addressbook.ListMemberRoleRenderer = function(value) {
+    if (Ext.isArray(value)) {
+        var result = [];
+        Ext.each(value, function(memberrole) {
+            if (memberrole.list_role_id.name) {
+                result.push(memberrole.list_role_id.name);
+            }
+        });
+        return result.toString();
+    }
+
+    return '';
+};
similarity index 82%
rename from tine20/Addressbook/js/SearchCombo.js
rename to tine20/Addressbook/js/ContactSearchCombo.js
index ba3f63a..3f5449a 100644 (file)
@@ -5,7 +5,7 @@
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -34,7 +34,7 @@ Ext.ns('Tine.Addressbook');
  * 
  * TODO         add     forceSelection: true ?
  */
-Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPickerComboBox, {
+Tine.Addressbook.ContactSearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPickerComboBox, {
     
     /**
      * @cfg {Boolean} userOnly
@@ -57,12 +57,18 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
     
     //private
     initComponent: function(){
-        
+        this.app = Tine.Tinebase.appMgr.get('Addressbook');
+
         if (this.recordClass === null) {
             this.recordClass = Tine.Addressbook.Model.Contact;
             this.recordProxy = Tine.Addressbook.contactBackend;
         }
 
+        this.emptyText = this.emptyText || (this.userOnly ?
+            this.app.i18n._('Search for users ...') :
+            this.app.i18n._('Search for Contacts ...')
+        );
+
         this.initTemplate();
         Tine.Addressbook.SearchCombo.superclass.initComponent.call(this);
     },
@@ -88,7 +94,15 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
      */
     onBeforeQuery: function(qevent){
         Tine.Addressbook.SearchCombo.superclass.onBeforeQuery.apply(this, arguments);
-        
+
+        var contactFilter = {condition: 'AND', filters: this.store.baseParams.filter},
+            pathFilter = { field: 'path', operator: 'contains', value: qevent.query };
+
+        this.store.baseParams.filter = [{condition: "OR", filters: [
+            contactFilter,
+            pathFilter
+        ] }];
+
         if (this.userOnly) {
             this.store.baseParams.filter.push({field: 'type', operator: 'equals', value: 'user'});
         }
@@ -116,8 +130,9 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
                             '</td>',
                         '</tr>',
                     '</table>',
+                    '{[Tine.widgets.path.pathsRenderer(values.paths, this.lastQuery)]}',
                 '</div></tpl>'
-             );
+            );
         }
     },
     
@@ -155,5 +170,8 @@ Tine.Addressbook.SearchCombo = Ext.extend(Tine.Tinebase.widgets.form.RecordPicke
 
 });
 
-Ext.reg('addressbookcontactpicker', Tine.Addressbook.SearchCombo);
-Tine.widgets.form.RecordPickerManager.register('Addressbook', 'Contact', Tine.Addressbook.SearchCombo);
+// legacy
+Tine.Addressbook.SearchCombo = Tine.Addressbook.ContactSearchCombo;
+
+Ext.reg('addressbookcontactpicker', Tine.Addressbook.ContactSearchCombo);
+Tine.widgets.form.RecordPickerManager.register('Addressbook', 'Contact', Tine.Addressbook.ContactSearchCombo);
index 6f67244..1010a85 100644 (file)
@@ -4,7 +4,7 @@
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Frederic Heihoff <heihoff@sh-systems.eu>
- * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2016 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -21,20 +21,32 @@ Ext.ns('Tine.Addressbook');
  * @author      Frederic Heihoff <heihoff@sh-systems.eu>
  */
 Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
-    
-    /**
-     * parse address button
-     * @type Ext.Button 
-     */
-    parseAddressButton: null,
-    
+
     windowNamePrefix: 'ListEditWindow_',
     appName: 'Addressbook',
     recordClass: Tine.Addressbook.Model.List,
     showContainerSelector: true,
     multipleEdit: true,
-    
-    // TODO: Add History and Tagging Functionality
+    displayNotes: true,
+
+    /**
+     * init component
+     */
+    initComponent: function () {
+        this.memberGridPanel = new Tine.Addressbook.ListMemberRoleGridPanel({
+            region: "center",
+            frame: true,
+            margins: '6 0 0 0'
+        });
+        this.memberRolesPanel = new Tine.Addressbook.ListRoleGridPanel({
+            region: "south",
+            frame: true,
+            margins: '6 0 0 0',
+            height: 150
+        });
+        this.supr().initComponent.apply(this, arguments);
+    },
+
     getFormItems: function () {
         return {
             xtype: 'tabpanel',
@@ -71,10 +83,23 @@ Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                                     fieldLabel: this.app.i18n._('Name'),
                                     name: 'name',
                                     maxLength: 64
-                                }]]
+                                }],
+                                [new Tine.Tinebase.widgets.keyfield.ComboBox({
+                                        columnWidth: 1,
+                                        fieldLabel: this.app.i18n._('List type'),
+                                        name: 'list_type',
+                                        app: 'Addressbook',
+                                        keyFieldName: 'listType',
+                                        value: ''
+                                    })
+                                ]]
                             }]
                         }]
-                    }, this.memberGridPanel]
+                    },
+                        // TODO allow user to switch between those two grid panels (card layout?)
+                        this.memberGridPanel,
+                        this.memberRolesPanel
+                    ]
                 }, {
                     // activities and tags
                     region: 'east',
@@ -107,39 +132,25 @@ Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
                                 emptyText: this.app.i18n._('Enter description'),
                                 requiredGrant: 'editGrant'
                             }]
-                        })/*,
-                        new Tine.widgets.activities.ActivitiesPanel({
-                            app: 'Addressbook',
-                            showAddNoteForm: false,
-                            border: false,
-                            bodyStyle: 'border:1px solid #B5B8C8;'
                         }),
                         new Tine.widgets.tags.TagPanel({
                             app: 'Addressbook',
                             border: false,
                             bodyStyle: 'border:1px solid #B5B8C8;'
-                        })*/
+                        })
                     ]
                 }]
             },
-            /*new Tine.widgets.activities.ActivitiesTabPanel({
+            new Tine.widgets.activities.ActivitiesTabPanel({
                 app: this.appName,
                 record_id: (this.record && ! this.copyRecord) ? this.record.id : '',
                 record_model: this.appName + '_Model_' + this.recordClass.getMeta('modelName')
-            })*/
+            })
             ]
         };
     },
     
     /**
-     * init component
-     */
-    initComponent: function () {    
-        this.memberGridPanel = new Tine.Addressbook.ListMemberGridPanel({ region: "center", frame: true, margins: '6 0 0 0' });           
-        this.supr().initComponent.apply(this, arguments);
-    },
-    
-    /**
      * checks if form data is valid
      * 
      * @return {Boolean}
@@ -166,24 +177,12 @@ Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
      * onRecordLoad
      */
     onRecordLoad: function () {
-        this.memberGridPanel.setMembers(this.record.get("members")); 
         // NOTE: it comes again and again till
         if (this.rendered) {
-            var container = this.record.get('container_id');
-            
-            // handle default container
-            // TODO is this still needed? don't we already have generic default container handling?
-            if (! this.record.id) {
-                if (this.forceContainer) {
-                    container = this.forceContainer;
-                    // only force initially!
-                    this.forceContainer = null;
-                } else if (! Ext.isObject(container)) {
-                    container = Tine.Addressbook.registry.get('defaultAddressbook');
-                }
-                
-                this.record.set('container_id', '');
-                this.record.set('container_id', container);
+            this.memberGridPanel.record = this.record;
+            if (this.record.id) {
+                this.memberGridPanel.setMembers();
+                this.memberGridPanel.memberRolesPanel = this.memberRolesPanel;
             }
         }
         this.supr().onRecordLoad.apply(this, arguments);
@@ -195,7 +194,8 @@ Tine.Addressbook.ListEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog, {
     onRecordUpdate: function() {
         Tine.Addressbook.ListEditDialog.superclass.onRecordUpdate.apply(this, arguments);
         this.record.set("members", this.memberGridPanel.getMembers());
-    },
+        this.record.set("memberroles", this.memberGridPanel.getMemberRoles());
+    }
 });
 
 /**
index 7e59ac7..d39ec7e 100644 (file)
@@ -47,7 +47,7 @@ Tine.Addressbook.ListGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     /**
      * @cfg {Bool} hasDetailsPanel 
      */
-    hasDetailsPanel: true,
+    hasDetailsPanel: false,
     
     /**
      * inits this cmp
@@ -97,9 +97,17 @@ Tine.Addressbook.ListGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
      */
     getColumns: function() {
         return [
-            { id: 'type', header: this.app.i18n._('Type'), dataIndex: 'type', width: 30, renderer: this.listTypeRenderer.createDelegate(this), hidden: false },
+            { id: 'type', header: this.app.i18n._('Type'), dataIndex: 'type', width: 30, renderer: Tine.Addressbook.ListGridPanel.listTypeRenderer, hidden: false },
             { id: 'name', header: this.app.i18n._('Name'), dataIndex: 'name', width: 30, hidden: false },
-            { id: 'emails', header: this.app.i18n._('Emails'), dataIndex: 'emails', hidden: false },
+            { id: 'list_type', header: this.app.i18n._('List type'), dataIndex: 'list_type', width: 30, renderer: Tine.Tinebase.widgets.keyfield.Renderer.get('Addressbook', 'listType'), hidden: false },
+            { id: 'emails', header: this.app.i18n._('Emails'), dataIndex: 'emails', hidden: false, renderer: function(value) {
+                if (! value) {
+                    return '';
+                }
+                // TODO should be fixed on server side
+                // remove leading comma
+                return value.replace(/^,/, '');
+            }},
         ].concat(this.getModlogColumns().concat(this.getCustomfieldColumns()));
     },
     
@@ -109,21 +117,7 @@ Tine.Addressbook.ListGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     initActions: function() {        
         Tine.Addressbook.ListGridPanel.superclass.initActions.call(this);
     },
-        
-    /**
-     * tid renderer
-     * 
-     * @private
-     * @return {String} HTML
-     */
-    listTypeRenderer: function(data, cell, record) {
-        if (data == "group") {
-            return '<div style="background-position:0px;" class="renderer_typeAccountIcon">&#160</div>';
-        } else {
-            return '<div style="background-position:0px;" class="renderer_typeContactIcon">&#160</div>';
-        }
-    },
-    
+
     /**
      * returns details panel
      * 
@@ -137,3 +131,18 @@ Tine.Addressbook.ListGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         });
     }
 });
+
+/**
+ * list type renderer
+ *
+ * @private
+ * @return {String} HTML
+ */
+Tine.Addressbook.ListGridPanel.listTypeRenderer = function(data, cell, record) {
+    var i18n = Tine.Tinebase.appMgr.get('Addressbook').i18n,
+        type = ((record.get && record.get('type')) || record.type),
+        cssClass = type == 'group' ? 'renderer_typeGroupIcon' : 'renderer_typeListIcon',
+        qtipText = Tine.Tinebase.common.doubleEncode(type == 'group' ? i18n._('System Group') : i18n._('Group'));
+
+    return '<div ext:qtip="' + qtipText + '" style="background-position:0px;" class="' + cssClass + '">&#160</div>';
+};
diff --git a/tine20/Addressbook/js/ListMemberGridPanel.js b/tine20/Addressbook/js/ListMemberGridPanel.js
deleted file mode 100644 (file)
index ae506ce..0000000
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Tine 2.0
- * 
- * @package     Addressbook
- * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @author      Frederic Heihoff <heihoff@sh-systems.eu>
- * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
- *
- */
-Ext.ns('Tine.Addressbook');
-
-/**
- * @namespace   Tine.Addressbook
- * @class       Tine.Addressbook.ListMemberGridPanel
- * @extends     Ext.grid.EditorGridPanel
- * @author      Frederic Heihoff <heihoff@sh-systems.eu>
- */
-Tine.Addressbook.ListMemberGridPanel = Ext.extend(Ext.grid.EditorGridPanel, {
-    clicksToEdit: 1,
-
-    /**
-     * init component
-     */
-    initComponent: function() {
-        this.app = this.app ? this.app : Tine.Tinebase.appMgr.get('Addressbook');
-
-        this.clicksToEdit = 1;
-        
-        this.title = this.hasOwnProperty('title') ? this.title : this.app.i18n._('Members');
-        this.plugins = this.plugins || [];
-
-        this.sm = new Ext.grid.RowSelectionModel({singleSelect:true});
-        this.sm.on('selectionchange', function(sm){
-            this.removeBtn.setDisabled(sm.getCount() < 1);
-        }, this);
-
-        this.tbar = [{
-            text: this.app.i18n._('Add'),
-            handler: function(){
-                //this.stopEditing();
-                this.store.insert(0, new Tine.Addressbook.Model.Contact({id: ""}));
-                this.getView().refresh();
-                this.getSelectionModel().selectRow(0);
-                this.startEditing(0,0);
-            }.createDelegate(this)
-        },{
-            ref: '../removeBtn',
-            text: this.app.i18n._('Remove'),
-            disabled: true,
-            handler: function(){
-                this.stopEditing();
-                var s = this.getSelectionModel().getSelections();
-                for(var i = 0, r; r = s[i]; i++){
-                    this.store.remove(r);
-                }
-            }.createDelegate(this)
-        }]
-
-        this.initColumns();
-        this.store = this.store = new Ext.data.Store({
-            autoSave: false,
-            fields:  Tine.Addressbook.Model.Contact,
-            proxy: Tine.Addressbook.contactBackend,
-            reader: Tine.Addressbook.contactBackend.getReader(),
-        });
-
-        this.addListener("afteredit", this._afterEdit, this);
-
-        Tine.Addressbook.ListMemberGridPanel.superclass.initComponent.call(this);
-    },
-
-    /**
-     * initialises grid with an array of member uids
-     */
-    setMembers: function(members) {
-        if (members) {
-            var options = {params: {filter: [ { "field":"id","operator":"in", "value": members } ]}};
-            this.store.load(options);
-            this.store.sort("n_fileas");
-        }
-    },
-
-    /**
-     * returns current array of member uids
-     */
-    getMembers: function() {
-        var result = [];
-       for (var i = 0; i < this.store.getCount(); i++){
-            var item = this.store.getAt(i).data;
-            if (item.id != "") {
-                result.push(item.id);
-            }
-      &nb