Merge branch '2014.11' into 2014.11-develop
authorPhilipp Schüle <p.schuele@metaways.de>
Thu, 3 Dec 2015 12:40:03 +0000 (13:40 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 3 Dec 2015 12:40:03 +0000 (13:40 +0100)
367 files changed:
tests/tine20/Addressbook/Convert/Contact/VCard/AllTests.php
tests/tine20/Addressbook/Convert/Contact/VCard/TelefonbuchTest.php [new file with mode: 0644]
tests/tine20/Addressbook/Import/CsvTest.php
tests/tine20/Addressbook/Import/files/adb_import_csv_split.xml [new file with mode: 0644]
tests/tine20/Addressbook/Import/files/import_split.csv [new file with mode: 0644]
tests/tine20/Addressbook/Import/files/import_split_duplicate.csv [new file with mode: 0644]
tests/tine20/Addressbook/Import/files/telefonbuch.vcf [new file with mode: 0644]
tests/tine20/Addressbook/JsonTest.php
tests/tine20/Admin/JsonTest.php
tests/tine20/Calendar/Controller/EventNotificationsTests.php
tests/tine20/Calendar/Convert/Event/VCalendar/GenericTest.php
tests/tine20/Calendar/Export/ICalTest.php
tests/tine20/Crm/Acl/RolesTest.php
tests/tine20/Crm/AllTests.php
tests/tine20/Crm/Backend/LeadTest.php
tests/tine20/Crm/Export/CsvTest.php
tests/tine20/Crm/Export/PdfTest.php
tests/tine20/Crm/Import/CsvTest.php [new file with mode: 0644]
tests/tine20/Crm/Import/files/leads.csv [new file with mode: 0644]
tests/tine20/Crm/JsonTest.php
tests/tine20/Crm/NotificationsTests.php
tests/tine20/ExampleApplication/JsonTest.php
tests/tine20/ExampleApplication/TestCase.php
tests/tine20/Filemanager/Controller/DownloadLinkTests.php
tests/tine20/ImportTestCase.php [new file with mode: 0644]
tests/tine20/Sales/AllTests.php
tests/tine20/Sales/InvoiceControllerTests.php
tests/tine20/Sales/ProductControllerTest.php [new file with mode: 0644]
tests/tine20/Sales/PurchaseInvoiceTest.php [new file with mode: 0644]
tests/tine20/Sales/SuppliersTest.php [new file with mode: 0644]
tests/tine20/TestCase.php
tests/tine20/Timetracker/JsonTest.php
tests/tine20/Tinebase/AllTests.php
tests/tine20/Tinebase/ConfigTest.php
tests/tine20/Tinebase/ContainerTest.php
tests/tine20/Tinebase/Frontend/CliTest.php
tests/tine20/Tinebase/Frontend/HttpTest.php
tests/tine20/Tinebase/Frontend/JsonTest.php
tests/tine20/Tinebase/LockTest.php [new file with mode: 0644]
tests/tine20/Tinebase/PreferenceTest.php
tests/tine20/Tinebase/ScheduledImportTest.php
tests/tine20/Tinebase/User/ActiveDirectoryTest.php
tine20/ActiveSync/Config.php
tine20/Addressbook/Acl/Rights.php
tine20/Addressbook/Config.php
tine20/Addressbook/Convert/Contact/VCard/Abstract.php
tine20/Addressbook/Convert/Contact/VCard/Factory.php
tine20/Addressbook/Convert/Contact/VCard/Telefonbuch.php [new file with mode: 0644]
tine20/Addressbook/Convert/Contact/config/convert_from_string_improved.xml [new file with mode: 0644]
tine20/Addressbook/Frontend/Http.php
tine20/Addressbook/Frontend/Json.php
tine20/Addressbook/Import/Csv.php
tine20/Addressbook/Import/VCard.php
tine20/Addressbook/Model/Contact.php
tine20/Addressbook/Model/ContactFilter.php
tine20/Addressbook/Setup/Initialize.php
tine20/Addressbook/js/ContactEditDialog.js
tine20/Addressbook/js/ContactGrid.js
tine20/Addressbook/translations/de.po
tine20/Addressbook/translations/en.po
tine20/Addressbook/translations/template.pot
tine20/Admin/Admin.jsb2
tine20/Admin/Controller/Config.php [new file with mode: 0644]
tine20/Admin/Controller/Keyfield.php [new file with mode: 0644]
tine20/Admin/Frontend/Json.php
tine20/Admin/js/AdminPanel.js
tine20/Admin/js/Applications.js
tine20/Admin/js/Groups.js
tine20/Admin/js/Models.js
tine20/Admin/js/Roles.js
tine20/Admin/js/Tags.js
tine20/Admin/js/config/FieldManager.js [new file with mode: 0644]
tine20/Admin/js/config/GridPanel.js [new file with mode: 0644]
tine20/Admin/js/customfield/EditDialog.js
tine20/Admin/js/customfield/GridPanel.js
tine20/Calendar/Acl/Rights.php
tine20/Calendar/Calendar.jsb2
tine20/Calendar/Config.php
tine20/Calendar/Controller/Event.php
tine20/Calendar/Controller/EventNotifications.php
tine20/Calendar/Controller/Resource.php
tine20/Calendar/Convert/Event/Json.php
tine20/Calendar/Frontend/WebDAV.php
tine20/Calendar/Model/Attender.php
tine20/Calendar/Model/Resource.php
tine20/Calendar/Preference.php
tine20/Calendar/Setup/Initialize.php
tine20/Calendar/Setup/Update/Release8.php
tine20/Calendar/Setup/setup.xml
tine20/Calendar/js/AdminPanel.js
tine20/Calendar/js/AttendeeGridPanel.js
tine20/Calendar/js/CalendarPanelSplitPlugin.js [new file with mode: 0644]
tine20/Calendar/js/DaysView.js
tine20/Calendar/js/EventContextAttendeesItem.js [new file with mode: 0644]
tine20/Calendar/js/EventContextTagsItem.js [new file with mode: 0644]
tine20/Calendar/js/EventEditDialog.js
tine20/Calendar/js/EventUI.js
tine20/Calendar/js/GridView.js
tine20/Calendar/js/MainScreenCenterPanel.js
tine20/Calendar/js/Model.js
tine20/Calendar/js/MonthView.js
tine20/Calendar/js/PerspectiveCombo.js
tine20/Calendar/js/ResourceEditDialog.js
tine20/Calendar/translations/de.po
tine20/Courses/Acl/Rights.php
tine20/Courses/Config.php
tine20/Courses/Setup/Initialize.php
tine20/Crm/Acl/Rights.php
tine20/Crm/Config.php
tine20/Crm/Controller.php
tine20/Crm/Controller/Lead.php
tine20/Crm/Crm.jsb2
tine20/Crm/Frontend/Json.php
tine20/Crm/Import/Csv.php [new file with mode: 0644]
tine20/Crm/Import/definitions/crm_tine_import_csv.xml [new file with mode: 0644]
tine20/Crm/Model/Lead.php
tine20/Crm/Model/LeadFilter.php
tine20/Crm/Model/LeadQueryFilter.php
tine20/Crm/Setup/Update/Release8.php
tine20/Crm/Setup/setup.xml
tine20/Crm/js/AdminPanel.js
tine20/Crm/js/Crm.js
tine20/Crm/js/LeadEditDialog.js
tine20/Crm/js/LeadGridPanel.js
tine20/Crm/js/LeadSource.js
tine20/Crm/js/Model.js
tine20/Crm/js/Product.js
tine20/Crm/js/ProductPickerCombo.js [new file with mode: 0644]
tine20/Crm/translations/de.po
tine20/Crm/translations/template.pot
tine20/Crm/views/importNotificationPlain.php [new file with mode: 0644]
tine20/Crm/views/newLeadHtml.php
tine20/ExampleApplication/Config.php
tine20/ExampleApplication/Controller.php
tine20/ExampleApplication/ExampleApplication.jsb2
tine20/ExampleApplication/Model/ExampleRecord.php
tine20/ExampleApplication/Setup/Initialize.php
tine20/ExampleApplication/Setup/setup.xml
tine20/ExampleApplication/js/AdminPanel.js [new file with mode: 0644]
tine20/ExampleApplication/js/ExampleRecordEditDialog.js
tine20/Felamimail/Controller/Cache/Folder.php
tine20/Felamimail/Controller/Cache/Message.php
tine20/Felamimail/Controller/Message/Send.php
tine20/Felamimail/Preference.php
tine20/Felamimail/Setup/Update/Release8.php
tine20/Felamimail/Setup/setup.xml
tine20/Felamimail/js/AccountEditDialog.js
tine20/Felamimail/js/Felamimail.js
tine20/Felamimail/js/MessageEditDialog.js
tine20/Filemanager/Acl/Rights.php
tine20/Filemanager/Controller.php
tine20/Filemanager/Controller/DownloadLink.php
tine20/Filemanager/Frontend/Download.php
tine20/Filemanager/js/DownloadLinkDialog.js
tine20/Filemanager/js/DownloadLinkGridPanel.js
tine20/Filemanager/js/Filemanager.js
tine20/HumanResources/Config.php
tine20/HumanResources/Setup/Initialize.php
tine20/HumanResources/js/AdminPanel.js
tine20/HumanResources/js/DatePicker.js
tine20/Inventory/Config.php
tine20/Inventory/Controller.php
tine20/Inventory/Setup/Initialize.php
tine20/Phone/Frontend/Snom.php
tine20/Projects/Acl/Rights.php
tine20/Projects/Config.php
tine20/Projects/Setup/Initialize.php
tine20/Sales/Acl/Rights.php
tine20/Sales/Backend/Invoice.php
tine20/Sales/Backend/PurchaseInvoice.php [new file with mode: 0644]
tine20/Sales/Backend/Supplier.php [new file with mode: 0644]
tine20/Sales/Config.php
tine20/Sales/Controller.php
tine20/Sales/Controller/Contract.php
tine20/Sales/Controller/Invoice.php
tine20/Sales/Controller/Product.php
tine20/Sales/Controller/PurchaseInvoice.php [new file with mode: 0644]
tine20/Sales/Controller/Supplier.php [new file with mode: 0644]
tine20/Sales/Export/Ods/PurchaseInvoice.php [new file with mode: 0644]
tine20/Sales/Export/Ods/Supplier.php [new file with mode: 0644]
tine20/Sales/Export/definitions/purchaseinvoice_default_ods.xml [new file with mode: 0644]
tine20/Sales/Export/definitions/supplier_default_ods.xml [new file with mode: 0644]
tine20/Sales/Frontend/Cli.php
tine20/Sales/Frontend/Http.php
tine20/Sales/Frontend/Json.php
tine20/Sales/Frontend/WebDAV.php [new file with mode: 0644]
tine20/Sales/Frontend/WebDAV/Import.php [new file with mode: 0644]
tine20/Sales/Frontend/WebDAV/Module.php [new file with mode: 0644]
tine20/Sales/Model/Invoice.php
tine20/Sales/Model/Offer.php
tine20/Sales/Model/OrderConfirmation.php
tine20/Sales/Model/PaymentMethod.php [new file with mode: 0644]
tine20/Sales/Model/Product.php
tine20/Sales/Model/ProductAggregate.php
tine20/Sales/Model/ProductCategory.php [new file with mode: 0644]
tine20/Sales/Model/PurchaseInvoice.php [new file with mode: 0644]
tine20/Sales/Model/PurchaseInvoiceFilter.php [new file with mode: 0644]
tine20/Sales/Model/Supplier.php [new file with mode: 0644]
tine20/Sales/Model/SupplierFilter.php [new file with mode: 0644]
tine20/Sales/Sales.jsb2
tine20/Sales/Scheduler/Task.php [new file with mode: 0644]
tine20/Sales/Setup/Initialize.php
tine20/Sales/Setup/Update/Release8.php
tine20/Sales/Setup/setup.xml
tine20/Sales/js/AddressGridPanel.js
tine20/Sales/js/AdminPanel.js
tine20/Sales/js/ContractEditDialog.js
tine20/Sales/js/CustomerEditDialog.js
tine20/Sales/js/InvoiceDetailsPanel.js
tine20/Sales/js/InvoiceEditDialog.js
tine20/Sales/js/InvoiceSearchCombo.js [new file with mode: 0644]
tine20/Sales/js/OfferSearchCombo.js [new file with mode: 0644]
tine20/Sales/js/OrderConfirmationSearchCombo.js [new file with mode: 0644]
tine20/Sales/js/ProductEditDialog.js
tine20/Sales/js/PurchaseInvoiceApproverFilterModel.js [new file with mode: 0644]
tine20/Sales/js/PurchaseInvoiceDetailsPanel.js [new file with mode: 0644]
tine20/Sales/js/PurchaseInvoiceEditDialog.js [new file with mode: 0644]
tine20/Sales/js/PurchaseInvoiceGridPanel.js [new file with mode: 0644]
tine20/Sales/js/Sales.js
tine20/Sales/js/SupplierDetailsPanel.js [new file with mode: 0644]
tine20/Sales/js/SupplierEditDialog.js [new file with mode: 0644]
tine20/Sales/js/SupplierFilterModel.js [new file with mode: 0644]
tine20/Sales/js/SupplierGridPanel.js [new file with mode: 0644]
tine20/Sales/js/SupplierSearchCombo.js [new file with mode: 0644]
tine20/Sales/translations/de.po
tine20/Sales/translations/en.po
tine20/Sales/translations/template.pot
tine20/Setup/Backend/Mysql.php
tine20/Setup/Backend/Oracle.php
tine20/Setup/Backend/Pgsql.php
tine20/Setup/Controller.php
tine20/Setup/Initialize.php
tine20/Setup/js/Setup.js
tine20/Setup/js/init.js
tine20/SimpleFAQ/js/AdminPanel.js
tine20/Sipgate/Config.php
tine20/Sipgate/Setup/Initialize.php
tine20/Tasks/Acl/Rights.php
tine20/Tasks/Config.php
tine20/Tasks/Setup/Initialize.php
tine20/Timetracker/Acl/Rights.php
tine20/Timetracker/Controller/Timesheet.php
tine20/Timetracker/Model/Timeaccount.php
tine20/Timetracker/Model/TimeaccountFilter.php
tine20/Timetracker/js/TimeaccountEditDialog.js
tine20/Tinebase/Acl/Rights/Abstract.php
tine20/Tinebase/Application.php
tine20/Tinebase/Auth.php
tine20/Tinebase/Auth/CredentialCache.php
tine20/Tinebase/Backend/Sql/Abstract.php
tine20/Tinebase/Config.php
tine20/Tinebase/Config/Abstract.php
tine20/Tinebase/Config/KeyFieldRecord.php
tine20/Tinebase/Container.php
tine20/Tinebase/Controller.php
tine20/Tinebase/Controller/Abstract.php
tine20/Tinebase/Controller/Record/Abstract.php
tine20/Tinebase/Controller/ScheduledImport.php
tine20/Tinebase/Convert/ImportExportDefinition/Json.php
tine20/Tinebase/Convert/Json.php
tine20/Tinebase/Core.php
tine20/Tinebase/CustomField.php
tine20/Tinebase/DateTime.php
tine20/Tinebase/EmailUser/Imap/Dovecot.php
tine20/Tinebase/Export.php
tine20/Tinebase/Export/Abstract.php
tine20/Tinebase/Export/Spreadsheet/Abstract.php
tine20/Tinebase/Export/Spreadsheet/Ods.php
tine20/Tinebase/Export/Spreadsheet/Xls.php
tine20/Tinebase/FileSystem.php
tine20/Tinebase/Frontend/Cli.php
tine20/Tinebase/Frontend/Cli/Abstract.php
tine20/Tinebase/Frontend/Http.php
tine20/Tinebase/Frontend/Json.php
tine20/Tinebase/Frontend/Json/Abstract.php
tine20/Tinebase/Import/Abstract.php
tine20/Tinebase/Import/Csv/Abstract.php
tine20/Tinebase/ImportExportDefinition.php
tine20/Tinebase/Lock.php [new file with mode: 0644]
tine20/Tinebase/Model/Config.php
tine20/Tinebase/Model/Filter/Container.php
tine20/Tinebase/Model/Filter/FilterGroup.php
tine20/Tinebase/Model/Filter/Float.php [new file with mode: 0644]
tine20/Tinebase/Model/Filter/Int.php
tine20/Tinebase/Model/Filter/Query.php
tine20/Tinebase/Model/Preference.php
tine20/Tinebase/Model/TagRight.php
tine20/Tinebase/ModelConfiguration.php
tine20/Tinebase/Notification.php
tine20/Tinebase/Preference.php
tine20/Tinebase/Preference/Abstract.php
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Record/Iterator.php
tine20/Tinebase/Relation/Backend/Sql.php
tine20/Tinebase/Relations.php
tine20/Tinebase/Scheduler/Task.php
tine20/Tinebase/Server/Cli.php
tine20/Tinebase/Session/Abstract.php
tine20/Tinebase/Setup/Initialize.php
tine20/Tinebase/Setup/Update/Release8.php
tine20/Tinebase/Setup/setup.xml
tine20/Tinebase/Tags.php
tine20/Tinebase/Timemachine/ModificationLog.php
tine20/Tinebase/Tinebase.jsb2
tine20/Tinebase/User.php
tine20/Tinebase/User/ActiveDirectory.php
tine20/Tinebase/WebDav/Collection/AbstractContainerTree.php
tine20/Tinebase/css/ux/display/DisplayPanel.css
tine20/Tinebase/js/AppManager.js
tine20/Tinebase/js/Application.js
tine20/Tinebase/js/ApplicationStarter.js
tine20/Tinebase/js/ExceptionDialog.js
tine20/Tinebase/js/ExceptionHandler.js
tine20/Tinebase/js/LoginPanel.js
tine20/Tinebase/js/MainMenu.js
tine20/Tinebase/js/MainScreen.js
tine20/Tinebase/js/Models.js
tine20/Tinebase/js/common.js
tine20/Tinebase/js/data/Record.js
tine20/Tinebase/js/data/RecordProxy.js
tine20/Tinebase/js/extInit.js
tine20/Tinebase/js/tineInit.js
tine20/Tinebase/js/ux/PopupWindow.js
tine20/Tinebase/js/ux/WindowFactory.js
tine20/Tinebase/js/ux/display/DisplayField.js
tine20/Tinebase/js/ux/file/Upload.js
tine20/Tinebase/js/ux/form/ColorField.js
tine20/Tinebase/js/ux/form/LayerCombo.js
tine20/Tinebase/js/ux/form/NumberField.js
tine20/Tinebase/js/ux/util/Cookie.js [new file with mode: 0644]
tine20/Tinebase/js/ux/util/MixedLocalStorageCollection.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/ActivitiesPanel.js
tine20/Tinebase/js/widgets/TimezoneChooser.js
tine20/Tinebase/js/widgets/account/ChangeAccountAction.js
tine20/Tinebase/js/widgets/container/FilterModel.js
tine20/Tinebase/js/widgets/container/GrantsGrid.js
tine20/Tinebase/js/widgets/customfields/ConfigManager.js
tine20/Tinebase/js/widgets/dialog/DuplicateResolveGridPanel.js
tine20/Tinebase/js/widgets/dialog/EditDialog.js
tine20/Tinebase/js/widgets/dialog/ImportDialog.js
tine20/Tinebase/js/widgets/dialog/PreferencesPanel.js
tine20/Tinebase/js/widgets/dialog/SimpleImportDialog.js
tine20/Tinebase/js/widgets/dialog/WizardPanel.js
tine20/Tinebase/js/widgets/form/ConfigPanel.js
tine20/Tinebase/js/widgets/form/RecordPickerComboBox.js
tine20/Tinebase/js/widgets/grid/BbarGridPanel.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/grid/DetailsPanel.js
tine20/Tinebase/js/widgets/grid/ExportButton.js
tine20/Tinebase/js/widgets/grid/FileUploadGrid.js
tine20/Tinebase/js/widgets/grid/FilterPanel.js
tine20/Tinebase/js/widgets/grid/GridPanel.js
tine20/Tinebase/js/widgets/grid/PickerGridPanel.js
tine20/Tinebase/js/widgets/grid/QuickaddGridPanel.js
tine20/Tinebase/js/widgets/grid/RendererManager.js
tine20/Tinebase/js/widgets/keyfield/ComboBox.js
tine20/Tinebase/js/widgets/keyfield/ConfigField.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/keyfield/ConfigGrid.js [new file with mode: 0644]
tine20/Tinebase/js/widgets/keyfield/Store.js
tine20/Tinebase/js/widgets/tags/TagsMassAttachAction.js
tine20/Tinebase/js/widgets/tags/TagsPanel.js
tine20/Tinebase/translations/de.po
tine20/Tinebase/translations/template.pot
tine20/composer.json
tine20/composer.lock
tine20/library/Store/store.bind.js [new file with mode: 0644]
tine20/library/Store/store.compat.js [new file with mode: 0644]
tine20/library/Store/store2.js [new file with mode: 0644]

index 74c9372..d0ce696 100644 (file)
@@ -25,6 +25,7 @@ class Addressbook_Convert_Contact_VCard_AllTests
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook All Import Vcard Tests');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_FactoryTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_GenericTest');
+        $suite->addTestSuite('Addressbook_Convert_Contact_VCard_TelefonbuchTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_IOSTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_MacOSXTest');
         $suite->addTestSuite('Addressbook_Convert_Contact_VCard_SogoTest');
diff --git a/tests/tine20/Addressbook/Convert/Contact/VCard/TelefonbuchTest.php b/tests/tine20/Addressbook/Convert/Contact/VCard/TelefonbuchTest.php
new file mode 100644 (file)
index 0000000..4fa2b18
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Addressbook
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Michael Spahn <kontakt@michaelspahn.de>
+ */
+
+/**
+ * Test helper
+ */
+require_once dirname(dirname(dirname(dirname(dirname(__FILE__))))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
+
+if (!defined('PHPUnit_MAIN_METHOD')) {
+    define('PHPUnit_MAIN_METHOD', 'Addressbook_Convert_Contact_VCard_TelefonbuchTest::main');
+}
+
+/**
+ * Test class for Addressbook_Convert_Contact_VCard_TelefonbuchTest
+ */
+class Addressbook_Convert_Contact_VCard_TelefonbuchTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var array test objects
+     */
+    protected $objects = array();
+    
+    /**
+     * Runs the test methods of this class.
+     *
+     * @access public
+     * @static
+     */
+    public static function main()
+    {
+        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook WebDAV Telefonbuch Contact Tests');
+        PHPUnit_TextUI_TestRunner::run($suite);
+    }
+
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+    }
+
+    /**
+     * Tears down the fixture
+     * This method is called after a test is executed.
+     *
+     * @access protected
+     */
+    protected function tearDown()
+    {
+    }
+
+    /**
+     * test converting vcard from sogo connector to Addressbook_Model_Contact
+     * 
+     * @return Addressbook_Model_Contact
+     */
+    public function testConvertToTine20Model()
+    {
+        $vcardStream = fopen(dirname(__FILE__) . '/../../../Import/files/telefonbuch.vcf', 'r');
+
+        $converter = Addressbook_Convert_Contact_VCard_Factory::factory(Addressbook_Convert_Contact_VCard_Factory::CLIENT_TELEFONBUCH);
+
+        $contact = $converter->toTine20Model($vcardStream);
+
+        $this->assertEquals('Hamburg',                 $contact->adr_one_locality);
+        $this->assertEquals('12345',                   $contact->adr_one_postalcode);
+        $this->assertEquals('Teststraße 1',            $contact->adr_one_street);
+        $this->assertEquals('Spahn',                   $contact->n_family);
+        $this->assertEquals('Spahn, Michael',          $contact->n_fileas);
+        $this->assertEquals('Michael',                 $contact->n_given);
+        $this->assertEquals('040 12345',               $contact->tel_work);
+        $this->assertEquals('http://michaelspahn.de', $contact->url);
+
+        return $contact;
+    }
+}
index cbf247e..280d6a5 100644 (file)
@@ -4,48 +4,20 @@
  * 
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2015 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';
-
-/**
  * Test class for Addressbook_Import_Csv
  */
-class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
+class Addressbook_Import_CsvTest extends ImportTestCase
 {
-    /**
-     * @var Addressbook_Import_Csv instance
-     */
-    protected $_instance = NULL;
-    
-    /**
-     * @var string $_filename
-     */
-    protected $_filename = NULL;
-    
-    /**
-     * @var boolean
-     */
-    protected $_deleteImportFile = TRUE;
-    
-    protected $_deletePersonalContacts = FALSE;
-    
-    /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Addressbook Csv Import Tests');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
+    protected $_deletePersonalContacts = false;
+
+    protected $_importerClassName = 'Addressbook_Import_Csv';
+    protected $_exporterClassName = 'Addressbook_Export_Csv';
+    protected $_modelName = 'Addressbook_Model_Contact';
 
     /**
      * Sets up the fixture.
@@ -83,7 +55,7 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
     
     /**
      * test import duplicate data
-     * 
+     *
      * @return array
      */
     public function testImportDuplicates()
@@ -95,10 +67,10 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
         $result = $this->_doImport($options, 'adb_tine_import_csv', new Addressbook_Model_ContactFilter(array(
             array('field' => 'container_id', 'operator' => 'equals', 'value' => $internalContainer->getId()),
         )));
-        
+
         $this->assertGreaterThan(0, $result['duplicatecount'], 'no duplicates.');
         $this->assertTrue($result['exceptions'] instanceof Tinebase_Record_RecordSet);
-        
+
         return $result;
     }
     
@@ -124,7 +96,7 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
             }
         }
         
-        $this->assertTrue($found);
+        $this->assertTrue($found, 'did not find user record in import exceptions: ' . print_r($result['exceptions']->toArray(), true));
     }
 
     /**
@@ -173,7 +145,7 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
      */
     public function testImportCustomField()
     {
-        $customField = $this->_createCustomField();
+        $this->_createCustomField();
         
         // create/get new import/export definition with customfield
         $filename = dirname(__FILE__) . '/files/adb_google_import_csv_test.xml';
@@ -248,7 +220,7 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
     }
     
     /**
-     * returns import defintion from file
+     * returns import definition from file
      * 
      * @param string $filename
      * @return Tinebase_Model_ImportExportDefinition
@@ -263,31 +235,6 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
     }
     
     /**
-     * import helper
-     * 
-     * @param array $_options
-     * @param string|Tinebase_Model_ImportExportDefinition $_definition
-     * @param Addressbook_Model_ContactFilter $_exportFilter
-     * @return array
-     */
-    protected function _doImport(array $_options, $_definition, Addressbook_Model_ContactFilter $_exportFilter = NULL)
-    {
-        $definition = ($_definition instanceof Tinebase_Model_ImportExportDefinition) ? $_definition : Tinebase_ImportExportDefinition::getInstance()->getByName($_definition);
-        $this->_instance = Addressbook_Import_Csv::createFromDefinition($definition, $_options);
-        
-        // export first
-        if ($_exportFilter !== NULL) {
-            $exporter = new Addressbook_Export_Csv($_exportFilter, Addressbook_Controller_Contact::getInstance());
-            $this->_filename = $exporter->generate();
-        }
-        
-        // then import
-        $result = $this->_instance->importFile($this->_filename);
-        
-        return $result;
-    }
-    
-    /**
     * get custom field record
     * 
     * @param string $name
@@ -334,7 +281,7 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
         $this->_filename = dirname(__FILE__) . '/files/import_duplicate_1.csv';
         $this->_deleteImportFile = FALSE;
         
-        $result = $this->_doImport(array(), $definition);
+        $this->_doImport(array(), $definition);
         $this->_deletePersonalContacts = TRUE;
 
         $this->_filename = dirname(__FILE__) . '/files/import_duplicate_2.csv';
@@ -388,9 +335,43 @@ class Addressbook_Import_CsvTest extends PHPUnit_Framework_TestCase
         $this->assertEquals('Straßbough', $result['results'][1]['adr_one_locality'],
                 'should have changed the locality of contact #2: ' . print_r($result['results'][1]->toArray(), true));
         $this->assertEquals('Dr. Schutheiss', $result['results'][3]['n_family']);
-        $this->assertEquals(1, $result['results'][2]['seq'], 'Wolfer should not be updated - nothing changed');
+        // TODO this should be researched, imho the relation should not trigger an update of the record
+        $this->assertEquals(1, $result['results'][3]['seq'], 'Wolfer has been updated - relations changed');
         $this->assertEquals('Weixdorf DD', $result['results'][0]['adr_one_locality'], 'locality should persist');
         $this->assertEquals('Gartencenter Röhr & Vater', $result['results'][4]['n_fileas']);
         $this->assertEquals('Straßback', $result['results'][5]['adr_one_locality']);
     }
+
+    public function testSplitField()
+    {
+        $definition = $this->_getDefinitionFromFile('adb_import_csv_split.xml');
+
+        $this->_filename = dirname(__FILE__) . '/files/import_split.csv';
+        $this->_deleteImportFile = FALSE;
+
+        $result = $this->_doImport(array('dryrun' => true), $definition);
+
+        $this->assertEquals(1, $result['totalcount'], print_r($result, true));
+        $importedRecord = $result['results']->getFirstRecord();
+
+        $this->assertEquals('21222', $importedRecord->adr_one_postalcode, print_r($importedRecord->toArray(), true));
+        $this->assertEquals('Käln', $importedRecord->adr_one_locality, print_r($importedRecord->toArray(), true));
+    }
+
+    /**
+     * @see 0011354: keep both records if duplicates are within current import file
+     */
+    public function testImportDuplicateInImport()
+    {
+        $definition = $this->_getDefinitionFromFile('adb_import_csv_split.xml');
+
+        $this->_filename = dirname(__FILE__) . '/files/import_split_duplicate.csv';
+        $this->_deletePersonalContacts = TRUE;
+        $this->_deleteImportFile = FALSE;
+
+        $result = $this->_doImport(array('dryrun' => false), $definition);
+
+        $this->assertEquals(2, $result['totalcount'], print_r($result, true));
+        $this->assertEquals(2, count(array_unique($result['results']->getArrayOfIds())));
+    }
 }
diff --git a/tests/tine20/Addressbook/Import/files/adb_import_csv_split.xml b/tests/tine20/Addressbook/Import/files/adb_import_csv_split.xml
new file mode 100644 (file)
index 0000000..ec4109b
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config>
+    <name>adb_csv_split</name>
+    <model>Addressbook_Model_Contact</model>
+    <plugin>Addressbook_Import_Csv</plugin>
+    <type>import</type>
+    <headline>1</headline>
+    <dryrun>0</dryrun>
+    <delimiter>,</delimiter>
+    <label>Simple CSV import</label>
+    <description>simple import</description>
+    <extension>csv</extension>
+    <duplicateResolveStrategy>mergeTheirs</duplicateResolveStrategy>
+    <mapping>
+        <field>
+            <source>n_family</source>
+            <destination>n_family</destination>
+        </field>
+        <field>
+            <source>n_given</source>
+            <destination>n_given</destination>
+        </field>
+        <field>
+            <source>PLZ/Ort Kombination</source>
+            <destinations>
+                <destination>adr_one_postalcode</destination>
+                <destination>adr_one_locality</destination>
+            </destinations>
+        </field>
+    </mapping>
+</config>
\ No newline at end of file
diff --git a/tests/tine20/Addressbook/Import/files/import_split.csv b/tests/tine20/Addressbook/Import/files/import_split.csv
new file mode 100644 (file)
index 0000000..46c2f36
--- /dev/null
@@ -0,0 +1,2 @@
+"n_given","n_family","PLZ/Ort Kombination"\r
+"Jörg","Heinz","21222 Käln"\r
diff --git a/tests/tine20/Addressbook/Import/files/import_split_duplicate.csv b/tests/tine20/Addressbook/Import/files/import_split_duplicate.csv
new file mode 100644 (file)
index 0000000..204f1ab
--- /dev/null
@@ -0,0 +1,3 @@
+"n_given","n_family","PLZ/Ort Kombination"\r
+"Jörg","Heinz","21222 Käln"\r
+"Jörg","Heinz","21225 Kön"\r
diff --git a/tests/tine20/Addressbook/Import/files/telefonbuch.vcf b/tests/tine20/Addressbook/Import/files/telefonbuch.vcf
new file mode 100644 (file)
index 0000000..37a9fc4
--- /dev/null
@@ -0,0 +1,9 @@
+BEGIN:VCARD
+VERSION:2.1
+N:Spahn
+FN:Michael
+ADR:;;Teststraße 1;Hamburg;;12345;
+TEL;WORK:040 12345
+EMAIL;INTERNET:m.spahn@metaways.de
+URL;WORK:http://michaelspahn.de
+END:VCARD
index 383350b..6ab5621 100644 (file)
@@ -4,18 +4,13 @@
  *
  * @package     Addressbook
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  *
  * @todo        add testSetImage (NOTE: we can't test the upload yet, so we needd to simulate the upload)
  */
 
 /**
- * Test helper
- */
-require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-/**
  * Test class for Addressbook_Frontend_Json
  */
 class Addressbook_JsonTest extends TestCase
@@ -74,6 +69,8 @@ class Addressbook_JsonTest extends TestCase
      */
     protected $_groupIdsToDelete = NULL;
     
+    protected $_originalRoleRights = null;
+    
     /**
      * Runs the test methods of this class.
      *
@@ -157,8 +154,21 @@ class Addressbook_JsonTest extends TestCase
                 $this->objects['createdTagIds'] = array();
             }
         }
+        
+        $this->_resetOriginalRoleRights();
     }
-
+    
+    protected function _resetOriginalRoleRights()
+    {
+        if (! empty($this->_originalRoleRights)) {
+            foreach ($this->_originalRoleRights as $roleId => $rights) {
+                Tinebase_Acl_Roles::getInstance()->setRoleRights($roleId, $rights);
+            }
+            
+            $this->_originalRoleRights = null;
+        }
+    }
+    
     /**
      * try to get all contacts
      */
@@ -385,7 +395,6 @@ class Addressbook_JsonTest extends TestCase
         $this->_checkChangedNote($record['id'], 'adr_one_region ( -> PHPUNIT_multipleUpdate) url ( -> http://www.phpunit.de) customfields ( -> {');
         
         // check invalid data
-        
         $changes = array(
             array('name' => 'type', 'value' => 'Z'),
         );
@@ -489,8 +498,10 @@ class Addressbook_JsonTest extends TestCase
 
     /**
     * test attach multiple tags modlog
+    * 
+    * @param string $type tag type
     */
-    public function testAttachMultipleTagsModlog()
+    public function testAttachMultipleTagsModlog($type = Tinebase_Model_Tag::TYPE_SHARED)
     {
         $contact = $this->_addContact();
         $filter = new Addressbook_Model_ContactFilter(array(array(
@@ -498,7 +509,7 @@ class Addressbook_JsonTest extends TestCase
             'operator' => 'equals',
             'value'    =>  $contact['id']
         )));
-        $sharedTagName = $this->_createAndAttachTag($filter);
+        $sharedTagName = $this->_createAndAttachTag($filter, $type);
         $this->_checkChangedNote($contact['id'], array(',"name":"' . $sharedTagName . '","description":"testTagDescription"', 'tags ([] -> [{'));
     }
     
@@ -518,6 +529,38 @@ class Addressbook_JsonTest extends TestCase
     }
     
     /**
+     * testCreatePersonalTagWithoutRight
+     * 
+     * @see 0010732: add "use personal tags" right to all applications
+     */
+    public function testCreatePersonalTagWithoutRight()
+    {
+        $this->_originalRoleRights = $this->_removeRoleRight('Addressbook', Tinebase_Acl_Rights::USE_PERSONAL_TAGS);
+        
+        try {
+            $this->testAttachMultipleTagsModlog(Tinebase_Model_Tag::TYPE_PERSONAL);
+            $this->fail('personal tags right is disabled');
+        } catch (Tinebase_Exception $e) {
+            $this->assertTrue($e instanceof Tinebase_Exception_AccessDenied, 'did not get expected exception: ' . $e);
+        }
+    }
+    
+    /**
+     * testFetchPersonalTagWithoutRight
+     * 
+     * @see 0010732: add "use personal tags" right to all applications
+     */
+    public function testFetchPersonalTagWithoutRight()
+    {
+        $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']);
+        
+        $this->assertTrue(! isset($contact['tags']) || count($contact['tags'] === 0), 'record should not have any tags');
+    }
+    
+    /**
      * try to get contacts by owner
      *
      */
@@ -1679,4 +1722,27 @@ Steuernummer 33/111/32212";
         $result = $this->_instance->searchLists($filter, array());
         $this->assertEquals(0, $result['totalcount'], 'should not find hidden list: ' . print_r($result, TRUE));
     }
+
+    public function testAttachMultipleTagsToMultipleRecords()
+    {
+        $contact1 = $this->_addContact('contact1');
+        $contact2 = $this->_addContact('contact2');
+        $tag1 = Tinebase_Tags::getInstance()->create($this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL, 'tag1'));
+        $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();
+
+        $json->attachMultipleTagsToMultipleRecords($filter,'Addressbook_Model_ContactFilter',array(
+            $tag1->toArray(),
+            $tag2->toArray(),
+        ));
+
+        $result = $this->_instance->searchContacts($filter, array());
+        $this->assertCount(2, $result['results'], 'search count failed');
+
+        foreach($result['results'] as $contactData) {
+            $this->assertCount(2, $contactData['tags'], $contactData['n_fn'] . ' tags failed');
+        }
+    }
 }
index 58b4cb1..b103515 100644 (file)
@@ -1143,4 +1143,88 @@ class Admin_JsonTest extends TestCase
         $this->assertEquals($registryData['primarydomain'], 'localhost');
         $this->assertEquals($registryData['secondarydomains'], 'example.com');
     }
+
+//    public function testGetConfig()
+//    {
+//        $afj = new Admin_Frontend_Json();
+//        $config = $afj->getConfig('Calendar');
+//
+//        $this->assertGreaterThanOrEqual(2, count($config));
+//        $this->assertArrayHasKey('attendeeRoles', $config);
+//        $this->assertArrayHasKey('records', $config['attendeeRoles']);
+//        $this->assertGreaterThanOrEqual(1, $config['attendeeRoles']['records']);
+//    }
+//
+//    public function testSetConfig()
+//    {
+//        $afj = new Admin_Frontend_Json();
+//        $config = $afj->getConfig('Calendar');
+//
+//        $attendeeRoles = $config['attendeeRoles'];
+//        $attendeeRoles['records'][] = array(
+//            'id'    => 'CHAIR',
+//            'value' => 'Chair'
+//        );
+//
+//        $afj->setConfig('Calendar', 'attendeeRoles', $attendeeRoles);
+//
+//        $updatedConfig = $afj->getConfig('Calendar');
+//        $this->assertEquals(count($attendeeRoles['records']), count($updatedConfig['attendeeRoles']['records']));
+//    }
+
+    public function testSearchConfigs()
+    {
+        $afj = new Admin_Frontend_Json();
+
+        $result = $afj->searchConfigs(array(
+            'application_id' => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId()
+        ), array());
+
+        $this->assertGreaterThanOrEqual(2, $result['totalcount']);
+
+        $attendeeRoles = NULL;
+        foreach($result['results'] as $configData) {
+            if ($configData['name'] == 'attendeeRoles') {
+                $attendeeRoles = $configData;
+                break;
+            }
+        }
+
+        $this->assertNotNull($attendeeRoles);
+        $this->assertContains('{', $attendeeRoles);
+
+        return $attendeeRoles;
+    }
+
+    public function testGetConfig()
+    {
+        $attendeeRoles = $this->testUpdateConfig();
+
+        $afj = new Admin_Frontend_Json();
+        $fetchedAttendeeRoles = $afj->getConfig($attendeeRoles['id']);
+
+        $this->assertEquals($attendeeRoles['value'], $fetchedAttendeeRoles['value']);
+    }
+
+    public function testUpdateConfig()
+    {
+        $attendeeRoles = $this->testSearchConfigs();
+
+        $keyFieldConfig = json_decode($attendeeRoles['value'], true);
+        $keyFieldConfig['records'][] = array(
+            'id'    => 'CHAIR',
+            'value' => 'Chair'
+        );
+        $attendeeRoles['value'] = json_encode($keyFieldConfig);
+        $attendeeRoles['id'] = '';
+
+
+        $afj = new Admin_Frontend_Json();
+        $afj->saveConfig($attendeeRoles);
+
+        $updatedAttendeeRoles = $this->testSearchConfigs();
+
+        $this->assertEquals($attendeeRoles['value'], $updatedAttendeeRoles['value']);
+        return $updatedAttendeeRoles;
+    }
 }
index 8b1c92b..e4ac085 100644 (file)
@@ -1302,12 +1302,15 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
      * checks if notification mail is sent to configured mail address of a resource
      * 
      * @see 0009954: resource manager and email handling
+     *
+     * @param boolean $suppress_notification
      */
-    public function testResourceNotification()
+    public function testResourceNotification($suppress_notification = false)
     {
         // create resource with email address of unittest user
         $resource = $this->_getResource();
         $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
+        $resource->suppress_notification = $suppress_notification;
         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
         
         // create event with this resource as attender
@@ -1320,14 +1323,18 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
         $this->assertEquals(3, count($persistentEvent->attendee));
 
         $messages = self::getMessages();
-        
-        $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
+
+        if ($suppress_notification) {
+            $this->assertEquals(1, count($messages), 'one mails should be send to current user (only attender)');
+        } else {
+            $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
+        }
     }
 
     /**
      * Enable by a preference which sends mails to every user who got permissions to edit the resource
      */
-    public function testResourceNotificationForGrantedUsers()
+    public function testResourceNotificationForGrantedUsers($userIsAttendee = true)
     {
         // Enable feature, disabled by default!
         Calendar_Config::getInstance()->set(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS, true);
@@ -1338,6 +1345,16 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
 
         $event = $this->_getEvent(/*now = */ true);
         $event->attendee->addRecord($this->_createAttender($persistentResource->getId(), Calendar_Model_Attender::USERTYPE_RESOURCE));
+
+        if (! $userIsAttendee) {
+            // remove organizer attendee
+            foreach ($event->attendee as $idx => $attender) {
+                if ($attender->user_id === $event->organizer) {
+                    $event->attendee->removeRecord($attender);
+                }
+            }
+        }
+
         $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($resource->container_id);
 
         $newGrants = array(
@@ -1357,9 +1374,29 @@ class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
 
         Tinebase_Container::getInstance()->setGrants($resource->container_id, $grants);
 
+        $this->assertContains('Resource "' . $persistentResource->name . '" was booked', print_r($messages, true));
         $this->assertContains('Meeting Room (Required, No response)', print_r($messages, true));
-        $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody whos allowed to edit this resource)');
-        $this->assertEquals(3, count($persistentEvent->attendee));
+
+        $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody who is allowed to edit this resource)');
+        $this->assertEquals(count($event->attendee), count($persistentEvent->attendee));
+    }
+
+    /**
+     * @see 0011272: ressource invitation: organizer receives no mail if he is no attendee
+     */
+    public function testResourceNotificationForNonAttendeeOrganizer()
+    {
+        $this->testResourceNotificationForGrantedUsers(/* $userIsAttendee = */ false);
+    }
+
+    /**
+     * testResourceNotificationMuteForEditors
+     *
+     * @see 0011312: Make resource notification handling and default status configurable
+     */
+    public function testResourceNotificationMuteForEditors()
+    {
+        $this->testResourceNotification(/* $suppress_notification = */ false);
     }
     
     /**
index f8bd0fc..489b579 100644 (file)
@@ -183,7 +183,7 @@ class Calendar_Convert_Event_VCalendar_GenericTest extends PHPUnit_Framework_Tes
      */
     public function testConvertToTine20ModelWithGroupInvitation()
     {
-        $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Model_Config::SMTP, new Tinebase_Config_Struct())->toArray();
+        $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP, new Tinebase_Config_Struct())->toArray();
         if (!isset($smtpConfig['primarydomain'])) {
             $this->markTestSkipped('no primary smtp domain configured');
         }
index 8f49c0e..30e53c8 100644 (file)
@@ -191,7 +191,7 @@ class Calendar_Export_ICalTest extends Calendar_TestCase
     {
         $eventData = $this->_getEvent(TRUE)->toArray();
         $this->_uit = new Calendar_Frontend_Json();
-         $this->_uit->saveEvent($eventData);
+        $this->_uit->saveEvent($eventData);
         
         $this->_testNeedsTransaction();
         $cmd = realpath(__DIR__ . "/../../../../tine20/tine20.php") . ' --method Calendar.exportICS ' .
index 2567fa3..735e11b 100644 (file)
@@ -173,7 +173,6 @@ class Crm_Acl_RolesTest extends TestCase
     
     /**
      * try to add a role right
-     *
      */
     public function testSetRoleRight()
     {
index e1c5e51..827c838 100644 (file)
@@ -4,26 +4,12 @@
  *
  * @package     Crm
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2008 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2008-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Lars Kneschke <l.kneschke@metaways.de>
  */
 
-/**
- * Test helper
- */
-require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php';
-
-if (! defined('PHPUnit_MAIN_METHOD')) {
-    define('PHPUnit_MAIN_METHOD', 'Crm_AllTests::main');
-}
-
 class Crm_AllTests
 {
-    public static function main ()
-    {
-        PHPUnit_TextUI_TestRunner::run(self::suite());
-    }
-    
     public static function suite ()
     {
         $suite = new PHPUnit_Framework_TestSuite('Tine 2.0 Crm All Tests');
@@ -33,10 +19,7 @@ class Crm_AllTests
         $suite->addTestSuite('Crm_JsonTest');
         $suite->addTestSuite('Crm_NotificationsTests');
         $suite->addTestSuite('Crm_Acl_RolesTest');
+        $suite->addTestSuite('Crm_Import_CsvTest');
         return $suite;
     }
 }
-
-if (PHPUnit_MAIN_METHOD == 'Crm_AllTests::main') {
-    Crm_AllTests::main();
-}
index 0d284a0..184bd33 100644 (file)
@@ -16,7 +16,7 @@ require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHe
 /**
  * Test class for Crm_Backend_Lead
  */
-class Crm_Backend_LeadTest extends PHPUnit_Framework_TestCase
+class Crm_Backend_LeadTest extends TestCase
 {
     /**
      * Testcontainer
@@ -48,33 +48,12 @@ class Crm_Backend_LeadTest extends PHPUnit_Framework_TestCase
      */
     protected function setUp()
     {
-        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
+        parent::setUp();
         $this->_backend = new Crm_Backend_Lead();
-        
-        $personalContainer = Tinebase_Container::getInstance()->getPersonalContainer(
-            Zend_Registry::get('currentAccount'), 
-            'Crm', 
-            Zend_Registry::get('currentAccount'), 
-            Tinebase_Model_Grants::GRANT_EDIT
-        );
-        
-        if ($personalContainer->count() === 0) {
-            $this->_testContainer = Tinebase_Container::getInstance()->addPersonalContainer(Zend_Registry::get('currentAccount')->accountId, 'Crm', 'PHPUNIT');
-        } else {
-            $this->_testContainer = $personalContainer[0];
-        }
-    }
-    
-    /**
-     * Tears down the fixture
-     * 
-     * This method is called after a test is executed.
-     */
-    protected function tearDown()
-    {
-        Tinebase_TransactionManager::getInstance()->rollBack();
+
+        $this->_testContainer = $this->_getPersonalContainer('Crm');
     }
-    
+
     /**
      * try to add a lead
      * 
index f0a2cde..21720a5 100644 (file)
@@ -5,17 +5,12 @@
  * @package     Crm
  * @subpackage  Export
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2009-2015 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';
-
-/**
  * Test class for Crm_Export_Csv
  */
 class Crm_Export_CsvTest extends Crm_Export_AbstractTest
@@ -35,18 +30,6 @@ class Crm_Export_CsvTest extends Crm_Export_AbstractTest
     protected $_filename;
     
     /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Crm_Export_CsvTest');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
-
-    /**
      * Sets up the fixture.
      * This method is called before a test is executed.
      *
@@ -69,7 +52,7 @@ class Crm_Export_CsvTest extends Crm_Export_AbstractTest
         unlink($this->_filename);
         parent::tearDown();
     }
-
+    
     /**
      * test csv export
      * 
@@ -99,4 +82,24 @@ class Crm_Export_CsvTest extends Crm_Export_AbstractTest
         $dateString = Tinebase_Translation::dateToStringInTzAndLocaleFormat(NULL, NULL, NULL, 'date');
         $this->assertContains($dateString, $export, 'note date wrong');
     }
+    
+    /**
+     * test sorted csv export
+     * 
+     * @return void
+     * 
+     * @see 0010790: use current grid sort in exports
+     */
+    public function testSortedExportCsv()
+    {
+        $options = array(
+            'sortInfo' => array('field' => 'leadstate_id')
+        );
+        $this->_instance = new Crm_Export_Csv(new Crm_Model_LeadFilter($this->_getLeadFilter()), Crm_Controller_Lead::getInstance(), $options);
+        
+        $this->_filename = $this->_instance->generate();
+        
+        $export = file_get_contents($this->_filename);
+        $this->assertContains('"Metaways Infosystems GmbH"', $export);
+    }
 }
index 4422488..57625e6 100644 (file)
@@ -18,7 +18,7 @@ require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHe
 /**
  * Test class for Tinebase_Group
  */
-class Crm_Export_PdfTest extends PHPUnit_Framework_TestCase
+class Crm_Export_PdfTest extends TestCase
 {
     /**
      * @var array test objects
@@ -26,16 +26,9 @@ class Crm_Export_PdfTest extends PHPUnit_Framework_TestCase
     protected $objects = array();
 
     /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
+     * @var Tinebase_Model_Container
      */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Crm_Export_PdfTest');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
+    protected $_testContainer = null;
 
     /**
      * Sets up the fixture.
@@ -45,27 +38,15 @@ class Crm_Export_PdfTest extends PHPUnit_Framework_TestCase
      */
     protected function setUp()
     {
-        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
-        
-        $personalContainer = Tinebase_Container::getInstance()->getPersonalContainer(
-            Zend_Registry::get('currentAccount'), 
-            'Crm', 
-            Zend_Registry::get('currentAccount'), 
-            Tinebase_Model_Grants::GRANT_EDIT
-        );
-        
-        if($personalContainer->count() === 0) {
-            $this->testContainer = Tinebase_Container::getInstance()->addPersonalContainer(Zend_Registry::get('currentAccount')->accountId, 'Crm', 'PHPUNIT');
-        } else {
-            $this->testContainer = $personalContainer[0];
-        }
-        
+        parent::setUp();
+        $this->_testContainer = $this->_getPersonalContainer('Crm');
+
         $this->objects['lead'] = new Crm_Model_Lead(array(
             'lead_name'     => 'PHPUnit',
             'leadstate_id'  => 1,
             'leadtype_id'   => 1,
             'leadsource_id' => 1,
-            'container_id'     => $this->testContainer->id,
+            'container_id'  => $this->_testContainer->id,
             'start'         => new Tinebase_DateTime( "2007-12-12" ),
             'description'   => 'Lead Description',
             'end'           => Tinebase_DateTime::now(),
@@ -79,7 +60,7 @@ class Crm_Export_PdfTest extends PHPUnit_Framework_TestCase
             'leadstate_id'  => 1,
             'leadtype_id'   => 1,
             'leadsource_id' => 1,
-            'container_id'     => $this->testContainer->id,
+            'container_id'  => $this->_testContainer->id,
             'start'         => new Tinebase_DateTime( "2007-12-24" ),
             'description'   => 'Lead Description',
             'end'           => Tinebase_DateTime::now(),
@@ -133,22 +114,11 @@ class Crm_Export_PdfTest extends PHPUnit_Framework_TestCase
             'summary'               => 'task test',
         ));
         
-        $lead = Crm_Controller_Lead::getInstance()->create($this->objects['leadWithLink']);
+        Crm_Controller_Lead::getInstance()->create($this->objects['leadWithLink']);
         $this->objects['linkedContact'] = Addressbook_Controller_Contact::getInstance()->create($this->objects['linkedContact'], FALSE);
     }
 
     /**
-     * Tears down the fixture
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
-    protected function tearDown()
-    {
-        Tinebase_TransactionManager::getInstance()->rollBack();
-    }
-    
-    /**
      * try to create a pdf
      *
      */
diff --git a/tests/tine20/Crm/Import/CsvTest.php b/tests/tine20/Crm/Import/CsvTest.php
new file mode 100644 (file)
index 0000000..8dbe038
--- /dev/null
@@ -0,0 +1,167 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Crm
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test class for Crm_Import_Csv
+ */
+class Crm_Import_CsvTest extends ImportTestCase
+{
+    protected $_importerClassName = 'Crm_Import_Csv';
+    protected $_exporterClassName = 'Crm_Export_Csv';
+    protected $_modelName         = 'Crm_Model_Lead';
+
+    protected $_tasksToDelete = array();
+
+    /**
+     * tear down tests
+     */
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        // delete tasks
+        Tasks_Controller_Task::getInstance()->delete($this->_tasksToDelete);
+
+        Crm_Config::getInstance()->set(Crm_Config::LEAD_IMPORT_AUTOTASK, false);
+        Crm_Config::getInstance()->set(Crm_Config::LEAD_IMPORT_NOTIFICATION, false);
+    }
+    /**
+     * test import
+     *
+     * @param boolean $dryrun
+     * @return array
+     */
+    public function testImport($dryrun = true)
+    {
+        $result = $this->_importHelper('leads.csv', 'crm_tine_import_csv', $dryrun);
+        $this->assertEquals(2, $result['totalcount'], 'should import 2 records: ' . print_r($result, true));
+
+        $firstLead = $result['results']->getFirstRecord();
+        $this->assertContains('neuer lead', $firstLead->lead_name);
+        $this->assertEquals(1, count($firstLead->tags));
+        $this->assertEquals(5, count($firstLead->relations),
+            'relations not imported for first lead ' . print_r($firstLead->toArray(), true));
+        $this->assertEquals(6, count($result['results'][1]->relations),
+            'relations not imported for second lead ' . print_r($result['results'][1]->toArray(), true));
+
+        return $result;
+    }
+
+    /**
+     * import helper
+     *
+     * @param        $importFilename
+     * @param string $definitionName
+     * @param bool   $dryrun
+     * @return array
+     * @throws Tinebase_Exception_NotFound
+     */
+    protected function _importHelper($importFilename, $definitionName = 'crm_tine_import_csv', $dryrun = true)
+    {
+        $this->_testNeedsTransaction();
+
+        $this->_testContainer = $this->_getTestContainer('Crm');
+        $this->_filename = dirname(__FILE__) . '/files/' . $importFilename;
+        $this->_deleteImportFile = false;
+
+        $options = array(
+            'container_id'  => $this->_testContainer->getId(),
+            'dryrun' => $dryrun,
+        );
+
+        $result = $this->_doImport($options, $definitionName);
+
+        return $result;
+    }
+
+    /**
+     * @see 0011234: automatically add task for responsible person on lead import
+     */
+    public function testAutoTaskImport()
+    {
+        Crm_Config::getInstance()->set(Crm_Config::LEAD_IMPORT_AUTOTASK, true);
+        $personalContainerOfSClever = $this->_getPersonalContainer('Tasks', $this->_personas['sclever']);
+        $this->_setPersonaGrantsForTestContainer($personalContainerOfSClever->getId(), 'sclever', true, false);
+
+        $result = $this->testImport(/* dry run = */ false);
+        foreach ($result['results'] as $lead) {
+            foreach ($lead->relations as $relation) {
+                if ($relation->type === 'TASK') {
+                    $this->_tasksToDelete[] = $relation->related_id;
+                }
+            }
+        }
+
+        $tasks = $this->_searchTestTasks($personalContainerOfSClever->getId());
+        $this->assertEquals(1, count($tasks), 'could not find task in sclevers container: '
+            . print_r($personalContainerOfSClever->toArray(), true));
+        $task = $tasks->getFirstRecord();
+        $this->assertEquals($this->_personas['sclever']['accountId'], $task->organizer);
+        $this->assertEquals('IN-PROCESS', $task->status);
+    }
+
+    /**
+     * search tasks
+     *
+     * @param      $containerId
+     * @param null $summary
+     * @return array|Tinebase_Record_RecordSet
+     */
+    protected function _searchTestTasks($containerId, $summary = null)
+    {
+        if (! $summary) {
+            $translate = Tinebase_Translation::getTranslation('Crm');
+            $summary = $translate->_('Edit new lead');
+        }
+        $tasksFilter = new Tasks_Model_TaskFilter(array(
+            array('field' => 'container_id', 'operator' => 'equals', 'value' => $containerId),
+            array('field' => 'summary', 'operator' => 'contains', 'value' => $summary),
+        ));
+        $tasks = Tasks_Controller_Task::getInstance()->search($tasksFilter);
+        $this->_tasksToDelete = array_merge($this->_tasksToDelete, $tasks->getArrayOfIds());
+        return $tasks;
+    }
+
+    /**
+     * @see 0011376: send mail on lead import to responsibles
+     */
+    public function testEmailNotification()
+    {
+        $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP, new Tinebase_Config_Struct())->toArray();
+        if (empty($smtpConfig)) {
+            $this->markTestSkipped('No SMTP config found: this is needed to send notifications.');
+        }
+
+        Crm_Config::getInstance()->set(Crm_Config::LEAD_IMPORT_NOTIFICATION, true);
+        $this->testImport(/* dry run = */ false);
+        // mark tasks for deletion
+        $this->_searchTestTasks(Tinebase_Container::getInstance()->getDefaultContainer('Tasks_Model_Task')->getId(), 'task');
+
+        // assert emails for responsibles
+        $messages = self::getMessages();
+        $this->assertGreaterThan(1, count($messages));
+
+        $translate = Tinebase_Translation::getTranslation('Crm');
+        $importNotifications = array();
+        $subjectToMatch = sprintf($translate->_('%s new leads have been imported'), 1);
+        foreach ($messages as $message) {
+            if ($message->getSubject() == $subjectToMatch) {
+                $importNotifications[] = $message;
+            }
+        }
+
+        $this->assertGreaterThan(1, count($importNotifications),
+            'expecting 2 or more mails (at least for unittest + sclever) / messages:'
+            . print_r($messages, true));
+        $firstMessage = $importNotifications[0];
+        $this->assertContains('neuer lead 2', $firstMessage->getBodyText()->getContent(), 'lead name missing');
+        $this->assertContains('PHPUnit', $firstMessage->getBodyText()->getContent(), 'container name missing');
+    }
+}
diff --git a/tests/tine20/Crm/Import/files/leads.csv b/tests/tine20/Crm/Import/files/leads.csv
new file mode 100644 (file)
index 0000000..b3c0386
--- /dev/null
@@ -0,0 +1,3 @@
+"lead_name","leadstate_id","Leadstate","leadtype_id","Leadtype","leadsource_id","Leadsource","container_id","start","description","end","turnover","probableTurnover","probability","end_scheduled","resubmission_date","tags","attachments","notes","seq","CUSTOMER","PARTNER","RESPONSIBLE","TASK","PRODUCT"
+"neuer lead 2","2","kontaktiert","1","Kunde","2","Email","","2015-01-31 00:00:00","","","0","0","10","","","","","31.01.2015 17:39:58 - created by schüle, phil","1","derkunde","schüle","clever","task1","product1"
+"neuer lead 1","1","","1","Kunde","1","","","2015-01-31 00:00:00","","","0","0","0","","","","","31.01.2015 17:39:42 - created by schüle, phil","1","derkunde","schüle","unittest","task2","product1;product2"
index 6bbb3a1..189ea78 100644 (file)
@@ -33,6 +33,14 @@ class Crm_JsonTest extends Crm_AbstractTest
      */
     protected $_fsController;
 
+
+    /**
+     * customfield name
+     *
+     * @var string
+     */
+    protected $_cfcName = null;
+
    /**
      * Sets up the fixture.
      * This method is called before a test is executed.
@@ -45,6 +53,7 @@ class Crm_JsonTest extends Crm_AbstractTest
         
         $this->_instance = new Crm_Frontend_Json();
         $this->_fsController = Tinebase_FileSystem::getInstance();
+        Crm_Controller_Lead::getInstance()->duplicateCheckFields(array());
     }
 
     /**
@@ -66,6 +75,7 @@ class Crm_JsonTest extends Crm_AbstractTest
         }
         
         parent::tearDown();
+        Crm_Controller_Lead::getInstance()->duplicateCheckFields(array('lead_name'));
     }
      
     /**
@@ -176,13 +186,15 @@ class Crm_JsonTest extends Crm_AbstractTest
         $this->assertEquals($searchLeads['results'][0]['turnover']*$getLead['probability']/100, $searchLeads['results'][0]['probableTurnover']);
         // now we need 2 relations here (frontend search shall return relations with related_model Addressbook_Model_Contact or Sales_Model_Product
         $this->assertEquals(2, count($searchLeads['results'][0]['relations']), 'did not get all relations');
-        
+
+        $relatedTask = null;
         foreach($getLead['relations'] as $rel) {
             if ($rel['type'] == 'TASK') {
                 $relatedTask = $rel['related_record'];
             }
         }
-        
+
+        $this->assertTrue($relatedTask !== null);
         $this->assertEquals($this->_getTask()->summary, $relatedTask['summary'], 'task summary does not match');
         $defaultTaskContainerId = Tinebase_Core::getPreference('Tasks')->getValue(Tasks_Preference::DEFAULTTASKLIST);
         $this->assertEquals($defaultTaskContainerId, $relatedTask['container_id']);
@@ -382,7 +394,7 @@ class Crm_JsonTest extends Crm_AbstractTest
         
         $taskData = $taskJson->saveTask($taskData);
         $taskData['description'] = 1;
-        $taskData = $taskJson->saveTask($taskData);
+        $taskJson->saveTask($taskData);
         
         $savedLead = $this->_instance->getLead($leadData['id']);
         $savedLead['relations'][0]['related_record']['description'] = '2';
@@ -391,7 +403,7 @@ class Crm_JsonTest extends Crm_AbstractTest
         // client may send wrong seq -> this should cause a concurrency conflict
         $savedLead['relations'][0]['related_record']['seq'] = 0;
         try {
-            $savedLead = $this->_instance->saveLead($savedLead);
+            $this->_instance->saveLead($savedLead);
             $this->fail('expected concurrency exception');
         } catch (Tinebase_Timemachine_Exception_ConcurrencyConflict $ttecc) {
             $this->assertEquals('concurrency conflict!', $ttecc->getMessage());
@@ -775,10 +787,10 @@ class Crm_JsonTest extends Crm_AbstractTest
      */
     public function testSortByLeadState()
     {
-        $savedLead1 = $this->_saveLead();
+        $this->_saveLead();
         $lead2 = $this->_getLead()->toArray();  // open
         $lead2['leadstate_id'] = 2;             // contacted
-        $savedLead2 = $this->_instance->saveLead($lead2);
+        $this->_instance->saveLead($lead2);
         
         $sort = array(
             'sort' => 'leadstate_id',
@@ -788,4 +800,21 @@ class Crm_JsonTest extends Crm_AbstractTest
         
         $this->assertEquals(2, $searchLeads['results'][0]['leadstate_id'], 'leadstate "contacted" should come first');
     }
+    
+    /**
+     * testAdvancedSearch in related products
+     * 
+     * @see 0010814: quicksearch should search in related records
+     */
+    public function testAdvancedSearchInProduct()
+    {
+        Tinebase_Core::getPreference()->setValue(Tinebase_Preference::ADVANCED_SEARCH, true);
+        
+        $this->_saveLead();
+        $filter = array(
+            array('field' => 'query',           'operator' => 'contains',       'value' => 'PHPUnit test product'),
+        );
+        $searchLeads = $this->_instance->searchLeads($filter, '');
+        $this->assertEquals(1, $searchLeads['totalcount']);
+    }
 }
index 6a6586e..fc810aa 100644 (file)
@@ -21,11 +21,6 @@ class Crm_NotificationsTests extends Crm_AbstractTest
     protected $_leadController;
     
     /**
-     * @var Zend_Mail_Transport_Array
-     */
-    protected static $_mailer = NULL;
-    
-    /**
      * (non-PHPdoc)
      * @see tests/tine20/Crm/AbstractTest::setUp()
      */
index d4c2b94..ece137f 100644 (file)
@@ -26,7 +26,6 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
      */
     public function setUp()
     {
-        // enable courses app
         Tinebase_Application::getInstance()->setApplicationState(array(
             Tinebase_Application::getInstance()->getApplicationByName('ExampleApplication')->getId()
         ), Tinebase_Application::ENABLED);
@@ -79,7 +78,7 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
         $searchDefaultFilter = $this->_getFilter();
         $mergedSearchFilter = array_merge($searchIDFilter, $searchDefaultFilter);
         
-        $returned = $this->_json->searchExampleRecords($searchDefaultFilter, $this->_getPaging());
+        $returned = $this->_json->searchExampleRecords($mergedSearchFilter, $this->_getPaging());
         
         $this->assertEquals($returned['totalcount'], 1);
         
@@ -104,7 +103,8 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
     public function testSearchExampleRecordsTags()
     {
         $exampleRecordWithTag = $this->testCreateExampleRecord();
-        $exampleRecordWithoutTag = $this->testCreateExampleRecord();
+        // create a second record with no tag
+        $this->testCreateExampleRecord();
         
         $exampleRecordWithTag['tags'] = array(array(
             'name'    => 'supi',
@@ -132,7 +132,6 @@ class ExampleApplication_JsonTest extends ExampleApplication_TestCase
         $this->assertEquals($returnValueDeletion['status'], 'success');
         
         $this->setExpectedException('Tinebase_Exception_NotFound');
-        $returnValueGet = $this->_json->getExampleRecord($exampleRecordID);
+        $this->_json->getExampleRecord($exampleRecordID);
     }
-    
 }
index 0c2375e..5e45272 100644 (file)
@@ -16,60 +16,37 @@ require_once dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'TestHelper.php'
 /**
  * Test class for Inventory_TestCase
  */
-class ExampleApplication_TestCase extends PHPUnit_Framework_TestCase
+class ExampleApplication_TestCase extends TestCase
 {
     /**
      * @var ExampleApplication_Frontend_Json
      */
     protected $_json = array();
-    
-    /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 ExampleApplication Json Tests');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
-    
-    /**
-     * Sets up the fixture.
-     * This method is called before a test is executed.
-     *
-     * @access protected
-     */
-    protected function setUp()
-    {
-        Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb());
-    }
-    
+
     /**
      * get ExampleRecord record
      *
-     * @return Inventory_Model_ExampleRecord
+     * @return ExampleApplication_Model_ExampleRecord
      */
     protected function _getExampleRecord()
     {
         return new ExampleApplication_Model_ExampleRecord(array(
-                'name' => 'minimal example record by PHPUnit::ExampleApplication_JsonTest'
+            'name' => 'minimal example record by PHPUnit::ExampleApplication_JsonTest'
         ));
     }
     
     /**
      * get filter for ExampleApplication search
      *
-     * @return Tasks_Model_Task
+     * @return array
      */
     protected function _getFilter()
     {
         // define filter
         return array(
-                array('field' => 'container_id', 'operator' => 'specialNode', 'value' => 'all'),
-                array('field' => 'name'        , 'operator' => 'contains',    'value' => 'example record by PHPUnit'),
-                array('field' => 'due'         , 'operator' => 'within',      'value' => 'dayThis'),
+            array('field' => 'container_id', 'operator' => 'specialNode', 'value' => 'all'),
+            array('field' => 'name'        , 'operator' => 'contains',    'value' => 'example record by PHPUnit'),
+            array('field' => 'due'         , 'operator' => 'within',      'value' => 'dayThis'),
         );
     }
     
@@ -82,21 +59,10 @@ class ExampleApplication_TestCase extends PHPUnit_Framework_TestCase
     {
         // define paging
         return array(
-                'start' => 0,
-                'limit' => 50,
-                'sort' => 'name',
-                'dir' => 'ASC',
+            'start' => 0,
+            'limit' => 50,
+            'sort' => 'name',
+            'dir' => 'ASC',
         );
     }
-    
-    /**
-     * Tears down the fixture
-     * This method is called after a test is executed.
-     *
-     * @access protected
-     */
-    protected function tearDown()
-    {
-        Tinebase_TransactionManager::getInstance()->rollBack();
-    }
 }
index 5197732..3e220ed 100644 (file)
@@ -141,4 +141,20 @@ class Filemanager_Controller_DownloadLinkTests extends TestCase
             $this->assertEquals('Download link has expired', $tead->getMessage());
         }
     }
+
+    /**
+     * testDownloadLinkAccessCount
+     */
+    public function testDownloadLinkAccessCount()
+    {
+        $initialDownloadLink = $this->testCreateDownloadLink();
+
+        // simulate two concurrent downloads
+        $this->_getUit()->increaseAccessCount($initialDownloadLink);
+        $this->_getUit()->increaseAccessCount($initialDownloadLink);
+
+        $downloadLink = $this->_getUit()->get($initialDownloadLink->getId());
+
+        $this->assertEquals(2, $downloadLink->access_count);
+    }
 }
diff --git a/tests/tine20/ImportTestCase.php b/tests/tine20/ImportTestCase.php
new file mode 100644 (file)
index 0000000..eebc320
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Tests
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * abstract Test class for import tests
+ * 
+ * @package     Tests
+ */
+abstract class ImportTestCase extends TestCase
+{
+    /**
+     * importer instance
+     * 
+     * @var Object
+     */
+    protected $_instance = null;
+
+    /**
+     * @var string $_filename of the export
+     */
+    protected $_filename = null;
+
+    /**
+     * @var boolean
+     *
+     * TODO needed here?
+     */
+    protected $_deleteImportFile = true;
+
+    protected $_importerClassName = null;
+    protected $_exporterClassName = null;
+    protected $_modelName = null;
+    protected $_testContainer = null;
+
+    /**
+     * tear down tests
+     */
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        if ($this->_testContainer) {
+            Tinebase_Container::getInstance()->deleteContainer($this->_testContainer);
+        }
+    }
+
+    /**
+     * import helper
+     *
+     * @param array $_options
+     * @param string|Tinebase_Model_ImportExportDefinition $_definition
+     * @param Tinebase_Model_Filter_FilterGroup $_exportFilter
+     * @throws Tinebase_Exception_NotFound
+     * @return array
+     */
+    protected function _doImport(array $_options, $_definition, Tinebase_Model_Filter_FilterGroup $_exportFilter = NULL)
+    {
+        if (! $this->_importerClassName || ! $this->_modelName) {
+            throw new Tinebase_Exception_NotFound('No import class or model name given');
+        }
+
+        $definition = ($_definition instanceof Tinebase_Model_ImportExportDefinition) ? $_definition : Tinebase_ImportExportDefinition::getInstance()->getByName($_definition);
+        $this->_instance = call_user_func_array($this->_importerClassName . '::createFromDefinition' , array($definition, $_options));
+
+        // export first
+        if ($_exportFilter !== NULL && $this->_exporterClassName) {
+            $exporter = new $this->_exporterClassName($_exportFilter, Tinebase_Core::getApplicationInstance($this->_modelName));
+            $this->_filename = $exporter->generate();
+        }
+
+        // then import
+        $result = $this->_instance->importFile($this->_filename);
+
+        return $result;
+    }
+}
index 5b32961..fa42eaf 100644 (file)
@@ -32,6 +32,8 @@ class Sales_AllTests
         $suite->addTestSuite('Sales_Backend_CostCenterTest');
         $suite->addTestSuite('Sales_ControllerTest');
         $suite->addTestSuite('Sales_JsonTest');
+        $suite->addTestSuite('Sales_SuppliersTest');
+        $suite->addTestSuite('Sales_PurchaseInvoiceTest');
         $suite->addTestSuite('Sales_CustomFieldTest');
         $suite->addTestSuite('Sales_InvoiceControllerTests');
         $suite->addTestSuite('Sales_InvoiceJsonTests');
index f5670c5..c239fb0 100644 (file)
@@ -9,6 +9,7 @@
  * 
  */
 
+
 /**
  * Test class for Sales Invoice Controller
  */
@@ -319,7 +320,227 @@ class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
             $this->assertTrue($ts->invoice_id == NULL, print_r($ts->toArray(), 1));
         }
     }
-    
+
+    protected function _createInvoiceUpdateRecreationFixtures($createTimesheet = true)
+    {
+        $this->_createFullFixtures();
+
+        // we dont want this contract 1 to be part of the runs below, move it out of the way
+        $this->_contractRecords->getByIndex(0)->start_date->addMonth(12);
+        Sales_Controller_Contract::getInstance()->update($this->_contractRecords->getByIndex(0));
+
+        $date = clone $this->_referenceDate;
+        $customer4Timeaccount = $this->_timeaccountRecords->filter('title', 'TA-for-Customer4')->getFirstRecord();
+        $customer4Timeaccount->status = 'to bill';
+        $customer4Timeaccount->budget = NULL;
+
+        if (null === $this->_timesheetController)
+            $this->_timesheetController = Timetracker_Controller_Timesheet::getInstance();
+        if (null === $this->_timeaccountController)
+            $this->_timeaccountController = Timetracker_Controller_Timeaccount::getInstance();
+        $this->_timeaccountController->update($customer4Timeaccount);
+
+        // this is a ts on 20xx-03-18
+        $this->sharedTimesheet = new Timetracker_Model_Timesheet(array(
+            'account_id' => Tinebase_Core::getUser()->getId(),
+            'timeaccount_id' => $customer4Timeaccount->getId(),
+            'start_date' => $date->addMonth(2)->addDay(17),
+            'duration' => 120,
+            'description' => 'ts from ' . (string) $date,
+        ));
+        if (true === $createTimesheet)
+            $this->_timesheetController->create($this->sharedTimesheet);
+
+        //run autoinvoicing with 20xx-04-01
+        $date = clone $this->_referenceDate;
+        $date->addMonth(3);
+        $result = $this->_invoiceController->createAutoInvoices($date);
+        $this->assertEquals(2, count($result['created']));
+
+        return $result;
+    }
+
+    public function testInvoiceRecreation()
+    {
+        $result = $this->_createInvoiceUpdateRecreationFixtures();
+
+        $oldInvoiceId0 = $result['created'][0];
+        $ipc = Sales_Controller_InvoicePosition::getInstance();
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $oldInvoiceId0),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(9, $positions->count());
+
+        $oldInvoiceId1 = $result['created'][1];
+        $ipc = Sales_Controller_InvoicePosition::getInstance();
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $oldInvoiceId1),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(4, $positions->count());
+
+        $contract4 = $this->_contractRecords->getByIndex(3);
+        $filter = new Sales_Model_ProductAggregateFilter(
+            array(
+                array('field' => 'interval', 'operator' => 'equals', 'value' => 3),
+                //array('field' => 'contract_id', 'operator' => 'equals', 'value' => $this->_contractRecords->getByIndex(3)->getId()),
+            ), 'AND');
+        $filter->addFilter(new Tinebase_Model_Filter_ForeignId(//ExplicitRelatedRecord(
+            array('field' => 'contract_id', 'operator' => 'AND', 'value' =>
+                array(
+                    array(
+                        'field' =>  ':id', 'operator' => 'equals', 'value' => $contract4->getId()
+                    )
+                ),
+                'options' => array(
+                    'controller'        => 'Sales_Controller_Contract',
+                    'filtergroup'       => 'Sales_Model_ContractFilter',
+                    //'own_filtergroup'   => 'Sales_Model_ProductAggregateFilter',
+                    //'own_controller'    => 'Sales_Controller_ProductAggregate',
+                    //'related_model'     => 'Sales_Model_Contract',
+                    'modelName' => 'Sales_Model_Contract',
+                ),
+            )
+        ));
+
+        $pA = Sales_Controller_ProductAggregate::getInstance()->search($filter);
+        $this->assertEquals(1, $pA->count());
+        $pA = $pA->getFirstRecord();
+        $pA->interval = 4;
+        Sales_Controller_ProductAggregate::getInstance()->update($pA);
+        $contract4->title = $contract4->getTitle() . ' changed';
+        sleep(1);
+        $this->_contractController->update($contract4);
+
+        $this->sharedTimesheet->id = NULL;
+        $this->_timesheetController->create($this->sharedTimesheet);
+
+        $result = $this->_invoiceController->checkForContractOrInvoiceUpdates();
+        $this->assertEquals(2, count($result));
+        $this->assertNotEquals($oldInvoiceId0, $result[0]);
+        $this->assertNotEquals($oldInvoiceId1, $result[1]);
+
+        $this->_checkInvoiceUpdateExistingTimeaccount($result[1]);
+
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $result[0]),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(10, $positions->count());
+
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $result[1]),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(1, $positions->count());
+    }
+
+    /**
+     *
+     */
+    public function testInvoiceUpdateExistingTimeaccount()
+    {
+        $result = $this->_createInvoiceUpdateRecreationFixtures();
+
+        $this->sharedTimesheet->id = NULL;
+        $this->_timesheetController->create($this->sharedTimesheet);
+
+        $this->_invoiceController->checkForUpdate($result['created'][1]);
+
+        $this->_checkInvoiceUpdateExistingTimeaccount($result['created'][1]);
+
+        //check that the same update run doesnt do anything anymore
+        $this->_invoiceController->checkForUpdate($result['created'][1]);
+
+        $this->_checkInvoiceUpdateExistingTimeaccount($result['created'][1]);
+    }
+
+    public function testCheckForContractOrInvoiceUpdatesExistingTimeaccount()
+    {
+        $result = $this->_createInvoiceUpdateRecreationFixtures();
+
+        $this->sharedTimesheet->id = NULL;
+        $this->_timesheetController->create($this->sharedTimesheet);
+
+        $this->_invoiceController->checkForContractOrInvoiceUpdates();
+
+        $this->_checkInvoiceUpdateExistingTimeaccount($result['created'][1]);
+
+        $this->_invoiceController->checkForContractOrInvoiceUpdates();
+
+        $this->_checkInvoiceUpdateExistingTimeaccount($result['created'][1]);
+    }
+
+    protected function _checkInvoiceUpdateExistingTimeaccount($invoiceId)
+    {
+        $ipc = Sales_Controller_InvoicePosition::getInstance();
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'model', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount'),
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $invoiceId),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(1, $positions->count());
+        $this->assertEquals(4, $positions->getFirstRecord()->quantity);
+    }
+
+    protected function _checkInvoiceUpdateWithNewTimeaccount($invoiceId)
+    {
+        $ipc = Sales_Controller_InvoicePosition::getInstance();
+        $f = new Sales_Model_InvoicePositionFilter(array(
+            array('field' => 'model', 'operator' => 'equals', 'value' => 'Timetracker_Model_Timeaccount'),
+            array('field' => 'invoice_id', 'operator' => 'AND', 'value' => array(
+                array('field' => 'id', 'operator' => 'equals', 'value' => $invoiceId),
+            )),
+        ));
+        $positions = $ipc->search($f);
+        $this->assertEquals(1, $positions->count());
+        $this->assertEquals(2, $positions->getFirstRecord()->quantity);
+    }
+    /**
+     *
+     */
+    public function testInvoiceUpdateWithNewTimeaccount()
+    {
+        $result = $this->_createInvoiceUpdateRecreationFixtures(false);
+
+        $this->_timesheetController->create($this->sharedTimesheet);
+
+        $this->_invoiceController->checkForUpdate($result['created'][1]);
+
+        $this->_checkInvoiceUpdateWithNewTimeaccount($result['created'][1]);
+
+        //check that the same update run doesnt do anything anymore
+        $this->_invoiceController->checkForUpdate($result['created'][1]);
+
+        $this->_checkInvoiceUpdateWithNewTimeaccount($result['created'][1]);
+    }
+
+    public function testCheckForContractOrInvoiceUpdatesWithNewTimeaccount()
+    {
+        $result = $this->_createInvoiceUpdateRecreationFixtures(false);
+
+        $this->_timesheetController->create($this->sharedTimesheet);
+
+        $this->_invoiceController->checkForContractOrInvoiceUpdates();
+
+        $this->_checkInvoiceUpdateWithNewTimeaccount($result['created'][1]);
+
+        $this->_invoiceController->checkForContractOrInvoiceUpdates();
+
+        $this->_checkInvoiceUpdateWithNewTimeaccount($result['created'][1]);
+    }
+
     /**
      * @see: rt127444
      * 
@@ -340,7 +561,7 @@ class Sales_InvoiceControllerTests extends Sales_InvoiceTestCase
         $taController = Timetracker_Controller_Timeaccount::getInstance();
         $taController->update($customer1Timeaccount);
         
-        // this is a ts on 20xx-01-17
+        // this is a ts on 20xx-01-18
         $timesheet = new Timetracker_Model_Timesheet(array(
             'account_id' => Tinebase_Core::getUser()->getId(),
             'timeaccount_id' => $customer1Timeaccount->getId(),
diff --git a/tests/tine20/Sales/ProductControllerTest.php b/tests/tine20/Sales/ProductControllerTest.php
new file mode 100644 (file)
index 0000000..8ffb445
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Sales
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * 
+ */
+
+/**
+ * Test class for Sales_Controller_Product
+ */
+class Sales_ProductControllerTest extends TestCase
+{
+    /**
+     * lazy init of uit
+     *
+     * @return Sales_Controller_Product
+     */
+    protected function _getUit()
+    {
+        if ($this->_uit === null) {
+            $this->_uit = Sales_Controller_Product::getInstance();
+        }
+        
+        return $this->_uit;
+    }
+    
+    /**
+     * 
+     * @return Sales_Model_Contract
+     */
+    public function testCreateProduct()
+    {
+        $product = $this->_getUit()->create(new Sales_Model_Product(array(
+            'name' => 'A new product'
+        )));
+        
+        $this->assertNotEmpty($product->number);
+        
+        return $product;
+    }
+    
+    /**
+     * testUpdateProductLifespan
+     * 
+     * @see 0010766: set product lifespan
+     */
+    public function testUpdateProductLifespan()
+    {
+        $product1 = $this->_getUit()->create(new Sales_Model_Product(array(
+            'name' => 'product activates in future',
+            'lifespan_start' => Tinebase_DateTime::now()->addDay(1)
+        )));
+        $product2 = $this->_getUit()->create(new Sales_Model_Product(array(
+            'name' => 'product lifespan ended',
+            'lifespan_end' => Tinebase_DateTime::now()->subDay(1)
+        )));
+        $product3 = $this->_getUit()->create(new Sales_Model_Product(array(
+            'is_active' => 0,
+            'name' => 'product lifespan started',
+            'lifespan_start' => Tinebase_DateTime::now()->subDay(1)
+        )));
+        $product4 = $this->_getUit()->create(new Sales_Model_Product(array(
+            'is_active' => 0,
+            'name' => 'product lifespan not yet ended',
+            'lifespan_end' => Tinebase_DateTime::now()->addDay(1)
+        )));
+        
+        $productsToTest = array(
+            array('expectedIsActive' => 0, 'product' => $product1),
+            array('expectedIsActive' => 0, 'product' => $product2),
+            array('expectedIsActive' => 1, 'product' => $product3),
+            array('expectedIsActive' => 1, 'product' => $product4),
+        );
+        
+        $this->_getUit()->updateProductLifespan();
+        
+        foreach ($productsToTest as $product) {
+            $updatedProduct = $this->_getUit()->get($product['product']);
+            $this->assertEquals($product['expectedIsActive'], $updatedProduct->is_active, print_r($product['product']->toArray(), true));
+        }
+    }
+}
diff --git a/tests/tine20/Sales/PurchaseInvoiceTest.php b/tests/tine20/Sales/PurchaseInvoiceTest.php
new file mode 100644 (file)
index 0000000..826f5da
--- /dev/null
@@ -0,0 +1,202 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ *
+ * @package     Sales
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2008-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ */
+
+/**
+ * Test class for Sales_PurchaseInvoice
+ */
+class Sales_PurchaseInvoiceTest extends TestCase
+{
+    /**
+     *
+     * @var Sales_Frontend_Json
+     */
+    protected $_json;
+    
+    /**
+     * get paging
+     *
+     * @return array
+     */
+    protected function _getPaging()
+    {
+        return array(
+                'start' => 0,
+                'limit' => 50,
+                'sort' => 'number',
+                'dir' => 'ASC',
+        );
+    }
+    
+    /**
+     * get filter
+     *
+     * @return array
+     */
+    protected function _getFilter()
+    {
+        return array(
+                array('field' => 'query', 'operator' => 'contains', 'value' => '12345'),
+        );
+    }
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+    
+        $this->_contactController  = Addressbook_Controller_Contact::getInstance();
+        $this->_json               = new Sales_Frontend_Json();
+    }
+    
+    /**
+     *
+     * @return array
+     */
+    protected function _createPurchaseInvoice()
+    {
+        $container = Tinebase_Container::getInstance()->getSharedContainer(
+                Tinebase_Core::getUser()->getId(),
+                'Addressbook_Model_Contact',
+                'WRITE'
+        );
+    
+        $containerContracts = Tinebase_Container::getInstance()->getSharedContainer(
+                Tinebase_Core::getUser()->getId(),
+                'Sales_Model_Contract',
+                'WRITE'
+        );
+    
+        $container = $container->getFirstRecord();
+    
+        $contact1 = $this->_contactController->create(new Addressbook_Model_Contact(
+                array('n_given' => 'Yiting', 'n_family' => 'Huang', 'container_id' => $container->getId()))
+        );
+        $contact2 = $this->_contactController->create(new Addressbook_Model_Contact(
+                array('n_given' => 'Hans Friedrich', 'n_family' => 'Ochs', 'container_id' => $container->getId()))
+        );
+    
+        $customerData = array(
+                'name' => 'Worldwide Electronics International',
+                'cpextern_id' => $contact1->getId(),
+                'cpintern_id' => $contact2->getId(),
+                'number'      => 54321,
+    
+                'iban'        => 'CN09234098324098234598',
+                'bic'         => '0239580429570923432444',
+                'url'         => 'http://wwei.cn',
+                'vatid'       => '239rc9mwqe9c2q',
+                'credit_term' => '30',
+                'currency'    => 'EUR',
+                'curreny_trans_rate' => 7.034,
+                'discount'    => 12.5,
+    
+                'adr_prefix1' => 'no prefix 1',
+                'adr_prefix2' => 'no prefix 2',
+                'adr_street' => 'Mao st. 2000',
+                'adr_postalcode' => '1',
+                'adr_locality' => 'Shanghai',
+                'adr_region' => 'Shanghai',
+                'adr_countryname' => 'China',
+                'adr_pobox'   => '7777777'
+        );
+        
+        $purchaseData = array(
+                'number' => 'R-12345',
+                'description' => 'test',
+                'discount' => 0,
+                'due_in' => 10,
+                'date' => '2015-03-17 00:00:00',
+                'due_at' => '2015-03-27 00:00:00',
+                'price_net' => 10,
+                'sales_tax' => 19,
+                'price_tax' => 1.9,
+                'price_gross' => 11.9,
+                'price_gross2' => 1,
+                'price_total' => 12.9,
+                'relations' => array(array(
+                        'own_model' => 'Sales_Model_PurchaseInvoice',
+                        'own_degree' => Tinebase_Model_Relation::DEGREE_SIBLING,
+                        'related_model' => 'Sales_Model_Supplier',
+                        'related_record' => $customerData,
+                        'type' => 'SUPPLIER'
+                )
+            )
+        );
+        
+        return $this->_json->savePurchaseInvoice($purchaseData);
+    }
+    
+    /**
+     * try to save a PurchaseInvoice
+     */
+    public function testSavePurchaseInvoice()
+    {
+        $purchase = $this->_createPurchaseInvoice();
+        $this->assertEquals('R-12345', $purchase['number']);
+        $this->assertEquals('Worldwide Electronics International', $purchase['supplier']['name']);
+        $this->assertEquals('2015-03-17 00:00:00', $purchase['date']);
+        $this->assertEquals('2015-03-27 00:00:00', $purchase['due_at']);
+
+        $this->assertEquals(10, $purchase['price_net']);
+        $this->assertEquals(19, $purchase['sales_tax']);
+        $this->assertEquals(1.9, $purchase['price_tax']);
+        $this->assertEquals(11.9, $purchase['price_gross']);
+        $this->assertEquals(1, $purchase['price_gross2']);
+        $this->assertEquals(12.9, $purchase['price_total']);
+    }
+    
+    /**
+     * try to update a PurchaseInvoice
+     */
+    public function testUpdatePurchaseInvoice()
+    {
+        $purchase = $this->_createPurchaseInvoice();
+        $this->assertEquals('2015-03-27 00:00:00', $purchase['due_at']);
+        $purchase['due_at'] = '2015-04-07 00:00:00';
+        $updatedPurchase = $this->_json->savePurchaseInvoice($purchase);
+        $this->assertEquals('2015-04-07 00:00:00', $updatedPurchase['due_at']);
+    }
+
+    /**
+     * try to get a PurchaseInvoice
+     */
+    public function testSearchPurchaseInvoice()
+    {
+        $purchase = $this->_createPurchaseInvoice();
+        
+        // search & check
+        $search = $this->_json->searchPurchaseInvoices($this->_getFilter(), $this->_getPaging());
+        $this->assertEquals($purchase['number'], $search['results'][0]['number']);
+        $this->assertEquals(1, $search['totalcount']);
+    }
+    
+    /**
+     * try to delete a PurchaseInvoice
+     */
+    public function testDeletePurchaseInvoice()
+    {
+        $purchase = $this->_createPurchaseInvoice();
+        $this->assertEquals('R-12345', $purchase['number']);
+        
+        // delete record
+        $this->_json->deletePurchaseInvoices($purchase['id']);
+        
+        $this->setExpectedException('Tinebase_Exception_NotFound');
+        $customerBackend = new Sales_Backend_PurchaseInvoice();
+        $deletedPurchase = $customerBackend->get($purchase['id'], TRUE);
+        $this->assertEquals(1, $deletedPurchase->is_deleted);
+        
+    }
+
+}
diff --git a/tests/tine20/Sales/SuppliersTest.php b/tests/tine20/Sales/SuppliersTest.php
new file mode 100644 (file)
index 0000000..f84fca0
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Sales
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2015-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ */
+
+/**
+ * Test class for Sales_SuppliersTest
+ */
+class Sales_SuppliersTest extends TestCase
+{
+    /**
+     * 
+     * @var Addressbook_Controller_Contact
+     */
+    protected $_contactController;
+    
+    /**
+     * 
+     * @var Sales_Frontend_Json
+     */
+    protected $_json;
+    
+    /**
+     * Sets up the fixture.
+     * This method is called before a test is executed.
+     *
+     * @access protected
+     */
+    protected function setUp()
+    {
+        parent::setUp();
+        
+        $this->_contactController  = Addressbook_Controller_Contact::getInstance();
+        $this->_json               = new Sales_Frontend_Json();
+    }
+
+    /**
+     * 
+     * @return array
+     */
+    protected function _createSupplier()
+    {
+        $container = Tinebase_Container::getInstance()->getSharedContainer(
+            Tinebase_Core::getUser()->getId(),
+            'Addressbook_Model_Contact',
+            'WRITE'
+        );
+        
+        $containerContracts = Tinebase_Container::getInstance()->getSharedContainer(
+            Tinebase_Core::getUser()->getId(),
+            'Sales_Model_Contract',
+            'WRITE'
+        );
+        
+        $container = $container->getFirstRecord();
+        
+        $contact1 = $this->_contactController->create(new Addressbook_Model_Contact(
+            array('n_given' => 'Yiting', 'n_family' => 'Huang', 'container_id' => $container->getId()))
+        );
+        $contact2 = $this->_contactController->create(new Addressbook_Model_Contact(
+            array('n_given' => 'Hans Friedrich', 'n_family' => 'Ochs', 'container_id' => $container->getId()))
+        );
+        
+        $customerData = array(
+            'name' => 'Worldwide Electronics International',
+            'cpextern_id' => $contact1->getId(),
+            'cpintern_id' => $contact2->getId(),
+            'number'      => 4294967,
+        
+            'iban'        => 'CN09234098324098234598',
+            'bic'         => '0239580429570923432444',
+            'url'         => 'http://wwei.cn',
+            'vatid'       => '239rc9mwqe9c2q',
+            'credit_term' => '30',
+            'currency'    => 'EUR',
+            'curreny_trans_rate' => 7.034,
+            'discount'    => 12.5,
+        
+            'adr_prefix1' => 'no prefix 1',
+            'adr_prefix2' => 'no prefix 2',
+            'adr_street' => 'Mao st. 2000',
+            'adr_postalcode' => '1',
+            'adr_locality' => 'Shanghai',
+            'adr_region' => 'Shanghai',
+            'adr_countryname' => 'China',
+            'adr_pobox'   => '7777777'
+        );
+        
+        return $this->_json->saveSupplier($customerData);
+    }
+    
+    public function testLifecycleSupplier()
+    {
+        $retVal = $this->_createSupplier();
+        
+        $this->assertEquals(4294967, $retVal["number"]);
+        $this->assertEquals("Worldwide Electronics International", $retVal["name"]);
+        $this->assertEquals("http://wwei.cn", $retVal["url"]);
+        $this->assertEquals(NULL, $retVal['description']);
+        
+        $this->assertEquals('Yiting', $retVal['cpextern_id']['n_given']);
+        $this->assertEquals('Huang',  $retVal['cpextern_id']['n_family']);
+        
+        $this->assertEquals('Hans Friedrich', $retVal['cpintern_id']['n_given']);
+        $this->assertEquals('Ochs', $retVal['cpintern_id']['n_family']);
+
+        // delete record (set deleted=1) of customer and assigned addresses
+        $this->_json->deleteSuppliers(array($retVal['id']));
+        
+        $customerBackend = new Sales_Backend_Supplier();
+        $deletedSupplier = $customerBackend->get($retVal['id'], TRUE);
+        $this->assertEquals(1, $deletedSupplier->is_deleted);
+        
+        $addressBackend = new Sales_Backend_Address();
+        $deletedAddresses = $addressBackend->getMultipleByProperty($retVal['id'], 'customer_id', TRUE);
+
+        $this->assertEquals(1, $deletedAddresses->count());
+        
+        foreach($deletedAddresses as $address) {
+            $this->assertEquals(1, $address->is_deleted);
+        }
+        $this->setExpectedException('Tinebase_Exception_NotFound');
+        
+        return $this->_json->getSupplier($retVal['id']);
+    }
+    
+    /**
+     * checks if the number is always set to the correct value
+     */
+    public function testNumberable()
+    {
+        $controller = Sales_Controller_Supplier::getInstance();
+        
+        $record = $controller->create(new Sales_Model_Supplier(array('name' => 'auto1')));
+        
+        $this->assertGreaterThan(0, $record->number);
+        $initialNumber = $record->number;
+        
+        $record = $controller->create(new Sales_Model_Supplier(array('name' => 'auto2')));
+        
+        $this->assertEquals($initialNumber + 1, $record->number);
+        
+        // set number to $initialNumber + 3, should return the formatted number
+        $record = $controller->create(new Sales_Model_Supplier(array('name' => 'manu1', 'number' => $initialNumber + 3)));
+        $this->assertEquals($initialNumber + 3, $record->number);
+        
+        // the next number should be a number after the manual number
+        $record = $controller->create(new Sales_Model_Supplier(array('name' => 'auto3')));
+        $this->assertEquals($initialNumber + 4, $record->number);
+    }
+}
index 9f7f1f2..4fafda6 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Tests
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2013-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2013-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Philipp Schüle <p.schuele@metaways.de>
  */
 
@@ -14,7 +14,7 @@
 require_once __DIR__ . DIRECTORY_SEPARATOR . 'TestHelper.php';
 
 /**
- * Test class for Calendar_Backend_Sql
+ * Abstract test class
  * 
  * @package     Tests
  */
@@ -30,7 +30,7 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
     /**
      * usernames to be deleted (in sync backend)
      * 
-     * @var string
+     * @var array
      */
     protected $_usernamesToDelete = array();
     
@@ -49,6 +49,13 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
     protected $_removeGroupMembers = true;
     
     /**
+     * invalidate roles cache
+     * 
+     * @var boolean
+     */
+    protected $_invalidateRolesCache = false;
+    
+    /**
      * test personas
      * 
      * @var array
@@ -72,10 +79,17 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
     /**
      * the mailer
      * 
-     * @var Zend_Mail_Transport_Array
+     * @var Zend_Mail_Transport_Abstract
      */
     protected static $_mailer = null;
-    
+
+    /**
+     * db lock ids to be released
+     *
+     * @var array
+     */
+    protected $_releaseDBLockIds = array();
+
     /**
      * set up tests
      */
@@ -111,9 +125,24 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
             Tinebase_Core::set(Tinebase_Core::USER, $this->_originalTestUser);
         }
         
+        if ($this->_invalidateRolesCache) {
+            Tinebase_Acl_Roles::getInstance()->resetClassCache();
+        }
         Tinebase_Cache_PerRequest::getInstance()->resetCache();
 
+        $this->_releaseDBLocks();
+    }
+
+    /**
+     * release db locks
+     */
+    protected function _releaseDBLocks()
+    {
+        foreach ($this->_releaseDBLockIds as $lockId) {
+            Tinebase_Lock::releaseDBSessionLock($lockId);
+        }
 
+        $this->_releaseDBLockIds = array();
     }
 
     /**
@@ -229,27 +258,35 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
             $user = Tinebase_Core::getUser();
         }
         
-        return Tinebase_Container::getInstance()->getPersonalContainer(
+        $personalContainer = Tinebase_Container::getInstance()->getPersonalContainer(
             $user,
             $applicationName, 
             $user,
             Tinebase_Model_Grants::GRANT_EDIT
         )->getFirstRecord();
+
+        if (! $personalContainer) {
+            throw new Tinebase_Exception_UnexpectedValue('no personal container found!');
+        }
+
+        return $personalContainer;
     }
     
     /**
      * get test container
      * 
      * @param string $applicationName
+     * @param string $model
      */
-    protected function _getTestContainer($applicationName)
+    protected function _getTestContainer($applicationName, $model = null)
     {
         return Tinebase_Container::getInstance()->addContainer(new Tinebase_Model_Container(array(
-            'name'           => 'PHPUnit test container',
+            'name'           => 'PHPUnit ' . $model .' container',
             'type'           => Tinebase_Model_Container::TYPE_PERSONAL,
             'owner_id'       => Tinebase_Core::getUser(),
             'backend'        => 'Sql',
-            'application_id' => Tinebase_Application::getInstance()->getApplicationByName($applicationName)->getId()
+            'application_id' => Tinebase_Application::getInstance()->getApplicationByName($applicationName)->getId(),
+            'model'          => $model,
         ), true));
     }
     
@@ -390,13 +427,43 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
     }
     
     /**
-     * set grants for a persona
+     * remove right in all users roles
+     * 
+     * @param string $applicationName
+     * @param string $right
+     * @param boolean $removeAdminRight
+     * @return array original role rights by role id
+     */
+    protected function _removeRoleRight($applicationName, $rightToRemove, $removeAdminRight = true)
+    {
+        $app = Tinebase_Application::getInstance()->getApplicationByName($applicationName);
+        $rolesOfUser = Tinebase_Acl_Roles::getInstance()->getRoleMemberships(Tinebase_Core::getUser()->getId());
+        $this->_invalidateRolesCache = true;
+
+        $roleRights = array();
+        foreach ($rolesOfUser as $roleId) {
+            $roleRights[$roleId] = $rights = Tinebase_Acl_Roles::getInstance()->getRoleRights($roleId);
+            foreach ($rights as $idx => $right) {
+                if ($right['application_id'] === $app->getId() && ($right['right'] === $rightToRemove || $right['right'] === Tinebase_Acl_Rights_Abstract::ADMIN)) {
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
+                        . ' Removing right ' . $right['right'] . ' from app ' . $applicationName . ' in role (id) ' . $roleId);
+                    unset($rights[$idx]);
+                }
+            }
+            Tinebase_Acl_Roles::getInstance()->setRoleRights($roleId, $rights);
+        }
+        
+        return $roleRights;
+    }
+    
+    /**
+     * set grants for a persona and the current user
      * 
      * @param integer $containerId
      * @param string $persona
      * @param string $adminGrant
      */
-    protected function _setPersonaGrantsForTestContainer($containerId, $persona, $adminGrant = false)
+    protected function _setPersonaGrantsForTestContainer($containerId, $persona, $personaAdminGrant = false, $userAdminGrant = true)
     {
         $grants = new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array(array(
             'account_id'    => $this->_personas[$persona]->getId(),
@@ -405,7 +472,7 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
             Tinebase_Model_Grants::GRANT_ADD      => true,
             Tinebase_Model_Grants::GRANT_EDIT     => true,
             Tinebase_Model_Grants::GRANT_DELETE   => true,
-            Tinebase_Model_Grants::GRANT_ADMIN    => $adminGrant,
+            Tinebase_Model_Grants::GRANT_ADMIN    => $personaAdminGrant,
         ), array(
             'account_id'    => Tinebase_Core::getUser()->getId(),
             'account_type'  => 'user',
@@ -413,7 +480,7 @@ abstract class TestCase extends PHPUnit_Framework_TestCase
             Tinebase_Model_Grants::GRANT_ADD      => true,
             Tinebase_Model_Grants::GRANT_EDIT     => true,
             Tinebase_Model_Grants::GRANT_DELETE   => true,
-            Tinebase_Model_Grants::GRANT_ADMIN    => true,
+            Tinebase_Model_Grants::GRANT_ADMIN    => $userAdminGrant,
         )));
         
         Tinebase_Container::getInstance()->setGrants($containerId, $grants, TRUE);
index 609ed69..d4cfbfc 100644 (file)
@@ -1255,4 +1255,30 @@ class Timetracker_JsonTest extends Timetracker_AbstractTest
         $tsData = $this->_json->getTimesheet($tsData['id']);
         $this->assertSame(NULL,  $tsData['invoice_id']);
     }
+    
+    /**
+     * try to update a Timesheet with a closed TimeAccount
+     *
+     */
+    public function testUpdateClosedTimeaccount()
+    {
+        $timeaccountData = $this->_saveTimeaccountWithGrants();
+        $timeaccountData['is_open'] = 0;
+        $timeaccount = $this->_json->saveTimeaccount($timeaccountData);
+        
+        $timesheet = $this->_getTimesheet(array(
+             'timeaccount_id'    => $timeaccount['id'],
+        ));
+        $timesheetData = $this->_json->saveTimesheet($timesheet->toArray());
+        
+        Timetracker_ControllerTest::removeManageAllRight();
+        
+        $this->setExpectedException('Tinebase_Exception_AccessDenied');
+        
+        // update Timesheet
+        $timesheetData['description'] = "blubbblubb";
+        $timesheetData['account_id'] = $timesheetData['account_id']['accountId'];
+        $timesheetData['timeaccount_id'] = $timesheetData['timeaccount_id']['id'];
+        $timesheetUpdated = $this->_json->saveTimesheet($timesheetData);
+    }
 }
index c1f0f3a..838d522 100644 (file)
@@ -68,7 +68,9 @@ class Tinebase_AllTests
         $suite->addTestSuite('Tinebase_Redis_QueueTest');
         $suite->addTestSuite('Tinebase_Pluggable_ConcreteTest');
         $suite->addTestSuite('Tinebase_TempFileTest');
-        
+        $suite->addTestSuite('Tinebase_LockTest');
+        $suite->addTestSuite('Tinebase_ScheduledImportTest');
+
         $suite->addTest(Tinebase_User_AllTests::suite());
         $suite->addTest(Tinebase_Group_AllTests::suite());
         $suite->addTest(Tinebase_Timemachine_AllTests::suite());
index ca6784c..925be4b 100644 (file)
@@ -237,4 +237,14 @@ class Tinebase_ConfigTest extends PHPUnit_Framework_TestCase
         $cachedConfigFilename = Tinebase_Core::guessTempDir() . DIRECTORY_SEPARATOR . 'cachedConfig.inc.php';
         $this->assertTrue(file_exists($cachedConfigFilename), 'cached config file does not exist: ' . $cachedConfigFilename);
     }
+
+    /**
+     * @see 0011456: unable to add new activesync-devices in tine20
+     */
+    public function testDefaultNull()
+    {
+        // TODO maybe we need to remove the current config if is set
+        $defaultPolicy = ActiveSync_Config::getInstance()->get(ActiveSync_Config::DEFAULT_POLICY, null);
+        $this->assertTrue(is_null($defaultPolicy), 'config should be null: ' . var_export($defaultPolicy, true));
+    }
 }
index 9507d52..777d951 100644 (file)
@@ -521,6 +521,8 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         Tinebase_User::getInstance()->setStatus($user2, 'enabled');
         
         $container = Tinebase_Container::getInstance()->getPersonalContainer($user1, 'Calendar', $user1, Tinebase_Model_Grants::GRANT_READ);
+
+        $this->assertGreaterThan(0, count($container));
         
         $oldGrants = Tinebase_Container::getInstance()->getGrantsOfContainer($container->getFirstRecord()->id, TRUE);
         
@@ -664,7 +666,7 @@ class Tinebase_ContainerTest extends PHPUnit_Framework_TestCase
         Tinebase_Config::getInstance()->set(Tinebase_Config::ANYONE_ACCOUNT_DISABLED, TRUE);
         Tinebase_Core::getCache()->clean();
         $readGrant = $this->_instance->resetClassCache('hasGrant')->hasGrant(Tinebase_Core::getUser(), $sharedContainer, Tinebase_Model_Grants::GRANT_READ);
-        $this->assertFalse($readGrant);
+        $this->assertFalse($readGrant, 'user should not have read grant');
     }
     
     /**
index b0e18b1..eba02bd 100644 (file)
@@ -35,18 +35,6 @@ class Tinebase_Frontend_CliTest extends TestCase
     protected $_userPlugins = array();
     
     /**
-     * Runs the test methods of this class.
-     *
-     * @access public
-     * @static
-     */
-    public static function main()
-    {
-        $suite  = new PHPUnit_Framework_TestSuite('Tine 2.0 Tinebase Cli Tests');
-        PHPUnit_TextUI_TestRunner::run($suite);
-    }
-
-    /**
      * Sets up the fixture.
      * This method is called before a test is executed.
      *
@@ -140,20 +128,27 @@ class Tinebase_Frontend_CliTest extends TestCase
 
     /**
      * test purge deleted records
+     *
+     * @see 0010249: Tinebase.purgeDeletedRecords fails
      */
     public function testPurgeDeletedRecordsAllTables()
     {
         $opts = $this->_getOpts();
         $deletedContact = $this->_addAndDeleteContact();
         $deletedLead = $this->_addAndDeleteLead();
+
+        // delete personal adb container and tag, too
+        Tinebase_Container::getInstance()->deleteContainer($this->_getPersonalContainer('Addressbook')->getId());
+        Tinebase_Tags::getInstance()->deleteTags($deletedContact->tags->getFirstRecord()->getId());
         
         ob_start();
         $this->_cli->purgeDeletedRecords($opts);
         $out = ob_get_clean();
-        
+
         $this->assertContains('Removing all deleted entries before', $out);
         $this->assertContains('Cleared table addressbook (deleted ', $out);
         $this->assertContains('Cleared table metacrm_lead (deleted ', $out);
+        $this->assertNotContains('Failed to purge', $out);
 
         $contactBackend = Addressbook_Backend_Factory::factory(Addressbook_Backend_Factory::SQL);
         $contacts = $contactBackend->getMultipleByProperty($deletedContact->getId(), 'id', TRUE);
@@ -173,12 +168,13 @@ class Tinebase_Frontend_CliTest extends TestCase
     {
         $newContact = new Addressbook_Model_Contact(array(
             'n_family'          => 'PHPUNIT',
-            'container_id'      => Addressbook_Controller_Contact::getInstance()->getDefaultAddressbook()->getId(),
+            'container_id'      => $this->_getPersonalContainer('Addressbook')->getId(),
             'tel_cell_private'  => '+49TELCELLPRIVATE',
+            'tags'              => array(array('name' => 'temptag')),
         ));
         $newContact = Addressbook_Controller_Contact::getInstance()->create($newContact);
         Addressbook_Controller_Contact::getInstance()->delete($newContact->getId());
-        
+
         return $newContact;
     }
 
@@ -211,7 +207,8 @@ class Tinebase_Frontend_CliTest extends TestCase
         $opts = new Zend_Console_Getopt('abp:');
         $opts->setArguments(array());
         $this->_usernamesToDelete[] = 'cronuser';
-        
+        $this->_releaseDBLockIds[] = 'Tinebase_Frontend_Cli::triggerAsyncEvents';
+
         ob_start();
         $this->_cli->triggerAsyncEvents($opts);
         $out = ob_get_clean();
@@ -220,6 +217,7 @@ class Tinebase_Frontend_CliTest extends TestCase
         $this->assertEquals(0, count($userPlugins));
         
         $cronuserId = Tinebase_Config::getInstance()->get(Tinebase_Config::CRONUSERID);
+        $this->assertTrue(! empty($cronuserId), 'got empty cronuser id');
         $cronuser = Tinebase_User::getInstance()->getFullUserById($cronuserId);
         $this->assertEquals('cronuser', $cronuser->accountLoginName);
         $adminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
index 02f1743..fb5c32b 100644 (file)
@@ -27,7 +27,7 @@ class Tinebase_Frontend_HttpTest extends PHPUnit_Framework_TestCase
     {
         $this->_uit = new Tinebase_Frontend_Http;
     }
-    
+
     public function testMainScreen()
     {
         if (version_compare(PHPUnit_Runner_Version::id(), '3.3.0', '<')) {
index 773ada9..59e3988 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Json
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
 
@@ -230,7 +230,18 @@ class Tinebase_Frontend_JsonTest extends TestCase
         $noteTypes = $this->_instance->getNoteTypes();
         $this->assertTrue($noteTypes['totalcount'] >= 5);
     }
-    
+
+    /**
+     * toogle advanced search preference
+     */
+    public function testAdvancedSearchToogle()
+    {
+        $toogle = $this->_instance->toogleAdvancedSearch(1);
+
+        $this->assertEquals($toogle, 1);
+        $this->assertEquals(Tinebase_Core::getPreference()->getValue(Tinebase_Preference::ADVANCED_SEARCH, 0), 1);
+    }
+
     /**
      * search preferences by application
      *
@@ -652,6 +663,27 @@ class Tinebase_Frontend_JsonTest extends TestCase
         Tinebase_Controller::getInstance()->initUser($this->_originalTestUser, /* $fixCookieHeader = */ false);
     }
     
+    /**
+     * testOmitPersonalTagsOnSearch
+     * 
+     * @see 0010732: add "use personal tags" right to all applications
+     */
+    public function testOmitPersonalTagsOnSearch()
+    {
+        $personalTag = $this->_getTag(Tinebase_Model_Tag::TYPE_PERSONAL);
+        Tinebase_Tags::getInstance()->createTag($personalTag);
+        
+        $this->_removeRoleRight('Addressbook', Tinebase_Acl_Rights::USE_PERSONAL_TAGS);
+        $filter = array(
+            'application' => 'Addressbook',
+            'grant' => Tinebase_Model_TagRight::VIEW_RIGHT,
+            'type' => Tinebase_Model_Tag::TYPE_PERSONAL
+        );
+        $result = $this->_instance->searchTags($filter, array());
+        
+        $this->assertEquals(0, $result['totalCount']);
+    }
+    
     /******************** protected helper funcs ************************/
     
     /**
diff --git a/tests/tine20/Tinebase/LockTest.php b/tests/tine20/Tinebase/LockTest.php
new file mode 100644 (file)
index 0000000..f330614
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Tine 2.0 - http://www.tine20.org
+ * 
+ * @package     Tinebase
+ * @license     http://www.gnu.org/licenses/agpl.html
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ */
+
+/**
+ * Test class for Tinebase_Lock
+ */
+class Tinebase_LockTest extends TestCase
+{
+    protected $_testLockId = 'testlockId';
+    
+    /**
+     * tear down tests
+     */
+    protected function tearDown()
+    {
+        parent::tearDown();
+
+        Tinebase_Lock::releaseDBSessionLock($this->_testLockId);
+    }
+
+        /**
+     * Test create a lock
+     */
+    public function testLock()
+    {
+        $aquireLock1 = Tinebase_Lock::aquireDBSessionLock($this->_testLockId);
+
+        $this->assertTrue($aquireLock1, 'lock should be available');
+
+        $aquireLock2 = Tinebase_Lock::aquireDBSessionLock($this->_testLockId);
+
+        $this->assertFalse($aquireLock2, 'lock should not be available');
+    }
+
+    /**
+     * test lock release
+     */
+    public function testReleaseLock()
+    {
+        $this->testLock();
+
+        Tinebase_Lock::releaseDBSessionLock($this->_testLockId);
+
+        $aquireLock = Tinebase_Lock::aquireDBSessionLock($this->_testLockId);
+
+        $this->assertTrue($aquireLock, 'lock should be available again');
+    }
+}
index 5ee9785..d32c6eb 100644 (file)
@@ -259,7 +259,37 @@ class Tinebase_PreferenceTest extends PHPUnit_Framework_TestCase
         $this->assertEquals(Tinebase_Model_Preference::TYPE_FORCED, $pref->type);
         $this->assertEquals(0, $pref->value);
     }
-    
+
+    /**
+     * testSetLockedPref
+     *
+     * @see 0011178: allow to lock preferences for individual users
+     */
+    public function testSetLockedPref()
+    {
+        $tzPref = $this->_instance->getApplicationPreferenceDefaults(Tinebase_Preference::TIMEZONE);
+        $tzPref->type = Tinebase_Model_Preference::TYPE_USER;
+        $tzPref->locked = true;
+        $tzPref->id = null;
+        $tzPref->account_id = Tinebase_Core::getUser()->getId();
+        $tzPref->account_type = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
+        $tzPref = $this->_instance->create($tzPref);
+
+        $result = $this->_instance->search();
+        $pref = $result->filter('name', Tinebase_Preference::TIMEZONE)->getFirstRecord();
+
+        $this->assertTrue($pref !== null);
+        $this->assertEquals(Tinebase_Model_Preference::TYPE_USER, $pref->type);
+        $this->assertEquals(true, $pref->locked);
+
+        try {
+            $this->_instance->setValue(Tinebase_Preference::TIMEZONE, 'Europe/Berlin');
+            $this->fail('it is not allowed to set locked preference: ' . print_r($tzPref->toArray(), true));
+        } catch (Tinebase_Exception_AccessDenied $tead) {
+            $this->assertEquals('You are not allowed to change the locked preference.', $tead->getMessage());
+        }
+    }
+
     /******************** protected helper funcs ************************/
     
     /**
index d2ef323..97e985a 100644 (file)
@@ -4,7 +4,7 @@
  * 
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html
- * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2014-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  * @author      Michael Spahn <m.spahn@metaways.de>
  */
 
@@ -38,7 +38,7 @@ class Tinebase_ScheduledImportTest extends TestCase
     /**
      * Test create a scheduled import
      */
-    public function createScheduledImport()
+    public function createScheduledImport($source = 'http://localhost/test.ics')
     {
         $id = Tinebase_Record_Abstract::generateUID();
         $import = new Tinebase_Model_Import(
@@ -50,7 +50,7 @@ class Tinebase_ScheduledImportTest extends TestCase
                 'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId(),
                 'container_id'      => $this->_testCalendar->getId(),
                 'sourcetype'        => Tinebase_Model_Import::SOURCETYPE_REMOTE,
-                'source'            => 'http://www.schulferien.org/iCal/Ferien/icals/Ferien_Hamburg_2014.ics',
+                'source'            => $source,
                 'options'           => json_encode(array(
                     'forceUpdateExisting' => TRUE,
                     'import_defintion' => NULL,
@@ -62,15 +62,28 @@ class Tinebase_ScheduledImportTest extends TestCase
         $record = $this->_uit->create($import);
 
         $this->assertEquals(Calendar_Controller::getInstance()->getDefaultModel(), $this->_uit->get($id)->model);
-        
+
         return $record;
     }
     
     /**
      * testNextScheduledImport
+     *
+     * TODO this should run without the need for internet access to http://www.schulferien.org
+     *      maybe we should put the file into the local filesystem
      */
     public function testNextScheduledImport()
     {
+        $this->markTestSkipped('FIXME: use local ics file for this test / see TODO in doc block');
+
+        $icsUri = 'http://www.schulferien.org/iCal/Ferien/icals/Ferien_Hamburg_2014.ics';
+        $client = new Zend_Http_Client($icsUri);
+        try {
+            $client->request()->getBody();
+        } catch (Exception $e) {
+            $this->markTestSkipped('no access to ' . $icsUri);
+        }
+
         $cc = Calendar_Controller_Event::getInstance();
         $filter = new Calendar_Model_EventFilter(array(
             array('field' => 'container_id', 'operator' => 'equals', 'value' => $this->_testCalendar->getId()),
@@ -82,12 +95,14 @@ class Tinebase_ScheduledImportTest extends TestCase
         
         $now = Tinebase_DateTime::now()->subHour(1);
         
-        $record = $this->createScheduledImport();
+        $record = $this->createScheduledImport($icsUri);
         
         // assert setting timestamp to start value
         $this->assertEquals($now->format('YMDHi'), $record->timestamp->format('YMDHi'));
         
         $record = $this->_uit->runNextScheduledImport();
+
+        $this->assertTrue($record !== null, 'import did not run');
         
         // assert updating timestamp after successful run
         $now->addHour(1);
@@ -99,7 +114,7 @@ class Tinebase_ScheduledImportTest extends TestCase
         $this->assertEquals(7, $all->count());
         
         // this must not be run, the interval is not exceed
-        $ret = $this->_uit->runNextScheduledImport();
+        $this->_uit->runNextScheduledImport();
         $all = $cc->search($filter);
         $this->assertEquals($seq, $all->getFirstRecord()->seq);
         
@@ -108,8 +123,42 @@ class Tinebase_ScheduledImportTest extends TestCase
         
         $this->_uit->update($record);
         
-        $ret = $this->_uit->runNextScheduledImport();
+        $this->_uit->runNextScheduledImport();
         $all = $cc->search($filter);
         $this->assertEquals(7, $all->count());
     }
+
+    /**
+     * @see 0011342: ics-scheduled import only imports 1 remote calendar
+     */
+    public function testMultipleScheduledImports()
+    {
+        // add two imports and check if they are both executed
+        $import1 = $this->createScheduledImport();
+        sleep(1); // make sure first one is found first
+        $import2 = $this->createScheduledImport();
+
+        $importRun1 = $this->_uit->runNextScheduledImport();
+        $this->assertEquals($import1->getId(), $importRun1['id'], print_r($importRun1, true));
+        $this->assertGreaterThanOrEqual($importRun1['timestamp'], Tinebase_DateTime::now()->toString());
+
+        $importRun2 = $this->_uit->runNextScheduledImport();
+        $this->assertEquals($import2->getId(), $importRun2['id'], 'second import not run: ' . print_r($importRun1, true));
+        $this->assertGreaterThanOrEqual($importRun2['timestamp'], Tinebase_DateTime::now()->toString());
+    }
+
+    /**
+     * @see 0011342: ics-scheduled import only imports 1 remote calendar
+     */
+    public function testNextScheduledImportFilter()
+    {
+        $record = $this->createScheduledImport();
+        $record->timestamp = $record->timestamp->addHour(2);
+        $this->_uit->update($record);
+
+        $filter = $this->_uit->getScheduledImportFilter();
+        $result = $this->_uit->search($filter);
+
+        $this->assertEquals(0, count($result), 'no imports should be found: ' . print_r($result->toArray(), true));
+    }
 }
index a5f865c..9a3f770 100644 (file)
@@ -31,29 +31,20 @@ class Tinebase_User_ActiveDirectoryTest extends PHPUnit_Framework_TestCase
     protected $userBaseFilter  = 'objectclass=user';
     
     /**
-     * Sets up the fixture.
-     * This method is called before a test is executed.
+     * try to add a group
      *
-     * @access protected
      */
-    protected function setUp()
+    public function testAddUserToSyncBackend()
     {
         $this->markTestIncomplete('group backend breaks mocking');
-        
+
         $this->_userAD = new Tinebase_User_ActiveDirectory(array(
             'userDn'   => $this->userDN,
             'groupsDn' => $this->groupsDN,
             'ldap'     => $this->_getTinebaseLdapStub(),
             'useRfc2307' => true
-        )); 
-    }
+        ));
 
-    /**
-     * try to add a group
-     *
-     */
-    public function testAddUserToSyncBackend()
-    {
         $addedUser = $this->_userAD->addUserToSyncBackend(new Tinebase_Model_FullUser(array(
             'accountLoginName'    => 'larskneschke',
             'accountPrimaryGroup' => $this->groupSid,
@@ -94,7 +85,7 @@ class Tinebase_User_ActiveDirectoryTest extends PHPUnit_Framework_TestCase
     {
         switch ($dn) {
             default:
-                $this->fail("unkown dn $filter in " . __METHOD__);
+                $this->fail("unkown dn $dn in " . __METHOD__);
                 
                 break;
         }
@@ -143,4 +134,27 @@ class Tinebase_User_ActiveDirectoryTest extends PHPUnit_Framework_TestCase
         
         return $stub;
     }
+
+    /**
+     * testConvertADTimestamp
+     *
+     * @see 0011074: Active Directory as User Backend
+     */
+    public function testConvertADTimestamp()
+    {
+        $timestamps = array(
+            '130764553441237094'  => '2015-05-18 20:42:24',
+            '130791798699200155'  => '2015-06-19 09:31:09',
+            '9223372036854775807' => '30828-09-14 02:48:05',
+        );
+
+        foreach ($timestamps as $timestamp => $expected) {
+            $this->assertEquals($expected, Tinebase_User_ActiveDirectory::convertADTimestamp($timestamp)->toString());
+        }
+
+        // sometimes the ad timestamp is a float. i could not reproduce this case
+        // let's create a value like this and pass it directly to Tinebase_DateTime
+        $date = new Tinebase_DateTime('1391776840.7434058000');
+        $this->assertEquals('2014-02-07 12:40:40', $date->toString());
+    }
 }
index 0f5fd42..94b2db7 100644 (file)
@@ -57,6 +57,7 @@ class ActiveSync_Config extends Tinebase_Config_Abstract
             'clientRegistryInclude' => FALSE,
             'setByAdminModule'      => TRUE,
             'setBySetupModule'      => FALSE,
+            'default'               => null,
         ),
         self::DISABLE_ACCESS_LOG => array(
         //_('Disable Access Log')
@@ -75,7 +76,7 @@ class ActiveSync_Config extends Tinebase_Config_Abstract
         //_('For how long in the past the emails should be synchronized.')
             'description'           => 'For how long in the past the emails should be synchronized.',
             'type'                  => Tinebase_Config_Abstract::TYPE_INT,
-            // @todo options is not used yet (only for TYPE_KEYFIELD configs),
+            // @todo options is not used yet (only for TYPE_KEYFIELD_CONFIG configs),
             //  but this is helpful to see which values are possible here
             'options'               => array(
                 Syncroton_Command_Sync::FILTER_NOTHING,
@@ -98,7 +99,7 @@ class ActiveSync_Config extends Tinebase_Config_Abstract
         //_('For how long in the past the events should be synchronized.')
             'description'           => 'For how long in the past the events should be synchronized.',
             'type'                  => Tinebase_Config_Abstract::TYPE_INT,
-            // @todo options is not used yet (only for TYPE_KEYFIELD configs),
+            // @todo options is not used yet (only for TYPE_KEYFIELD_CONFIG configs),
             //  but this is helpful to see which values are possible here
             'options'               => array(
                 Syncroton_Command_Sync::FILTER_6_MONTHS_BACK,
index 160df5c..2e1f808 100644 (file)
@@ -5,8 +5,8 @@
  * @package     Addressbook
  * @subpackage  Acl
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
- * @copyright   Copyright (c) 2009 Metaways Infosystems GmbH (http://www.metaways.de)
- * @author      Philipp Schuele <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2009-2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Philipp Schüle <p.schuele@metaways.de>
  * 
  */
 
@@ -84,6 +84,7 @@ class Addressbook_Acl_Rights extends Tinebase_Acl_Rights_Abstract
         
         $addRights = array(
             Tinebase_Acl_Rights::MANAGE_SHARED_FOLDERS,
+            Tinebase_Acl_Rights::USE_PERSONAL_TAGS,
             self::MANAGE_SHARED_CONTACT_FAVORITES,
         );
         $allRights = array_merge($allRights, $addRights);
@@ -114,5 +115,4 @@ class Addressbook_Acl_Rights extends Tinebase_Acl_Rights_Abstract
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
         return $rightDescriptions;
     }
-
 }
index 9353b3f..f45cc2a 100644 (file)
@@ -63,7 +63,14 @@ class Addressbook_Config extends Tinebase_Config_Abstract
             'type'                  => 'keyFieldConfig',
             'options'               => array('recordModel' => 'Addressbook_Model_Salutation'),
             'clientRegistryInclude' => TRUE,
-        //            'default'               => 'MR'
+            'default'               => array(
+                'records' => array(
+                    array('id' => 'MR',      'value' => 'Mr',      'gender' => Addressbook_Model_Salutation::GENDER_MALE,   'image' => 'images/empty_photo_male.png',    'system' => true), //_('Mr')
+                    array('id' => 'MS',      'value' => 'Ms',      'gender' => Addressbook_Model_Salutation::GENDER_FEMALE, 'image' => 'images/empty_photo_female.png',  'system' => true), //_('Ms')
+                    array('id' => 'COMPANY', 'value' => 'Company', 'gender' => Addressbook_Model_Salutation::GENDER_OTHER,  'image' => 'images/empty_photo_company.png', 'system' => true), //_('Company')
+                ),
+//                'default' => 'MR'
+            )
         ),
         self::CONTACT_ADDRESS_PARSE_RULES_FILE => array(
         //_('Parsing rules for addresses')
index fda16f0..7d37e46 100644 (file)
@@ -69,7 +69,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
     public function toTine20Model($blob, Tinebase_Record_Abstract $_record = null, $options = array())
     {
         $vcard = self::getVObject($blob);
-        
+
         if ($_record instanceof Addressbook_Model_Contact) {
             $contact = $_record;
         } else {
@@ -77,7 +77,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
         }
         
         $data = $this->_emptyArray;
-        
+
         foreach ($vcard->children() as $property) {
             switch ($property->name) {
                 case 'VERSION':
@@ -99,7 +99,7 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
                     }
                     
                     $parts = $property->getParts();
-                    
+
                     if ($type == 'home') {
                         // home address
                         $data['adr_two_street2']     = $parts[1];
@@ -212,14 +212,14 @@ abstract class Addressbook_Convert_Contact_VCard_Abstract implements Tinebase_Co
                 $data['n_family'] = empty($data['n_fn']) ? 'VCARD (imported)' : $data['n_fn'];
             }
         }
-        
+
         $contact->setFromArray($data);
 
         if (isset($jpegphoto)) {
             $contact->setSmallContactImage($jpegphoto);
         }
         
-        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' data ' . print_r($contact->toArray(), true));
         
         if (isset($options[self::OPTION_USE_SERVER_MODLOG]) && $options[self::OPTION_USE_SERVER_MODLOG] === true) {
index d8925f7..844949a 100644 (file)
@@ -25,6 +25,7 @@ class Addressbook_Convert_Contact_VCard_Factory
     const CLIENT_EMCLIENT       = 'emclient';
     const CLIENT_COLLABORATOR   = 'WebDAVCollaborator';
     const CLIENT_AKONADI        = 'akonadi';
+    const CLIENT_TELEFONBUCH    = 'telefonbuch';
     
     /**
      * cache parsed user-agent strings
@@ -80,8 +81,10 @@ class Addressbook_Convert_Contact_VCard_Factory
                 
             case Addressbook_Convert_Contact_VCard_Factory::CLIENT_COLLABORATOR:
                 return new Addressbook_Convert_Contact_VCard_WebDAVCollaborator($_version);
-                
                 break;
+
+            case Addressbook_Convert_Contact_VCard_Factory::CLIENT_TELEFONBUCH:
+                return new Addressbook_Convert_Contact_VCard_Telefonbuch($_version);
         }
     }
     
diff --git a/tine20/Addressbook/Convert/Contact/VCard/Telefonbuch.php b/tine20/Addressbook/Convert/Contact/VCard/Telefonbuch.php
new file mode 100644 (file)
index 0000000..162b487
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Addressbook
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <kontakt@michaelspahn.de>
+ * @copyright   Copyright (c) 2015 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * class to convert a telefonbuch (http://www.dastelefonbuch.de/) vcard to contact model and back again
+ *
+ * @package     Addressbook
+ * @subpackage  Convert
+ */
+class Addressbook_Convert_Contact_VCard_Telefonbuch extends Addressbook_Convert_Contact_VCard_Abstract
+{
+    protected $_emptyArray = array();
+
+    /**
+     * converts vcard to Addressbook_Model_Contact
+     *
+     * @param  \Sabre\VObject\Component|stream|string  $blob       the vcard to parse
+     * @param  Tinebase_Record_Abstract                $_record    update existing contact
+     * @param  array                                   $options    array of options
+     * @return Addressbook_Model_Contact
+     */
+    public function toTine20Model($blob, Tinebase_Record_Abstract $_record = null, $options = array())
+    {
+        $vcard = self::getVObject($blob);
+
+        if ($_record instanceof Addressbook_Model_Contact) {
+            $contact = $_record;
+        } else {
+            $contact = new Addressbook_Model_Contact(null, false);
+        }
+
+        $data = $this->_emptyArray;
+
+        foreach ($vcard->children() as $property) {
+            switch ($property->name) {
+                case 'VERSION':
+                case 'PRODID':
+                case 'UID':
+                    // do nothing
+                    break;
+
+                case 'ADR':
+                    $parts = $property->getParts();
+
+                    // work address
+                    $data['adr_one_street2']     = $parts[1];
+                    $data['adr_one_street']      = $parts[2];
+                    $data['adr_one_locality']    = $parts[3];
+                    $data['adr_one_region']      = $parts[4];
+                    $data['adr_one_postalcode']  = $parts[5];
+                    $data['adr_one_countryname'] = $parts[6];
+                    break;
+
+                case 'CATEGORIES':
+                    $tags = Tinebase_Model_Tag::resolveTagNameToTag($property->getParts(), 'Addressbook');
+                    if (! isset($data['tags'])) {
+                        $data['tags'] = $tags;
+                    } else {
+                        $data['tags']->merge($tags);
+                    }
+                    break;
+
+                case 'EMAIL':
+                    $this->_toTine20ModelParseEmail($data, $property, $vcard);
+                    break;
+
+                case 'FN':
+                    $data['n_fn'] = $property->getValue();
+                    $data['n_given'] = $data['n_fn'];
+                    break;
+
+                case 'N':
+                    $parts = $property->getParts();
+
+                    $data['n_family'] = $parts[0];
+                    $data['n_middle'] = isset($parts[2]) ? $parts[2] : null;
+                    $data['n_prefix'] = isset($parts[3]) ? $parts[3] : null;
+                    $data['n_suffix'] = isset($parts[4]) ? $parts[4] : null;
+                    break;
+
+                case 'NOTE':
+                    $data['note'] = $property->getValue();
+                    break;
+
+                case 'ORG':
+                    $parts = $property->getParts();
+
+                    $data['org_name'] = $parts[0];
+                    $data['org_unit'] = isset($parts[1]) ? $parts[1] : null;
+                    break;
+
+                case 'PHOTO':
+                    $data['jpegphoto'] = $property->getValue();
+                    break;
+
+                case 'TEL':
+                    $this->_toTine20ModelParseTel($data, $property);
+                    break;
+
+                case 'URL':
+                    switch (strtoupper($property['TYPE'])) {
+                        case 'HOME':
+                            $data['url_home'] = $property->getValue();
+                            break;
+
+                        case 'WORK':
+                        default:
+                            $data['url'] = $property->getValue();
+                            break;
+                    }
+                    break;
+
+                case 'TITLE':
+                    $data['title'] = $property->getValue();
+                    break;
+
+                case 'BDAY':
+                    $this->_toTine20ModelParseBday($data, $property);
+                    break;
+
+                default:
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
+                        Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' cardData ' . $property->name);
+                    break;
+            }
+        }
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
+            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' data ' . print_r($data, true));
+
+        // Some email clients will only set a contact with FN (formatted name) without surname
+        if (empty($data['n_family']) && empty($data['org_name']) && (!empty($data['n_fn']))) {
+            if (strpos($data['n_fn'], ",") > 0) {
+                list($lastname, $firstname) = explode(",", $data['n_fn'], 2);
+                $data['n_family'] = trim($lastname);
+                $data['n_given']  = trim($firstname);
+
+            } elseif (strpos($data['n_fn'], " ") > 0) {
+                list($firstname, $lastname) = explode(" ", $data['n_fn'], 2);
+                $data['n_family'] = trim($lastname);
+                $data['n_given']  = trim($firstname);
+
+            } else {
+                $data['n_family'] = $data['n_fn'];
+            }
+        }
+
+        $contact->setFromArray($data);
+
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
+            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' data ' . print_r($contact->toArray(), true));
+
+        if (isset($options[self::OPTION_USE_SERVER_MODLOG]) && $options[self::OPTION_USE_SERVER_MODLOG] === true) {
+            $contact->creation_time = $_record->creation_time;
+            $contact->last_modified_time = $_record->last_modified_time;
+            $contact->seq = $_record->seq;
+        }
+
+        return $contact;
+    }
+}
diff --git a/tine20/Addressbook/Convert/Contact/config/convert_from_string_improved.xml b/tine20/Addressbook/Convert/Contact/config/convert_from_string_improved.xml
new file mode 100644 (file)
index 0000000..b241e9e
--- /dev/null
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<config>
+    <rules>
+        <rule>
+            <field>n_family</field>
+            <regex>/^(\w+)\s\w+/um</regex>
+        </rule>
+        <rule>
+            <field>n_given</field>
+            <regex>/^\w+\s(\w+)/um</regex>
+        </rule>
+        <rule>
+            <field>n_prefix</field>
+            <regex>/([Dr\.|Prof\.][\w+{2-4}\.]+)/um</regex>
+        </rule>
+        <rule>
+            <field>org_name</field>
+            <regex>/^\w+\s\w+\s[\w+{2-4}\.]+\s(\w+)/u</regex>
+        </rule>
+        <rule>
+            <field>adr_one_postalcode</field>
+            <regex>/\s*(\d{5})\s+[\w]+/um</regex>
+        </rule>
+        <rule>
+            <field>adr_one_postalcode</field>
+            <regex>/[A-Z]{1,3}-(\d{5})\s+[\w ]+/um</regex>
+        </rule>
+        <rule>
+            <field>adr_one_locality</field>
+            <regex>/\d{5}\s+([\w ]+)/um</regex>
+        </rule>
+        <rule>
+            <field>adr_one_street</field>
+            <regex>/^([\w ]+\s+\d+)/um</regex>
+        </rule>
+        <rule>
+            <field>org_name</field>
+            <regex>/(^.*(?:GmbH|GbR|OHG|KG).*)/um</regex>
+        </rule>
+        <rule>
+            <field>tel_work</field>
+            <regex>/^(?:Tel[^+^0-9]*)(\+{0,1}[0-9\ \t()-]+)/um</regex>
+        </rule>
+        <rule>
+            <field>tel_fax</field>
+            <regex>/^(?:Fax[^+^0-9]*)(\+{0,1}[0-9\ \t()-]+)/um</regex>
+        </rule>
+        <rule>
+            <field>email</field>
+            <regex>/([a-z0-9_\+-\.]+@[a-z0-9-\.]+\.[a-z]{2,5})/i</regex>
+        </rule>
+        <rule>
+            <field>url</field>
+            <regex>/((mailto\:|(news|(ht|f)tp(s?))\:\/\/){1}\S+)/i</regex>
+        </rule>
+    </rules>
+</config>
index 370dac4..8ad0367 100755 (executable)
@@ -6,7 +6,7 @@
  * @subpackage  Frontend
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Lars Kneschke <l.kneschke@metaways.de>
- * @copyright   Copyright (c) 2007-2009 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -35,13 +35,17 @@ class Addressbook_Frontend_Http extends Tinebase_Frontend_Http_Abstract
     public function exportContacts($filter, $options)
     {
         $decodedFilter = Zend_Json::decode($filter);
-        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Export filter: ' . print_r($decodedFilter, TRUE));
+        $decodedOptions = Zend_Json::decode($options);
+        
+        if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
+            . ' Export filter: ' . print_r($decodedFilter, true)
+            . ' Options: ' . print_r($decodedOptions, true));
         
         if (! is_array($decodedFilter)) {
             $decodedFilter = array(array('field' => 'id', 'operator' => 'equals', 'value' => $decodedFilter));
         }
         
         $filter = new Addressbook_Model_ContactFilter($decodedFilter);
-        parent::_export($filter, Zend_Json::decode($options), Addressbook_Controller_Contact::getInstance());
+        parent::_export($filter, $decodedOptions, Addressbook_Controller_Contact::getInstance());
     }
 }
index 8770eea..0323f80 100755 (executable)
@@ -25,7 +25,9 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
      * @var string
      */
     protected $_applicationName = 'Addressbook';
-    
+
+    protected $_defaultImportDefinitionName = 'adb_tine_import_csv';
+
     /**
      * resolve images
      * @param Tinebase_Record_RecordSet $_records
@@ -158,16 +160,45 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     */
     public function parseAddressData($address)
     {
-        $result = Addressbook_Controller_Contact::getInstance()->parseAddressData($address);
-        $contactData = $this->_recordToJson($result['contact']);
-        
-        unset($contactData['jpegphoto']);
-        unset($contactData['salutation']);
-        
-        return array(
-            'contact'             => $contactData,
-            'unrecognizedTokens'  => $result['unrecognizedTokens'],
-        );
+        if (preg_match('/^http/', $address)) {
+            $vcard = file_get_contents($address);
+
+            // Could not load file from remote
+            if ($vcard === false) {
+                return array('exceptions' => "Cannot get file from remote.");
+            }
+
+            $converter = Addressbook_Convert_Contact_VCard_Factory::factory(
+                strpos($address, 'dastelefonbuch')
+                ? Addressbook_Convert_Contact_VCard_Factory::CLIENT_TELEFONBUCH
+                : Addressbook_Convert_Contact_VCard_Factory::CLIENT_GENERIC
+            );
+
+            $record = $converter->toTine20Model($vcard);
+            $contactData = $this->_recordToJson($record);
+
+            if (array_key_exists('jpegphoto', $contactData)) {
+                unset($contactData['jpegphoto']);
+            }
+
+            return array('contact' => $contactData);
+        } else {
+            $result = Addressbook_Controller_Contact::getInstance()->parseAddressData($address);
+            $contactData = $this->_recordToJson($result['contact']);
+
+            if (array_key_exists('jpegphoto', $contactData)) {
+                unset($contactData['jpegphoto']);
+            }
+
+            if (array_key_exists('salutation', $contactData)) {
+                unset($contactData['salutation']);
+            }
+
+            return array(
+                'contact' => $contactData,
+                'unrecognizedTokens' => $result['unrecognizedTokens'],
+            );
+        }
     }
     
     /**
@@ -242,68 +273,19 @@ class Addressbook_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     }
 
     /**
-     * Returns registry data of addressbook.
+     * Returns registry data of Addressbook.
      * @see Tinebase_Application_Json_Abstract
      * 
      * @return mixed array 'variable name' => 'data'
      */
     public function getRegistryData()
     {
-        $definitionConverter = new Tinebase_Convert_ImportExportDefinition_Json();
-        $importDefinitions = $this->_getImportDefinitions();
-        $defaultDefinition = $this->_getDefaultImportDefinition($importDefinitions);
-        
         $registryData = array(
             'defaultAddressbook'        => $this->getDefaultAddressbook(),
-            'defaultImportDefinition'   => $definitionConverter->fromTine20Model($defaultDefinition),
-            'importDefinitions'         => array(
-                'results'               => $definitionConverter->fromTine20RecordSet($importDefinitions),
-                'totalcount'            => count($importDefinitions),
-            ),
         );
+
+        $registryData = array_merge($registryData, $this->_getImportDefinitionRegistryData());
+
         return $registryData;
     }
-    
-    /**
-     * get addressbook import definitions
-     * 
-     * @return Tinebase_Record_RecordSet
-     * 
-     * @todo generalize this
-     */
-    protected function _getImportDefinitions()
-    {
-        $filter = new Tinebase_Model_ImportExportDefinitionFilter(array(
-            array('field' => 'application_id',  'operator' => 'equals', 'value' => Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->getId()),
-            array('field' => 'type',            'operator' => 'equals', 'value' => 'import'),
-        ));
-        
-        $importDefinitions = Tinebase_ImportExportDefinition::getInstance()->search($filter);
-        
-        return $importDefinitions;
-    }
-    
-    /**
-     * get default definition
-     * 
-     * @param Tinebase_Record_RecordSet $_importDefinitions
-     * @return Tinebase_Model_ImportExportDefinition
-     * 
-     * @todo generalize this
-     */
-    protected function _getDefaultImportDefinition($_importDefinitions)
-    {
-        try {
-            $defaultDefinition = Tinebase_ImportExportDefinition::getInstance()->getByName('adb_tine_import_csv');
-        } catch (Tinebase_Exception_NotFound $tenf) {
-            if (count($_importDefinitions) > 0) {
-                $defaultDefinition = $_importDefinitions->getFirstRecord();
-            } else {
-                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' No import definitions found for Addressbook');
-                $defaultDefinition = NULL;
-            }
-        }
-        
-        return $defaultDefinition;
-    }
 }
index b468cda..adf8a30 100644 (file)
@@ -6,7 +6,7 @@
  * @subpackage  Import
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schuele <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2015 Metaways Infosystems GmbH (http://www.metaways.de)
  */
 
 /**
@@ -28,11 +28,11 @@ class Addressbook_Import_Csv extends Tinebase_Import_Csv_Abstract
     
     /**
      * creates a new importer from an importexport definition
-     * 
+     *
      * @param  Tinebase_Model_ImportExportDefinition $_definition
      * @param  array                                 $_options
      * @return Calendar_Import_Ical
-     * 
+     *
      * @todo move this to abstract when we no longer need to be php 5.2 compatible
      */
     public static function createFromDefinition(Tinebase_Model_ImportExportDefinition $_definition, array $_options = array())
index 37cdf2f..6ee8356 100644 (file)
@@ -171,6 +171,9 @@ class Addressbook_Import_VCard extends Tinebase_Import_Abstract
         if ($addressProperty) {
             $properties = $card->getProperties($addressProperty);
             foreach ($properties as $property){
+                if (! array_key_exists('TYPE', $property->params)) {
+                    $property->params['TYPE'] = 'work';
+                }
                 // types available from RFC : 'dom', 'intl', 'postal', 'parcel', 'home', 'work', 'pref'
                 $types = $property->params['TYPE'];
                 
@@ -180,7 +183,7 @@ class Addressbook_Import_VCard extends Tinebase_Import_Abstract
                 
                 $components = $property->getComponents();
                 
-                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__
                     . ' Address components ' . print_r($components, TRUE));
                 
                 $mapping = array(NULL, 'street2', 'street', 'locality', 'region', 'postalcode', 'countryname');
index eb55010..7ca307e 100644 (file)
  * 
  * @package     Addressbook
  * @subpackage  Model
- * @property    account_id                 id of associated user
- * @property    adr_one_countryname        name of the country the contact lives in
- * @property    adr_one_locality           locality of the contact
- * @property    adr_one_postalcode         postalcode belonging to the locality
- * @property    adr_one_region             region the contact lives in
- * @property    adr_one_street             street where the contact lives
- * @property    adr_one_street2            street2 where contact lives
- * @property    adr_two_countryname        second home/country where the contact lives
- * @property    adr_two_locality           second locality of the contact
- * @property    adr_two_postalcode         ostalcode belonging to second locality
- * @property    adr_two_region             second region the contact lives in
- * @property    adr_two_street             second street where the contact lives
- * @property    adr_two_street2            second street2 where the contact lives
- * @property    assistent                  name of the assistent of the contact
- * @property    bday                       date of birth of contact
- * @property    container_id               id of container
- * @property    email                      the email address of the contact
- * @property    email_home                 the private email address of the contact
- * @property    jpegphoto                  photo of the contact
- * @property    n_family                   surname of the contact
- * @property    n_fileas                   display surname, name
- * @property    n_fn                       the full name
- * @property    n_given                    forename of the contact
- * @property    n_middle                   middle name of the contact
- * @property    note                       notes of the contact    
- * @property    n_prefix
- * @property    n_suffix
- * @property    org_name                   name of the company the contact works at
- * @property    org_unit
- * @property    role                       type of role of the contact  
- * @property    tel_assistent              phone number of the assistent
- * @property    tel_car
- * @property    tel_cell                   mobile phone number
- * @property    tel_cell_private           private mobile number
- * @property    tel_fax                    number for calling the fax
- * @property    tel_fax_home               private fax number
- * @property    tel_home                   telephone number of contact's home
- * @property    tel_pager                  contact's pager number
- * @property    tel_work                   contact's office phone number
- * @property    title                      special title of the contact
- * @property    type                       type of contact
- * @property    url                        url of the contact
- * @property    url_home                   private url of the contact
+ * @property    string account_id                 id of associated user
+ * @property    string adr_one_countryname        name of the country the contact lives in
+ * @property    string adr_one_locality           locality of the contact
+ * @property    string adr_one_postalcode         postalcode belonging to the locality
+ * @property    string adr_one_region             region the contact lives in
+ * @property    string adr_one_street             street where the contact lives
+ * @property    string adr_one_street2            street2 where contact lives
+ * @property    string adr_two_countryname        second home/country where the contact lives
+ * @property    string adr_two_locality           second locality of the contact
+ * @property    string adr_two_postalcode         ostalcode belonging to second locality
+ * @property    string adr_two_region             second region the contact lives in
+ * @property    string adr_two_street             second street where the contact lives
+ * @property    string adr_two_street2            second street2 where the contact lives
+ * @property    string assistent                  name of the assistent of the contact
+ * @property    datetime bday                     date of birth of contact
+ * @property    integer container_id              id of container
+ * @property    string email                      the email address of the contact
+ * @property    string email_home                 the private email address of the contact
+ * @property    blob jpegphoto                    photo of the contact
+ * @property    string n_family                   surname of the contact
+ * @property    string n_fileas                   display surname, name
+ * @property    string n_fn                       the full name
+ * @property    string n_given                    forename of the contact
+ * @property    string n_middle                   middle name of the contact
+ * @property    string note                       notes of the contact
+ * @property    string n_prefix
+ * @property    string n_suffix
+ * @property    string org_name                   name of the company the contact works at
+ * @property    string org_unit
+ * @property    string role                       type of role of the contact
+ * @property    string tel_assistent              phone number of the assistent
+ * @property    string tel_car
+ * @property    string tel_cell                   mobile phone number
+ * @property    string tel_cell_private           private mobile number
+ * @property    string tel_fax                    number for calling the fax
+ * @property    string tel_fax_home               private fax number
+ * @property    string tel_home                   telephone number of contact's home
+ * @property    string tel_pager                  contact's pager number
+ * @property    string tel_work                   contact's office phone number
+ * @property    string title                      special title of the contact
+ * @property    string type                       type of contact
+ * @property    string url                        url of the contact
+ * @property    string url_home                   private url of the contact
  */
 class Addressbook_Model_Contact extends Tinebase_Record_Abstract
 {
@@ -247,7 +247,6 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
     * @param mixed $_data
     * @param bool $_bypassFilters
     * @param mixed $_convertDates
-    * @return void
     */
     public function __construct($_data = NULL, $_bypassFilters = false, $_convertDates = true)
     {
@@ -257,7 +256,7 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
             $this->_filters[$geoField]        = new Zend_Filter_Empty(NULL);
         }
     
-        return parent::__construct($_data, $_bypassFilters, $_convertDates);
+        parent::__construct($_data, $_bypassFilters, $_convertDates);
     }
     
     /**
@@ -290,23 +289,32 @@ class Addressbook_Model_Contact extends Tinebase_Record_Abstract
         if (! (isset($_data['org_name']) || array_key_exists('org_name', $_data))) {
             $_data['org_name'] = '';
         }
+
+        // try to guess name from n_fileas
+        // TODO: n_fn
+        if (empty($_data['org_name']) && empty($_data['n_family'])) {
+            if (! empty($_data['n_fileas'])) {
+                $names = preg_split('/\s*,\s*/', $_data['n_fileas']);
+                $_data['n_family'] = $names[0];
+                if (empty($_data['n_given'])&& isset($names[1])) {
+                    $_data['n_given'] = $names[1];
+                }
+            }
+        }
         
         // always update fileas and fn
         $_data['n_fileas'] = (!empty($_data['n_family'])) ? $_data['n_family']
             : ((! empty($_data['org_name'])) ? $_data['org_name']
             : ((isset($_data['n_fileas'])) ? $_data['n_fileas'] : ''));
-        
+
         if (!empty($_data['n_given'])) {
             $_data['n_fileas'] .= ', ' . $_data['n_given'];
-            
-            // to change n_fileas to "ngiven nfamily" use this line instead of the above
-            //$_data['n_fileas'] = $_data['n_given'] . ' ' . $_data['n_fileas'];
         }
-        
+
         $_data['n_fn'] = (!empty($_data['n_family'])) ? $_data['n_family']
             : ((! empty($_data['org_name'])) ? $_data['org_name']
             : ((isset($_data['n_fn'])) ? $_data['n_fn'] : ''));
-        
+
         if (!empty($_data['n_given'])) {
             $_data['n_fn'] = $_data['n_given'] . ' ' . $_data['n_fn'];
         }
index be414b8..0c5ba58 100644 (file)
@@ -109,6 +109,7 @@ class Addressbook_Model_ContactFilter extends Tinebase_Model_Filter_FilterGroup
         )),
         'last_modified_time'   => array('filter' => 'Tinebase_Model_Filter_Date'),
         'deleted_time'         => array('filter' => 'Tinebase_Model_Filter_DateTime'),
+        'is_deleted'            => array('filter' => 'Tinebase_Model_Filter_Bool'),
         'creation_time'        => array('filter' => 'Tinebase_Model_Filter_Date'),
         'last_modified_by'     => array('filter' => 'Tinebase_Model_Filter_User'),
         'created_by'           => array('filter' => 'Tinebase_Model_Filter_User'),
index a00aa3d..4d42974 100644 (file)
@@ -53,32 +53,6 @@ class Addressbook_Setup_Initialize extends Setup_Initialize
     }
     
     /**
-    * init key fields
-    */
-    protected function _initializeKeyFields()
-    {
-        $cb = new Tinebase_Backend_Sql(array(
-            'modelName' => 'Tinebase_Model_Config', 
-            'tableName' => 'config',
-        ));
-    
-        $keyfieldConfig = array(
-            'name'    => Addressbook_Config::CONTACT_SALUTATION,
-            'records' => array(
-                array('id' => 'MR',      'value' => 'Mr',        'gender' => Addressbook_Model_Salutation::GENDER_MALE,   'image' => 'images/empty_photo_male.png',    'system' => true), //_('Mr')
-                array('id' => 'MS',      'value' => 'Ms',      'gender' => Addressbook_Model_Salutation::GENDER_FEMALE, 'image' => 'images/empty_photo_female.png',  'system' => true), //_('Ms')
-                array('id' => 'COMPANY', 'value' => 'Company', 'gender' => Addressbook_Model_Salutation::GENDER_OTHER,  'image' => 'images/empty_photo_company.png', 'system' => true), //_('Company')
-            ),
-        );
-    
-        $cb->create(new Tinebase_Model_Config(array(
-            'application_id'    => Tinebase_Application::getInstance()->getApplicationByName('Addressbook')->getId(),
-            'name'              => Addressbook_Config::CONTACT_SALUTATION,
-            'value'             => json_encode($keyfieldConfig),
-        )));
-    }
-    
-    /**
      * init/create user contacts
      */
     protected function _initializeUserContacts()
index 3e81e14..0cac2fd 100644 (file)
@@ -443,7 +443,7 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
      */
     onParseAddress: function (button) {
         if (button.pressed) {
-            Ext.Msg.prompt(this.app.i18n._('Paste address'), this.app.i18n._('Please paste an address that should be parsed:'), function(btn, text) {
+            Ext.Msg.prompt(this.app.i18n._('Paste address'), this.app.i18n._('Please paste an address or a URI to a vcard that should be parsed:'), function(btn, text) {
                 if (btn == 'ok'){
                     this.parseAddress(text);
                 } else if (btn == 'cancel') {
@@ -468,19 +468,27 @@ Tine.Addressbook.ContactEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog,
             Tine.log.debug('parsed address:');
             Tine.log.debug(result);
 
-            // only set the fields that could be detected
-            Ext.iterate(result.contact, function(key, value) {
-                if (value && ! this.record.get(key)) {
-                    this.record.set(key, value);
+            if (result.hasOwnProperty('exceptions')) {
+                Ext.Msg.alert(this.app.i18n._('Failed to parse address!'), this.app.i18n._('The address could not be read.'));
+            } else {
+                // only set the fields that could be detected
+                Ext.iterate(result.contact, function(key, value) {
+                    if (value && ! this.record.get(key)) {
+                        this.record.set(key, value);
+                    }
+                }, this);
+
+                var oldNote = (this.record.get('note')) ? this.record.get('note') : '';
+
+                if (result.hasOwnProperty('unrecognizedTokens')) {
+                    this.record.set('note', result.unrecognizedTokens.join(' ') + oldNote);
                 }
-            }, this);
 
-            var oldNote = (this.record.get('note')) ? this.record.get('note') : '';
-            this.record.set('note', result.unrecognizedTokens.join(' ') + oldNote);
-            this.onRecordLoad();
+                this.onRecordLoad();
 
-            this.parseAddressButton.setText(this.app.i18n._('End token mode'));
-            this.tokenModePlugin.startTokenMode();
+                this.parseAddressButton.setText(this.app.i18n._('End token mode'));
+                this.tokenModePlugin.startTokenMode();
+            }
         }, this);
     },
 
index 935141e..9ef6cef 100644 (file)
@@ -228,6 +228,15 @@ Tine.Addressbook.ContactGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         
         Tine.Addressbook.ContactGridPanel.superclass.initActions.call(this);
     },
+
+    /**
+     * get default / selected addressbook container
+     *
+     * @returns {Object|Tine.Tinebase.Model.Container}
+     */
+    getDefaultContainer: function() {
+        return this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer('defaultAddressbook');
+    },
     
     /**
      * add custom items to action toolbar
@@ -264,34 +273,6 @@ Tine.Addressbook.ContactGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     },
     
     /**
-     * import contacts
-     * 
-     * @param {Button} btn 
-     * 
-     * TODO generalize this & the import button
-     */
-    onImport: function(btn) {
-        var popupWindow = Tine.widgets.dialog.ImportDialog.openWindow({
-            appName: 'Addressbook',
-            modelName: 'Contact',
-            defaultImportContainer: this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer('defaultAddressbook'),
-            
-            // update grid after import
-            listeners: {
-                scope: this,
-                'finish': function() {
-                    this.loadGridData({
-                        preserveCursor:     false, 
-                        preserveSelection:  false, 
-                        preserveScroller:   false,
-                        removeStrategy:     'default'
-                    });
-                }
-            }
-        });
-    },
-        
-    /**
      * tid renderer
      * 
      * @private
index 2054b51..24726fd 100644 (file)
@@ -1,4 +1,4 @@
-# 
+#
 # Translators:
 # Björn Balazs <transifex@lazs.de>, 2013
 # Juergen Heine <transifex.com@sysdef.de>, 2013
@@ -13,140 +13,26 @@ msgstr ""
 "PO-Revision-Date: 2014-11-21 11:48+0000\n"
 "Last-Translator: sstamer <s.stamer@metaways.de>\n"
 "Language-Team: German (http://www.transifex.com/projects/p/tine20/language/de/)\n"
+"Language: de\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"Language: de\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "X-Poedit-Country: GB\n"
 "X-Poedit-Language: en\n"
 "X-Poedit-SourceCharset: utf-8\n"
 
-#: Export/Pdf.php:37
-msgid "Business Contact Data"
-msgstr "Geschäftliche Kontaktdaten"
-
-#: Export/Pdf.php:39
-msgid "Organisation / Unit"
-msgstr "Firma / Abteilung"
-
-#: Export/Pdf.php:44
-msgid "Business Address"
-msgstr "Geschäftliche Anschrift"
-
-#: Export/Pdf.php:52 js/ContactGrid.js:132
-msgid "Email"
-msgstr "E-Mail"
-
-#: Export/Pdf.php:55
-msgid "Telephone Work"
-msgstr "Telefon (Büro)"
-
-#: Export/Pdf.php:58
-msgid "Telephone Cellphone"
-msgstr "Telefon (Mobil)"
-
-#: Export/Pdf.php:61
-msgid "Telephone Car"
-msgstr "Telefon (Auto)"
-
-#: Export/Pdf.php:64
-msgid "Telephone Fax"
-msgstr "Fax"
-
-#: Export/Pdf.php:67
-msgid "Telephone Page"
-msgstr "Pager"
-
-#: Export/Pdf.php:70
-msgid "URL"
-msgstr "URL"
-
-#: Export/Pdf.php:73
-msgid "Role"
-msgstr "Funktion"
-
-#: Export/Pdf.php:76 js/ContactEditDialog.js:146 js/Model.js:33
-#: js/ContactGrid.js:121
-msgid "Room"
-msgstr "Raum"
-
-#: Export/Pdf.php:79
-msgid "Assistant"
-msgstr "Assistent"
-
-#: Export/Pdf.php:82
-msgid "Assistant Telephone"
-msgstr "Telefon des Assistenten"
-
-#: Export/Pdf.php:86
-msgid "Private Contact Data"
-msgstr "Private Kontaktdaten"
-
-#: Export/Pdf.php:88 js/ContactEditDialog.js:299 js/Model.js:43
-#: js/Model.js:158 js/Model.js:159 js/Model.js:160 js/Model.js:161
-#: js/Model.js:162
-msgid "Private Address"
-msgstr "Private Anschrift"
-
-#: Export/Pdf.php:96
-msgid "Email Home"
-msgstr "E-Mail  (Zuhause)"
-
-#: Export/Pdf.php:99
-msgid "Telephone Home"
-msgstr "Telefon  (Zuhause)"
-
-#: Export/Pdf.php:102
-msgid "Telephone Cellphone Private"
-msgstr "Telefon (Mobil)"
-
-#: Export/Pdf.php:105
-msgid "Telephone Fax Home"
-msgstr "Fax (Zuhause)"
-
-#: Export/Pdf.php:108
-msgid "URL Home"
-msgstr "URL (Zuhause)"
-
-#: Export/Pdf.php:112
-msgid "Other Data"
-msgstr "Andere Daten"
-
-#: Export/Pdf.php:114 js/ContactEditDialog.js:173 js/Model.js:26
-#: js/Model.js:152 js/ContactGrid.js:147
-msgid "Birthday"
-msgstr "Geburtstag"
-
-#: Export/Pdf.php:117 js/ContactEditDialog.js:167 js/Model.js:30
-#: js/Model.js:147 js/ContactGrid.js:119
-msgid "Job Title"
-msgstr "Berufs-Bezeichnung"
-
-#: Export/Doc.php:69
-msgid "Dear Mister"
-msgstr "Sehr geehrter Herr"
-
-#: Export/Doc.php:71
-msgid "Dear Miss"
-msgstr "Sehr geehrte Dame"
-
-#: Export/Doc.php:73
-msgid "Dear"
-msgstr "Sehr geehrte Damen und Herren"
-
-#: Export/Doc.php:89
-msgid "Mister"
-msgstr "Herr"
+#: Frontend/CardDAV/AllContacts.php:41
+msgid "All Contacts"
+msgstr "Alle Kontakte"
 
-#: Export/Doc.php:91
-msgid "Misses"
-msgstr "Frau"
+#: Frontend/Cli.php:166
+msgid "This contact has been automatically added by the system as an event attender"
+msgstr "Dieser Kontakt wurde automatisch vom System als Termin Teilnehmer hinzugefügt."
 
-#: Controller.php:105
-#, python-format
-msgid "%s's personal addressbook"
-msgstr "%ss persönliches Adressbuch"
+#: Controller/Contact.php:330
+msgid "Uploaded new contact image."
+msgstr "Ein neues Kontaktbild wurde hochgeladen."
 
 #: Acl/Rights.php:105
 msgid "manage shared addressbooks"
@@ -164,16 +50,62 @@ msgstr "Gemeinsame Adressbuch-Favoriten verwalten"
 msgid "Create or update shared addressbook favorites"
 msgstr "Gemeinsame Adressbuch-Favoriten erstellen oder bearbeiten"
 
-#: js/CardDAVContainerPropertiesHookField.js:35
-msgid "CardDAV URL"
-msgstr "CardDAV URL"
+#: Setup/setup.xml:654
+msgid "Internal Contacts"
+msgstr "Interne Kontakte"
 
-#: js/ContactFilterModel.js:35 js/Model.js:95 js/Model.js:134
-#: js/ContactGrid.js:304
-msgid "Contact"
-msgid_plural "Contacts"
-msgstr[0] "Kontakt"
-msgstr[1] "Kontakte"
+#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
+msgid "Mr"
+msgstr "Herr"
+
+#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
+msgid "Ms"
+msgstr "Frau"
+
+#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
+#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:117 js/Model.js:27
+#: js/Model.js:144 js/ContactGridDetailsPanel.js:131
+msgid "Company"
+msgstr "Firma"
+
+#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
+msgid "All contacts I have read grants for"
+msgstr "Alle Kontakte für die ich Lese-Rechte habe"
+
+#: Setup/Initialize.php:145
+msgid "My company"
+msgstr "Meine Firma"
+
+#: Setup/Initialize.php:146
+msgid "All coworkers in my company"
+msgstr "Alle Mitarbeiter meiner Firma"
+
+#: Setup/Initialize.php:159
+msgid "My contacts"
+msgstr "Meine Kontakte"
+
+#: Setup/Initialize.php:160
+msgid "All contacts in my Addressbooks"
+msgstr "Alle Kontakte in meinen Adressbüchern"
+
+#: Setup/Initialize.php:172
+msgid "Last modified by me"
+msgstr "Zuletzt von mir bearbeitet"
+
+#: Setup/Initialize.php:173
+msgid "All contacts that I have last modified"
+msgstr "Alle Kontakte, die ich zuletzt bearbeitet habe"
+
+#: js/Addressbook.js:22
+msgid "New Contact"
+msgstr "Neuer Kontakt"
+
+#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
+msgid "Addressbook"
+msgid_plural "Addressbooks"
+msgstr[0] "Adressbuch"
+msgstr[1] "Adressbücher"
 
 #: js/ContactEditDialog.js:41 js/ContactEditDialog.js:50 js/MapPanel.js:40
 msgid "Map"
@@ -183,40 +115,33 @@ msgstr "Karte"
 msgid "Personal Information"
 msgstr "Persönliche Informationen"
 
-#: js/ContactEditDialog.js:91 js/Model.js:29 js/ContactGrid.js:103
+#: js/ContactEditDialog.js:91 js/ContactGrid.js:103 js/Model.js:29
 msgid "Salutation"
 msgstr "Anrede"
 
-#: js/ContactEditDialog.js:107 js/Model.js:22 js/Model.js:140
-#: js/ContactGrid.js:111
+#: js/ContactEditDialog.js:107 js/ContactGrid.js:111 js/Model.js:22
+#: js/Model.js:140
 msgid "Title"
 msgstr "Titel"
 
-#: js/ContactEditDialog.js:112 js/Model.js:20 js/Model.js:141
-#: js/ContactGrid.js:114
+#: js/ContactEditDialog.js:112 js/ContactGrid.js:114 js/Model.js:20
+#: js/Model.js:141
 msgid "First Name"
 msgstr "Vorname"
 
-#: js/ContactEditDialog.js:117 js/Model.js:21 js/Model.js:143
-#: js/ContactGrid.js:112
+#: js/ContactEditDialog.js:117 js/ContactGrid.js:112 js/Model.js:21
+#: js/Model.js:143
 msgid "Middle Name"
 msgstr "zweiter Vorname"
 
 #: js/ContactEditDialog.js:122 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:19 js/Model.js:142
-#: js/ContactGrid.js:113
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:113 js/Model.js:19
+#: js/Model.js:142
 msgid "Last Name"
 msgstr "Nachname"
 
-#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:27 js/Model.js:144
-#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:117
-#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
-msgid "Company"
-msgstr "Firma"
-
-#: js/ContactEditDialog.js:133 js/Model.js:28 js/Model.js:145
-#: js/ContactGrid.js:118
+#: js/ContactEditDialog.js:133 js/ContactGrid.js:118 js/Model.js:28
+#: js/Model.js:145
 msgid "Unit"
 msgstr "Abteilung"
 
@@ -224,46 +149,59 @@ msgstr "Abteilung"
 msgid "Suffix"
 msgstr "Namenszusatz"
 
-#: js/ContactEditDialog.js:142 js/Model.js:31 js/Model.js:151
-#: js/ContactGrid.js:120
+#: js/ContactEditDialog.js:142 js/ContactGrid.js:120 js/Model.js:31
+#: js/Model.js:151
 msgid "Job Role"
 msgstr "Funktion"
 
-#: js/ContactEditDialog.js:162 js/Model.js:24 js/ContactGrid.js:116
+#: js/ContactEditDialog.js:146 js/ContactGrid.js:121 js/Model.js:33
+#: Export/Pdf.php:76
+msgid "Room"
+msgstr "Raum"
+
+#: js/ContactEditDialog.js:162 js/ContactGrid.js:116 js/Model.js:24
 msgid "Display Name"
 msgstr "Angezeigter Name"
 
+#: js/ContactEditDialog.js:167 js/ContactGrid.js:119 js/Model.js:30
+#: js/Model.js:147 Export/Pdf.php:117
+msgid "Job Title"
+msgstr "Berufs-Bezeichnung"
+
+#: js/ContactEditDialog.js:173 js/ContactGrid.js:147 js/Model.js:26
+#: js/Model.js:152 Export/Pdf.php:114
+msgid "Birthday"
+msgstr "Geburtstag"
+
 #: js/ContactEditDialog.js:181
 msgid "Contact Information"
 msgstr "Kontakt-Informationen"
 
-#: js/ContactEditDialog.js:186 js/Model.js:51 js/Model.js:146
-#: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
-#: js/ContactGrid.js:133
+#: js/ContactEditDialog.js:186 js/ContactGrid.js:133 js/Model.js:51
+#: js/Model.js:146 js/ContactGridDetailsPanel.js:139
+#: js/ContactGridDetailsPanel.js:163
 msgid "Phone"
 msgstr "Telefon"
 
-#: js/ContactEditDialog.js:191 js/Model.js:52
+#: js/ContactEditDialog.js:191 js/ContactGrid.js:134 js/Model.js:52
 #: js/ContactGridDetailsPanel.js:140 js/ContactGridDetailsPanel.js:164
-#: js/ContactGrid.js:134
 msgid "Mobile"
 msgstr "Handy"
 
-#: js/ContactEditDialog.js:196 js/Model.js:53
+#: js/ContactEditDialog.js:196 js/ContactGrid.js:135 js/Model.js:53
 #: js/ContactGridDetailsPanel.js:141 js/ContactGridDetailsPanel.js:165
-#: js/ContactGrid.js:135
 msgid "Fax"
 msgstr "Fax"
 
-#: js/ContactEditDialog.js:201 js/Model.js:57 js/ContactGrid.js:138
+#: js/ContactEditDialog.js:201 js/ContactGrid.js:138 js/Model.js:57
 msgid "Phone (private)"
 msgstr "Telefon (privat)"
 
-#: js/ContactEditDialog.js:206 js/Model.js:59 js/ContactGrid.js:140
+#: js/ContactEditDialog.js:206 js/ContactGrid.js:140 js/Model.js:59
 msgid "Mobile (private)"
 msgstr "Handy (privat)"
 
-#: js/ContactEditDialog.js:211 js/Model.js:58 js/ContactGrid.js:139
+#: js/ContactEditDialog.js:211 js/ContactGrid.js:139 js/Model.js:58
 msgid "Fax (private)"
 msgstr "Fax (privat)"
 
@@ -276,9 +214,8 @@ msgstr "E-Mail"
 msgid "E-Mail (private)"
 msgstr "E-Mail (privat)"
 
-#: js/ContactEditDialog.js:229 js/Model.js:64
+#: js/ContactEditDialog.js:229 js/ContactGrid.js:142 js/Model.js:64
 #: js/ContactGridDetailsPanel.js:144 js/ContactGridDetailsPanel.js:168
-#: js/ContactGrid.js:142
 msgid "Web"
 msgstr "Internet"
 
@@ -287,8 +224,8 @@ msgstr "Internet"
 msgid "Company Address"
 msgstr "Geschäftliche Anschrift"
 
-#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302 js/Model.js:153
-#: js/Model.js:158 js/ContactGrid.js:122
+#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302
+#: js/ContactGrid.js:122 js/Model.js:153 js/Model.js:158
 msgid "Street"
 msgstr "Straße"
 
@@ -296,8 +233,8 @@ msgstr "Straße"
 msgid "Street 2"
 msgstr "Straße 2"
 
-#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310 js/Model.js:154
-#: js/Model.js:159 js/ContactGrid.js:124
+#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310
+#: js/ContactGrid.js:124 js/Model.js:154 js/Model.js:159
 msgid "Region"
 msgstr "Region"
 
@@ -306,16 +243,21 @@ msgstr "Region"
 msgid "Postal Code"
 msgstr "Postleitzahl"
 
-#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318 js/Model.js:156
-#: js/Model.js:161 js/ContactGrid.js:123
+#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318
+#: js/ContactGrid.js:123 js/Model.js:156 js/Model.js:161
 msgid "City"
 msgstr "Ort"
 
-#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323 js/Model.js:157
-#: js/Model.js:162 js/ContactGrid.js:126
+#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323
+#: js/ContactGrid.js:126 js/Model.js:157 js/Model.js:162
 msgid "Country"
 msgstr "Land"
 
+#: js/ContactEditDialog.js:299 js/Model.js:43 js/Model.js:158 js/Model.js:159
+#: js/Model.js:160 js/Model.js:161 js/Model.js:162 Export/Pdf.php:88
+msgid "Private Address"
+msgstr "Private Anschrift"
+
 #: js/ContactEditDialog.js:344 js/Model.js:68 js/Model.js:148
 msgid "Description"
 msgstr "Beschreibung"
@@ -342,50 +284,166 @@ msgid "Paste address"
 msgstr "Adresse einfügen"
 
 #: js/ContactEditDialog.js:491
-msgid "Please paste an address that should be parsed:"
-msgstr "Bitte fügen Sie eine Adresse ein, die eingelesen werden soll."
+msgid "Please paste an address or a URI to a vcard that should be parsed:"
+msgstr "Bitte geben Sie die Address zu einer Vcard (http) ein oder eine Addresse als Text:"
+
+#: js/ContactEditDialog.js:517
+msgid "Failed to parse address!"
+msgstr "Die Adresse konnte nicht verarbeitet werden."
 
-#: js/ContactEditDialog.js:527
+#: js/ContactEditDialog.js:517
+msgid "The address could not be read."
+msgstr "Die Adresse konnte nicht gelesen werden."
+
+#: js/ContactEditDialog.js:530
 msgid "End token mode"
 msgstr "Beende Merkmalsmodus"
 
-#: js/Addressbook.js:22
-msgid "New Contact"
-msgstr "Neuer Kontakt"
-
-#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
-msgid "Addressbook"
-msgid_plural "Addressbooks"
-msgstr[0] "Adressbuch"
-msgstr[1] "Adressbücher"
+#: js/ContactGrid.js:101 js/Model.js:163
+msgid "Type"
+msgstr "Typ"
 
-#: js/ListMemberFilterModel.js:37
-msgid "Member of List"
-msgstr "Gruppenmitglied"
+#: js/ContactGrid.js:102
+msgid "Tags"
+msgstr "Tags"
 
-#: js/Model.js:19
-msgid "Name"
-msgstr "Name"
+#: js/ContactGrid.js:115
+msgid "Full Name"
+msgstr "Vollständiger Name"
 
-#: js/Model.js:34
-msgid "Street (Company Address)"
-msgstr "Straße (Geschäftliche Anschrift)"
+#: js/ContactGrid.js:125
+msgid "Postalcode"
+msgstr "Postleitzahl"
 
-#: js/Model.js:35
-msgid "Street 2 (Company Address)"
-msgstr "Straße 2 (Geschäftliche Anschrift)"
+#: js/ContactGrid.js:127
+msgid "Street (private)"
+msgstr "Straße (privat)"
 
-#: js/Model.js:36
-msgid "City (Company Address)"
-msgstr "Ort (Geschäftliche Anschrift)"
+#: js/ContactGrid.js:128
+msgid "City (private)"
+msgstr "Stadt (privat)"
 
-#: js/Model.js:37
-msgid "Region (Company Address)"
-msgstr "Region (Geschäftliche Anschrift)"
+#: js/ContactGrid.js:129
+msgid "Region (private)"
+msgstr "Region (privat)"
 
-#: js/Model.js:38
-msgid "Postal Code (Company Address)"
-msgstr "Postleitzahl (Geschäftliche Anschrift)"
+#: js/ContactGrid.js:130
+msgid "Postalcode (private)"
+msgstr "Postleitzahl (privat)"
+
+#: js/ContactGrid.js:131
+msgid "Country (private)"
+msgstr "Land (privat)"
+
+#: js/ContactGrid.js:132 Export/Pdf.php:52
+msgid "Email"
+msgstr "E-Mail"
+
+#: js/ContactGrid.js:136
+msgid "Car phone"
+msgstr "Auto-Telefon"
+
+#: js/ContactGrid.js:137
+msgid "Pager"
+msgstr "Pager"
+
+#: js/ContactGrid.js:141
+msgid "Email (private)"
+msgstr "E-Mail (privat)"
+
+#: js/ContactGrid.js:143
+msgid "URL (private)"
+msgstr "Webseite (privat)"
+
+#: js/ContactGrid.js:144
+msgid "Note"
+msgstr "Notizen"
+
+#: js/ContactGrid.js:145
+msgid "Timezone"
+msgstr "Zeitzone"
+
+#: js/ContactGrid.js:146
+msgid "Geo"
+msgstr "Geografischer Ort"
+
+#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
+#, python-brace-format
+msgid "Export {0}"
+msgid_plural "Export {0}"
+msgstr[0] "{0} exportieren"
+msgstr[1] "{0} exportieren"
+
+#: js/ContactGrid.js:168
+msgid "Export as PDF"
+msgstr "Als PDF exportieren"
+
+#: js/ContactGrid.js:175
+msgid "Export as CSV"
+msgstr "Als CSV exportieren"
+
+#: js/ContactGrid.js:182
+msgid "Export as ODS"
+msgstr "Als ODS exportieren"
+
+#: js/ContactGrid.js:189
+msgid "Export as XLS"
+msgstr "Als XLS exportieren"
+
+#: js/ContactGrid.js:196
+msgid "Export as DOC"
+msgstr "Exportieren als DOC"
+
+#: js/ContactGrid.js:203
+msgid "Export as ..."
+msgstr "Exportieren als ..."
+
+#: js/ContactGrid.js:215
+msgid "Import contacts"
+msgstr "Kontakte importieren"
+
+#: js/ContactGrid.js:304
+msgid "Contact of a user account"
+msgstr "Kontakt eines Nutzerkontos"
+
+#: js/ContactGrid.js:304 js/Model.js:95 js/Model.js:134
+#: js/ContactFilterModel.js:35
+msgid "Contact"
+msgid_plural "Contacts"
+msgstr[0] "Kontakt"
+msgstr[1] "Kontakte"
+
+#: js/CardDAVContainerPropertiesHookField.js:35
+msgid "CardDAV URL"
+msgstr "CardDAV URL"
+
+#: js/ListMemberFilterModel.js:37
+msgid "Member of List"
+msgstr "Gruppenmitglied"
+
+#: js/Model.js:19
+msgid "Name"
+msgstr "Name"
+
+#: js/Model.js:34
+msgid "Street (Company Address)"
+msgstr "Straße (Geschäftliche Anschrift)"
+
+#: js/Model.js:35
+msgid "Street 2 (Company Address)"
+msgstr "Straße 2 (Geschäftliche Anschrift)"
+
+#: js/Model.js:36
+msgid "City (Company Address)"
+msgstr "Ort (Geschäftliche Anschrift)"
+
+#: js/Model.js:37
+msgid "Region (Company Address)"
+msgstr "Region (Geschäftliche Anschrift)"
+
+#: js/Model.js:38
+msgid "Postal Code (Company Address)"
+msgstr "Postleitzahl (Geschäftliche Anschrift)"
 
 #: js/Model.js:39
 msgid "Country (Company Address)"
@@ -443,10 +501,6 @@ msgstr "Benutzerkonto"
 msgid "Quick Search"
 msgstr "Schnellsuche"
 
-#: js/Model.js:163 js/ContactGrid.js:101
-msgid "Type"
-msgstr "Typ"
-
 #: js/Model.js:164
 msgid "Last Modified Time"
 msgstr "Zuletzt geändert"
@@ -473,14 +527,6 @@ msgstr[1] "Listen"
 msgid "Lists"
 msgstr "Listen"
 
-#: js/MapPanel.js:58
-msgid "Company address"
-msgstr "Firmenadresse"
-
-#: js/MapPanel.js:65
-msgid "Private address"
-msgstr "Privatadresse"
-
 #: js/ContactGridDetailsPanel.js:65
 msgid "Select contact"
 msgstr "Kontakt auswählen"
@@ -501,108 +547,133 @@ msgstr "Unsicherer Link"
 msgid "Please review this link in edit dialog."
 msgstr "Bitte überprüfen Sie den Link im Detail-Dialog."
 
-#: js/ContactGrid.js:102
-msgid "Tags"
-msgstr "Tags"
+#: js/MapPanel.js:58
+msgid "Company address"
+msgstr "Firmenadresse"
 
-#: js/ContactGrid.js:115
-msgid "Full Name"
-msgstr "Vollständiger Name"
+#: js/MapPanel.js:65
+msgid "Private address"
+msgstr "Privatadresse"
 
-#: js/ContactGrid.js:125
-msgid "Postalcode"
-msgstr "Postleitzahl"
+#: Export/Doc.php:69
+msgid "Dear Mister"
+msgstr "Sehr geehrter Herr"
 
-#: js/ContactGrid.js:127
-msgid "Street (private)"
-msgstr "Straße (privat)"
+#: Export/Doc.php:71
+msgid "Dear Miss"
+msgstr "Sehr geehrte Dame"
 
-#: js/ContactGrid.js:128
-msgid "City (private)"
-msgstr "Stadt (privat)"
+#: Export/Doc.php:73
+msgid "Dear"
+msgstr "Sehr geehrte Damen und Herren"
 
-#: js/ContactGrid.js:129
-msgid "Region (private)"
-msgstr "Region (privat)"
+#: Export/Doc.php:89
+msgid "Mister"
+msgstr "Herr"
 
-#: js/ContactGrid.js:130
-msgid "Postalcode (private)"
-msgstr "Postleitzahl (privat)"
+#: Export/Doc.php:91
+msgid "Misses"
+msgstr "Frau"
 
-#: js/ContactGrid.js:131
-msgid "Country (private)"
-msgstr "Land (privat)"
+#: Export/Pdf.php:37
+msgid "Business Contact Data"
+msgstr "Geschäftliche Kontaktdaten"
 
-#: js/ContactGrid.js:136
-msgid "Car phone"
-msgstr "Auto-Telefon"
+#: Export/Pdf.php:39
+msgid "Organisation / Unit"
+msgstr "Firma / Abteilung"
 
-#: js/ContactGrid.js:137
-msgid "Pager"
+#: Export/Pdf.php:44
+msgid "Business Address"
+msgstr "Geschäftliche Anschrift"
+
+#: Export/Pdf.php:55
+msgid "Telephone Work"
+msgstr "Telefon (Büro)"
+
+#: Export/Pdf.php:58
+msgid "Telephone Cellphone"
+msgstr "Telefon (Mobil)"
+
+#: Export/Pdf.php:61
+msgid "Telephone Car"
+msgstr "Telefon (Auto)"
+
+#: Export/Pdf.php:64
+msgid "Telephone Fax"
+msgstr "Fax"
+
+#: Export/Pdf.php:67
+msgid "Telephone Page"
 msgstr "Pager"
 
-#: js/ContactGrid.js:141
-msgid "Email (private)"
-msgstr "E-Mail (privat)"
+#: Export/Pdf.php:70
+msgid "URL"
+msgstr "URL"
 
-#: js/ContactGrid.js:143
-msgid "URL (private)"
-msgstr "Webseite (privat)"
+#: Export/Pdf.php:73
+msgid "Role"
+msgstr "Funktion"
 
-#: js/ContactGrid.js:144
-msgid "Note"
-msgstr "Notizen"
+#: Export/Pdf.php:79
+msgid "Assistant"
+msgstr "Assistent"
 
-#: js/ContactGrid.js:145
-msgid "Timezone"
-msgstr "Zeitzone"
+#: Export/Pdf.php:82
+msgid "Assistant Telephone"
+msgstr "Telefon des Assistenten"
 
-#: js/ContactGrid.js:146
-msgid "Geo"
-msgstr "Geografischer Ort"
+#: Export/Pdf.php:86
+msgid "Private Contact Data"
+msgstr "Private Kontaktdaten"
 
-#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
-#, python-brace-format
-msgid "Export {0}"
-msgid_plural "Export {0}"
-msgstr[0] "{0} exportieren"
-msgstr[1] "{0} exportieren"
+#: Export/Pdf.php:96
+msgid "Email Home"
+msgstr "E-Mail  (Zuhause)"
 
-#: js/ContactGrid.js:168
-msgid "Export as PDF"
-msgstr "Als PDF exportieren"
+#: Export/Pdf.php:99
+msgid "Telephone Home"
+msgstr "Telefon  (Zuhause)"
 
-#: js/ContactGrid.js:175
-msgid "Export as CSV"
-msgstr "Als CSV exportieren"
+#: Export/Pdf.php:102
+msgid "Telephone Cellphone Private"
+msgstr "Telefon (Mobil)"
 
-#: js/ContactGrid.js:182
-msgid "Export as ODS"
-msgstr "Als ODS exportieren"
+#: Export/Pdf.php:105
+msgid "Telephone Fax Home"
+msgstr "Fax (Zuhause)"
 
-#: js/ContactGrid.js:189
-msgid "Export as XLS"
-msgstr "Als XLS exportieren"
+#: Export/Pdf.php:108
+msgid "URL Home"
+msgstr "URL (Zuhause)"
 
-#: js/ContactGrid.js:196
-msgid "Export as DOC"
-msgstr "Exportieren als DOC"
+#: Export/Pdf.php:112
+msgid "Other Data"
+msgstr "Andere Daten"
 
-#: js/ContactGrid.js:203
-msgid "Export as ..."
-msgstr "Exportieren als ..."
+#: Config.php:45
+msgid "Contact duplicate check fields"
+msgstr "Kontaktfelder für den Dublettencheck"
 
-#: js/ContactGrid.js:215
-msgid "Import contacts"
-msgstr "Kontakte importieren"
+#: Config.php:47
+msgid "These fields are checked when a new contact is created. If a record with the same data in the fields is found, a duplicate exception is thrown."
+msgstr "Diese Felder werden überprüft, wenn ein neuer Kontakt erstellt wird. Falls ein Eintrag mit den gleichen Daten in diesen Feldern angelegt wird, wird eine Fehlermeldung angezeit, auf die der Benutzer reagieren kann."
 
-#: js/ContactGrid.js:304
-msgid "Contact of a user account"
-msgstr "Kontakt eines Nutzerkontos"
+#: Config.php:59
+msgid "Contact salutations available"
+msgstr "Verfügbare Kontaktanreden"
 
-#: Controller/Contact.php:330
-msgid "Uploaded new contact image."
-msgstr "Ein neues Kontaktbild wurde hochgeladen."
+#: Config.php:61
+msgid "Possible contact salutations. Please note that additional values might impact other Addressbook systems on export or syncronisation."
+msgstr "Mögliche Kontaktanreden. Bitte beachten Sie, dass sich zusätzliche Werte beim Export oder der Synchronisation auf andere Systeme auswirken."
+
+#: Config.php:69
+msgid "Parsing rules for addresses"
+msgstr "Analyseregeln für Adressen"
+
+#: Config.php:71
+msgid "Path to a XML file with address parsing rules."
+msgstr "Pfad zu einer XML-Datei mit den Analyseregeln für Adressen."
 
 #: Preference.php:28
 msgid "All contacts"
@@ -644,117 +715,43 @@ msgstr "Hiermit können Sie die zu verwendende XLS-Export Konfiguration einstell
 msgid "default"
 msgstr "Standard"
 
-#: Frontend/Cli.php:166
-msgid ""
-"This contact has been automatically added by the system as an event attender"
-msgstr "Dieser Kontakt wurde automatisch vom System als Termin Teilnehmer hinzugefügt."
-
-#: Frontend/CardDAV/AllContacts.php:41
-msgid "All Contacts"
-msgstr "Alle Kontakte"
-
-#: Config.php:45
-msgid "Contact duplicate check fields"
-msgstr "Kontaktfelder für den Dublettencheck"
-
-#: Config.php:47
-msgid ""
-"These fields are checked when a new contact is created. If a record with the"
-" same data in the fields is found, a duplicate exception is thrown."
-msgstr "Diese Felder werden überprüft, wenn ein neuer Kontakt erstellt wird. Falls ein Eintrag mit den gleichen Daten in diesen Feldern angelegt wird, wird eine Fehlermeldung angezeit, auf die der Benutzer reagieren kann."
-
-#: Config.php:59
-msgid "Contact salutations available"
-msgstr "Verfügbare Kontaktanreden"
-
-#: Config.php:61
-msgid ""
-"Possible contact salutations. Please note that additional values might "
-"impact other Addressbook systems on export or syncronisation."
-msgstr "Mögliche Kontaktanreden. Bitte beachten Sie, dass sich zusätzliche Werte beim Export oder der Synchronisation auf andere Systeme auswirken."
-
-#: Config.php:69
-msgid "Parsing rules for addresses"
-msgstr "Analyseregeln für Adressen"
-
-#: Config.php:71
-msgid "Path to a XML file with address parsing rules."
-msgstr "Pfad zu einer XML-Datei mit den Analyseregeln für Adressen."
+#: Controller.php:105
+#, python-format
+msgid "%s's personal addressbook"
+msgstr "%ss persönliches Adressbuch"
 
 #: Import/definitions/adb_outlook2007_de_import_csv.xml:14
 msgid "CSV Outlook 2007 German"
 msgstr "CSV Outlook 2007 Deutsch"
 
+#: Import/definitions/adb_mac_import_csv.xml:17
+msgid "Contact CSV import from mac address book"
+msgstr "CSV-Kontaktimport aus dem Mac-Adressbuch"
+
 #: Import/definitions/adb_lxoffice_import_csv.xml:11
 #: Import/definitions/adb_tine_import_csv.xml:11
-#: Import/definitions/adb_tine_import_custom_csv.xml:11
 msgid "Contact CSV import"
 msgstr "CSV-Import für Kontakte"
 
+#: Import/definitions/adb_google_import_csv.xml:12
+msgid "Contact import from Google address book"
+msgstr "Kontaktimport aus dem Google-Adressbuch"
+
 #: Import/definitions/adb_tine_import_csv.xml:18
-#: Import/definitions/adb_tine_import_custom_csv.xml:18
 msgid "Import list (###CURRENTDATE###)"
 msgstr "Importliste (###CURRENTDATE###)"
 
 #: Import/definitions/adb_tine_import_csv.xml:20
-#: Import/definitions/adb_tine_import_custom_csv.xml:20
-msgid ""
-"Contacts imported on ###CURRENTDATE### at ###CURRENTTIME### by "
-"###USERFULLNAME###"
+msgid "Contacts imported on ###CURRENTDATE### at ###CURRENTTIME### by ###USERFULLNAME###"
 msgstr "Kontakte importiert am ###CURRENTDATE### um ###CURRENTTIME### von ###USERFULLNAME###"
 
 #: Import/definitions/adb_outlook_import_csv.xml:13
 msgid "Contact CSV import from Outlook address book"
 msgstr "CSV-Kontaktimport aus dem Outlook-Adressbuch"
 
-#: Import/definitions/adb_google_import_csv.xml:12
-msgid "Contact import from Google address book"
-msgstr "Kontaktimport aus dem Google-Adressbuch"
-
-#: Import/definitions/adb_mac_import_csv.xml:17
-msgid "Contact CSV import from mac address book"
-msgstr "CSV-Kontaktimport aus dem Mac-Adressbuch"
-
 #: Import/definitions/adb_import_vcard.xml:14
 msgid "Contact VCARD import"
 msgstr "VCARD-Import für Kontakte"
 
-#: Setup/setup.xml:654
-msgid "Internal Contacts"
-msgstr "Interne Kontakte"
-
-#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
-msgid "Mr"
-msgstr "Herr"
-
-#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
-msgid "Ms"
-msgstr "Frau"
-
-#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
-msgid "All contacts I have read grants for"
-msgstr "Alle Kontakte für die ich Lese-Rechte habe"
-
-#: Setup/Initialize.php:145
-msgid "My company"
-msgstr "Meine Firma"
-
-#: Setup/Initialize.php:146
-msgid "All coworkers in my company"
-msgstr "Alle Mitarbeiter meiner Firma"
-
-#: Setup/Initialize.php:159
-msgid "My contacts"
-msgstr "Meine Kontakte"
-
-#: Setup/Initialize.php:160
-msgid "All contacts in my Addressbooks"
-msgstr "Alle Kontakte in meinen Adressbüchern"
-
-#: Setup/Initialize.php:172
-msgid "Last modified by me"
-msgstr "Zuletzt von mir bearbeitet"
-
-#: Setup/Initialize.php:173
-msgid "All contacts that I have last modified"
-msgstr "Alle Kontakte, die ich zuletzt bearbeitet habe"
+#~ msgid "Please paste an address that should be parsed:"
+#~ msgstr "Bitte fügen Sie eine Adresse ein, die eingelesen werden soll."
index edcf88f..effc0f3 100644 (file)
@@ -13,131 +13,19 @@ msgstr ""
 "X-Poedit-SourceCharset: utf-8\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 
-#: Export/Pdf.php:37
-msgid "Business Contact Data"
-msgstr "Business Contact Data"
-
-#: Export/Pdf.php:39
-msgid "Organisation / Unit"
-msgstr "Organisation / Unit"
-
-#: Export/Pdf.php:44
-msgid "Business Address"
-msgstr "Business Address"
-
-#: Export/Pdf.php:52 js/ContactGrid.js:132
-msgid "Email"
-msgstr "Email"
-
-#: Export/Pdf.php:55
-msgid "Telephone Work"
-msgstr "Telephone Work"
-
-#: Export/Pdf.php:58
-msgid "Telephone Cellphone"
-msgstr "Telephone Cellphone"
-
-#: Export/Pdf.php:61
-msgid "Telephone Car"
-msgstr "Telephone Car"
-
-#: Export/Pdf.php:64
-msgid "Telephone Fax"
-msgstr "Telephone Fax"
-
-#: Export/Pdf.php:67
-msgid "Telephone Page"
-msgstr "Telephone Page"
-
-#: Export/Pdf.php:70
-msgid "URL"
-msgstr "URL"
-
-#: Export/Pdf.php:73
-msgid "Role"
-msgstr "Role"
-
-#: Export/Pdf.php:76 js/ContactEditDialog.js:146 js/Model.js:33
-#: js/ContactGrid.js:121
-msgid "Room"
-msgstr "Room"
-
-#: Export/Pdf.php:79
-msgid "Assistant"
-msgstr "Assistant"
-
-#: Export/Pdf.php:82
-msgid "Assistant Telephone"
-msgstr "Assistant Telephone"
-
-#: Export/Pdf.php:86
-msgid "Private Contact Data"
-msgstr "Private Contact Data"
-
-#: Export/Pdf.php:88 js/ContactEditDialog.js:299 js/Model.js:43
-#: js/Model.js:158 js/Model.js:159 js/Model.js:160 js/Model.js:161
-#: js/Model.js:162
-msgid "Private Address"
-msgstr "Private Address"
-
-#: Export/Pdf.php:96
-msgid "Email Home"
-msgstr "Email Home"
-
-#: Export/Pdf.php:99
-msgid "Telephone Home"
-msgstr "Telephone Home"
-
-#: Export/Pdf.php:102
-msgid "Telephone Cellphone Private"
-msgstr "Telephone Cellphone Private"
-
-#: Export/Pdf.php:105
-msgid "Telephone Fax Home"
-msgstr "Telephone Fax Home"
-
-#: Export/Pdf.php:108
-msgid "URL Home"
-msgstr "URL Home"
-
-#: Export/Pdf.php:112
-msgid "Other Data"
-msgstr "Other Data"
-
-#: Export/Pdf.php:114 js/ContactEditDialog.js:173 js/Model.js:26
-#: js/Model.js:152 js/ContactGrid.js:147
-msgid "Birthday"
-msgstr "Birthday"
-
-#: Export/Pdf.php:117 js/ContactEditDialog.js:167 js/Model.js:30
-#: js/Model.js:147 js/ContactGrid.js:119
-msgid "Job Title"
-msgstr "Job Title"
-
-#: Export/Doc.php:69
-msgid "Dear Mister"
-msgstr "Dear Mister"
-
-#: Export/Doc.php:71
-msgid "Dear Miss"
-msgstr "Dear Miss"
-
-#: Export/Doc.php:73
-msgid "Dear"
-msgstr "Dear"
-
-#: Export/Doc.php:89
-msgid "Mister"
-msgstr "Mister"
+#: Frontend/CardDAV/AllContacts.php:41
+msgid "All Contacts"
+msgstr "All Contacts"
 
-#: Export/Doc.php:91
-msgid "Misses"
-msgstr "Misses"
+#: Frontend/Cli.php:166
+msgid ""
+"This contact has been automatically added by the system as an event attender"
+msgstr ""
+"This contact has been automatically added by the system as an event attender"
 
-#: Controller.php:105
-#, python-format
-msgid "%s's personal addressbook"
-msgstr "%s's personal addressbook"
+#: Controller/Contact.php:330
+msgid "Uploaded new contact image."
+msgstr "Uploaded new contact image."
 
 #: Acl/Rights.php:105
 msgid "manage shared addressbooks"
@@ -155,16 +43,62 @@ msgstr "manage shared addressbook favorites"
 msgid "Create or update shared addressbook favorites"
 msgstr "Create or update shared addressbook favorites"
 
-#: js/CardDAVContainerPropertiesHookField.js:35
-msgid "CardDAV URL"
-msgstr "CardDAV URL"
+#: Setup/setup.xml:654
+msgid "Internal Contacts"
+msgstr "Internal Contacts"
 
-#: js/ContactFilterModel.js:35 js/Model.js:95 js/Model.js:134
-#: js/ContactGrid.js:304
-msgid "Contact"
-msgid_plural "Contacts"
-msgstr[0] "Contact"
-msgstr[1] "Contacts"
+#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
+msgid "Mr"
+msgstr "Mr"
+
+#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
+msgid "Ms"
+msgstr "Ms"
+
+#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
+#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:117 js/Model.js:27
+#: js/Model.js:144 js/ContactGridDetailsPanel.js:131
+msgid "Company"
+msgstr "Company"
+
+#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
+msgid "All contacts I have read grants for"
+msgstr "All contacts I have read grants for"
+
+#: Setup/Initialize.php:145
+msgid "My company"
+msgstr "My company"
+
+#: Setup/Initialize.php:146
+msgid "All coworkers in my company"
+msgstr "All coworkers in my company"
+
+#: Setup/Initialize.php:159
+msgid "My contacts"
+msgstr "My contacts"
+
+#: Setup/Initialize.php:160
+msgid "All contacts in my Addressbooks"
+msgstr "All contacts in my Addressbooks"
+
+#: Setup/Initialize.php:172
+msgid "Last modified by me"
+msgstr "Last modified by me"
+
+#: Setup/Initialize.php:173
+msgid "All contacts that I have last modified"
+msgstr "All contacts that I have last modified"
+
+#: js/Addressbook.js:22
+msgid "New Contact"
+msgstr "New Contact"
+
+#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
+msgid "Addressbook"
+msgid_plural "Addressbooks"
+msgstr[0] "Addressbook"
+msgstr[1] "Addressbooks"
 
 #: js/ContactEditDialog.js:41 js/ContactEditDialog.js:50 js/MapPanel.js:40
 msgid "Map"
@@ -174,40 +108,33 @@ msgstr "Map"
 msgid "Personal Information"
 msgstr "Personal Information"
 
-#: js/ContactEditDialog.js:91 js/Model.js:29 js/ContactGrid.js:103
+#: js/ContactEditDialog.js:91 js/ContactGrid.js:103 js/Model.js:29
 msgid "Salutation"
 msgstr "Salutation"
 
-#: js/ContactEditDialog.js:107 js/Model.js:22 js/Model.js:140
-#: js/ContactGrid.js:111
+#: js/ContactEditDialog.js:107 js/ContactGrid.js:111 js/Model.js:22
+#: js/Model.js:140
 msgid "Title"
 msgstr "Title"
 
-#: js/ContactEditDialog.js:112 js/Model.js:20 js/Model.js:141
-#: js/ContactGrid.js:114
+#: js/ContactEditDialog.js:112 js/ContactGrid.js:114 js/Model.js:20
+#: js/Model.js:141
 msgid "First Name"
 msgstr "First Name"
 
-#: js/ContactEditDialog.js:117 js/Model.js:21 js/Model.js:143
-#: js/ContactGrid.js:112
+#: js/ContactEditDialog.js:117 js/ContactGrid.js:112 js/Model.js:21
+#: js/Model.js:143
 msgid "Middle Name"
 msgstr "Middle Name"
 
 #: js/ContactEditDialog.js:122 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:19 js/Model.js:142
-#: js/ContactGrid.js:113
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:113 js/Model.js:19
+#: js/Model.js:142
 msgid "Last Name"
 msgstr "Last Name"
 
-#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:27 js/Model.js:144
-#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:117
-#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
-msgid "Company"
-msgstr "Company"
-
-#: js/ContactEditDialog.js:133 js/Model.js:28 js/Model.js:145
-#: js/ContactGrid.js:118
+#: js/ContactEditDialog.js:133 js/ContactGrid.js:118 js/Model.js:28
+#: js/Model.js:145
 msgid "Unit"
 msgstr "Unit"
 
@@ -215,46 +142,59 @@ msgstr "Unit"
 msgid "Suffix"
 msgstr "Suffix"
 
-#: js/ContactEditDialog.js:142 js/Model.js:31 js/Model.js:151
-#: js/ContactGrid.js:120
+#: js/ContactEditDialog.js:142 js/ContactGrid.js:120 js/Model.js:31
+#: js/Model.js:151
 msgid "Job Role"
 msgstr "Job Role"
 
-#: js/ContactEditDialog.js:162 js/Model.js:24 js/ContactGrid.js:116
+#: js/ContactEditDialog.js:146 js/ContactGrid.js:121 js/Model.js:33
+#: Export/Pdf.php:76
+msgid "Room"
+msgstr "Room"
+
+#: js/ContactEditDialog.js:162 js/ContactGrid.js:116 js/Model.js:24
 msgid "Display Name"
 msgstr "Display Name"
 
+#: js/ContactEditDialog.js:167 js/ContactGrid.js:119 js/Model.js:30
+#: js/Model.js:147 Export/Pdf.php:117
+msgid "Job Title"
+msgstr "Job Title"
+
+#: js/ContactEditDialog.js:173 js/ContactGrid.js:147 js/Model.js:26
+#: js/Model.js:152 Export/Pdf.php:114
+msgid "Birthday"
+msgstr "Birthday"
+
 #: js/ContactEditDialog.js:181
 msgid "Contact Information"
 msgstr "Contact Information"
 
-#: js/ContactEditDialog.js:186 js/Model.js:51 js/Model.js:146
-#: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
-#: js/ContactGrid.js:133
+#: js/ContactEditDialog.js:186 js/ContactGrid.js:133 js/Model.js:51
+#: js/Model.js:146 js/ContactGridDetailsPanel.js:139
+#: js/ContactGridDetailsPanel.js:163
 msgid "Phone"
 msgstr "Phone"
 
-#: js/ContactEditDialog.js:191 js/Model.js:52
+#: js/ContactEditDialog.js:191 js/ContactGrid.js:134 js/Model.js:52
 #: js/ContactGridDetailsPanel.js:140 js/ContactGridDetailsPanel.js:164
-#: js/ContactGrid.js:134
 msgid "Mobile"
 msgstr "Mobile"
 
-#: js/ContactEditDialog.js:196 js/Model.js:53
+#: js/ContactEditDialog.js:196 js/ContactGrid.js:135 js/Model.js:53
 #: js/ContactGridDetailsPanel.js:141 js/ContactGridDetailsPanel.js:165
-#: js/ContactGrid.js:135
 msgid "Fax"
 msgstr "Fax"
 
-#: js/ContactEditDialog.js:201 js/Model.js:57 js/ContactGrid.js:138
+#: js/ContactEditDialog.js:201 js/ContactGrid.js:138 js/Model.js:57
 msgid "Phone (private)"
 msgstr "Phone (private)"
 
-#: js/ContactEditDialog.js:206 js/Model.js:59 js/ContactGrid.js:140
+#: js/ContactEditDialog.js:206 js/ContactGrid.js:140 js/Model.js:59
 msgid "Mobile (private)"
 msgstr "Mobile (private)"
 
-#: js/ContactEditDialog.js:211 js/Model.js:58 js/ContactGrid.js:139
+#: js/ContactEditDialog.js:211 js/ContactGrid.js:139 js/Model.js:58
 msgid "Fax (private)"
 msgstr "Fax (private)"
 
@@ -267,9 +207,8 @@ msgstr "E-Mail"
 msgid "E-Mail (private)"
 msgstr "E-Mail (private)"
 
-#: js/ContactEditDialog.js:229 js/Model.js:64
+#: js/ContactEditDialog.js:229 js/ContactGrid.js:142 js/Model.js:64
 #: js/ContactGridDetailsPanel.js:144 js/ContactGridDetailsPanel.js:168
-#: js/ContactGrid.js:142
 msgid "Web"
 msgstr "Web"
 
@@ -278,8 +217,8 @@ msgstr "Web"
 msgid "Company Address"
 msgstr "Company Address"
 
-#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302 js/Model.js:153
-#: js/Model.js:158 js/ContactGrid.js:122
+#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302
+#: js/ContactGrid.js:122 js/Model.js:153 js/Model.js:158
 msgid "Street"
 msgstr "Street"
 
@@ -287,8 +226,8 @@ msgstr "Street"
 msgid "Street 2"
 msgstr "Street 2"
 
-#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310 js/Model.js:154
-#: js/Model.js:159 js/ContactGrid.js:124
+#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310
+#: js/ContactGrid.js:124 js/Model.js:154 js/Model.js:159
 msgid "Region"
 msgstr "Region"
 
@@ -297,16 +236,21 @@ msgstr "Region"
 msgid "Postal Code"
 msgstr "Postal Code"
 
-#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318 js/Model.js:156
-#: js/Model.js:161 js/ContactGrid.js:123
+#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318
+#: js/ContactGrid.js:123 js/Model.js:156 js/Model.js:161
 msgid "City"
 msgstr "City"
 
-#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323 js/Model.js:157
-#: js/Model.js:162 js/ContactGrid.js:126
+#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323
+#: js/ContactGrid.js:126 js/Model.js:157 js/Model.js:162
 msgid "Country"
 msgstr "Country"
 
+#: js/ContactEditDialog.js:299 js/Model.js:43 js/Model.js:158 js/Model.js:159
+#: js/Model.js:160 js/Model.js:161 js/Model.js:162 Export/Pdf.php:88
+msgid "Private Address"
+msgstr "Private Address"
+
 #: js/ContactEditDialog.js:344 js/Model.js:68 js/Model.js:148
 msgid "Description"
 msgstr "Description"
@@ -333,50 +277,166 @@ msgid "Paste address"
 msgstr "Paste address"
 
 #: js/ContactEditDialog.js:491
-msgid "Please paste an address that should be parsed:"
-msgstr "Please paste an address that should be parsed:"
+msgid "Please paste an address or a URI to a vcard that should be parsed:"
+msgstr "Please paste an address or a URI to a vcard that should be parsed:"
+
+#: js/ContactEditDialog.js:517
+msgid "Failed to parse address!"
+msgstr "Failed to parse address!"
 
-#: js/ContactEditDialog.js:527
+#: js/ContactEditDialog.js:517
+msgid "The address could not be read."
+msgstr "The address could not be read."
+
+#: js/ContactEditDialog.js:530
 msgid "End token mode"
 msgstr "End token mode"
 
-#: js/Addressbook.js:22
-msgid "New Contact"
-msgstr "New Contact"
+#: js/ContactGrid.js:101 js/Model.js:163
+msgid "Type"
+msgstr "Type"
 
-#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
-msgid "Addressbook"
-msgid_plural "Addressbooks"
-msgstr[0] "Addressbook"
-msgstr[1] "Addressbooks"
+#: js/ContactGrid.js:102
+msgid "Tags"
+msgstr "Tags"
 
-#: js/ListMemberFilterModel.js:37
-msgid "Member of List"
-msgstr "Member of List"
+#: js/ContactGrid.js:115
+msgid "Full Name"
+msgstr "Full Name"
 
-#: js/Model.js:19
-msgid "Name"
-msgstr "Name"
+#: js/ContactGrid.js:125
+msgid "Postalcode"
+msgstr "Postalcode"
 
-#: js/Model.js:34
-msgid "Street (Company Address)"
-msgstr "Street (Company Address)"
+#: js/ContactGrid.js:127
+msgid "Street (private)"
+msgstr "Street (private)"
 
-#: js/Model.js:35
-msgid "Street 2 (Company Address)"
-msgstr "Street 2 (Company Address)"
+#: js/ContactGrid.js:128
+msgid "City (private)"
+msgstr "City (private)"
 
-#: js/Model.js:36
-msgid "City (Company Address)"
-msgstr "City (Company Address)"
+#: js/ContactGrid.js:129
+msgid "Region (private)"
+msgstr "Region (private)"
 
-#: js/Model.js:37
-msgid "Region (Company Address)"
-msgstr "Region (Company Address)"
+#: js/ContactGrid.js:130
+msgid "Postalcode (private)"
+msgstr "Postalcode (private)"
 
-#: js/Model.js:38
-msgid "Postal Code (Company Address)"
-msgstr "Postal Code (Company Address)"
+#: js/ContactGrid.js:131
+msgid "Country (private)"
+msgstr "Country (private)"
+
+#: js/ContactGrid.js:132 Export/Pdf.php:52
+msgid "Email"
+msgstr "Email"
+
+#: js/ContactGrid.js:136
+msgid "Car phone"
+msgstr "Car phone"
+
+#: js/ContactGrid.js:137
+msgid "Pager"
+msgstr "Pager"
+
+#: js/ContactGrid.js:141
+msgid "Email (private)"
+msgstr "Email (private)"
+
+#: js/ContactGrid.js:143
+msgid "URL (private)"
+msgstr "URL (private)"
+
+#: js/ContactGrid.js:144
+msgid "Note"
+msgstr "Note"
+
+#: js/ContactGrid.js:145
+msgid "Timezone"
+msgstr "Timezone"
+
+#: js/ContactGrid.js:146
+msgid "Geo"
+msgstr "Geo"
+
+#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
+#, python-brace-format
+msgid "Export {0}"
+msgid_plural "Export {0}"
+msgstr[0] "Export {0}"
+msgstr[1] "Export {0}"
+
+#: js/ContactGrid.js:168
+msgid "Export as PDF"
+msgstr "Export as PDF"
+
+#: js/ContactGrid.js:175
+msgid "Export as CSV"
+msgstr "Export as CSV"
+
+#: js/ContactGrid.js:182
+msgid "Export as ODS"
+msgstr "Export as ODS"
+
+#: js/ContactGrid.js:189
+msgid "Export as XLS"
+msgstr "Export as XLS"
+
+#: js/ContactGrid.js:196
+msgid "Export as DOC"
+msgstr "Export as DOC"
+
+#: js/ContactGrid.js:203
+msgid "Export as ..."
+msgstr "Export as ..."
+
+#: js/ContactGrid.js:215
+msgid "Import contacts"
+msgstr "Import contacts"
+
+#: js/ContactGrid.js:304
+msgid "Contact of a user account"
+msgstr "Contact of a user account"
+
+#: js/ContactGrid.js:304 js/Model.js:95 js/Model.js:134
+#: js/ContactFilterModel.js:35
+msgid "Contact"
+msgid_plural "Contacts"
+msgstr[0] "Contact"
+msgstr[1] "Contacts"
+
+#: js/CardDAVContainerPropertiesHookField.js:35
+msgid "CardDAV URL"
+msgstr "CardDAV URL"
+
+#: js/ListMemberFilterModel.js:37
+msgid "Member of List"
+msgstr "Member of List"
+
+#: js/Model.js:19
+msgid "Name"
+msgstr "Name"
+
+#: js/Model.js:34
+msgid "Street (Company Address)"
+msgstr "Street (Company Address)"
+
+#: js/Model.js:35
+msgid "Street 2 (Company Address)"
+msgstr "Street 2 (Company Address)"
+
+#: js/Model.js:36
+msgid "City (Company Address)"
+msgstr "City (Company Address)"
+
+#: js/Model.js:37
+msgid "Region (Company Address)"
+msgstr "Region (Company Address)"
+
+#: js/Model.js:38
+msgid "Postal Code (Company Address)"
+msgstr "Postal Code (Company Address)"
 
 #: js/Model.js:39
 msgid "Country (Company Address)"
@@ -434,10 +494,6 @@ msgstr "User Account"
 msgid "Quick Search"
 msgstr "Quick Search"
 
-#: js/Model.js:163 js/ContactGrid.js:101
-msgid "Type"
-msgstr "Type"
-
 #: js/Model.js:164
 msgid "Last Modified Time"
 msgstr "Last Modified Time"
@@ -464,14 +520,6 @@ msgstr[1] "Lists"
 msgid "Lists"
 msgstr "Lists"
 
-#: js/MapPanel.js:58
-msgid "Company address"
-msgstr "Company address"
-
-#: js/MapPanel.js:65
-msgid "Private address"
-msgstr "Private address"
-
 #: js/ContactGridDetailsPanel.js:65
 msgid "Select contact"
 msgstr "Select contact"
@@ -492,108 +540,141 @@ msgstr "Insecure link"
 msgid "Please review this link in edit dialog."
 msgstr "Please review this link in edit dialog."
 
-#: js/ContactGrid.js:102
-msgid "Tags"
-msgstr "Tags"
+#: js/MapPanel.js:58
+msgid "Company address"
+msgstr "Company address"
 
-#: js/ContactGrid.js:115
-msgid "Full Name"
-msgstr "Full Name"
+#: js/MapPanel.js:65
+msgid "Private address"
+msgstr "Private address"
 
-#: js/ContactGrid.js:125
-msgid "Postalcode"
-msgstr "Postalcode"
+#: Export/Doc.php:69
+msgid "Dear Mister"
+msgstr "Dear Mister"
 
-#: js/ContactGrid.js:127
-msgid "Street (private)"
-msgstr "Street (private)"
+#: Export/Doc.php:71
+msgid "Dear Miss"
+msgstr "Dear Miss"
 
-#: js/ContactGrid.js:128
-msgid "City (private)"
-msgstr "City (private)"
+#: Export/Doc.php:73
+msgid "Dear"
+msgstr "Dear"
 
-#: js/ContactGrid.js:129
-msgid "Region (private)"
-msgstr "Region (private)"
+#: Export/Doc.php:89
+msgid "Mister"
+msgstr "Mister"
 
-#: js/ContactGrid.js:130
-msgid "Postalcode (private)"
-msgstr "Postalcode (private)"
+#: Export/Doc.php:91
+msgid "Misses"
+msgstr "Misses"
 
-#: js/ContactGrid.js:131
-msgid "Country (private)"
-msgstr "Country (private)"
+#: Export/Pdf.php:37
+msgid "Business Contact Data"
+msgstr "Business Contact Data"
 
-#: js/ContactGrid.js:136
-msgid "Car phone"
-msgstr "Car phone"
+#: Export/Pdf.php:39
+msgid "Organisation / Unit"
+msgstr "Organisation / Unit"
 
-#: js/ContactGrid.js:137
-msgid "Pager"
-msgstr "Pager"
+#: Export/Pdf.php:44
+msgid "Business Address"
+msgstr "Business Address"
 
-#: js/ContactGrid.js:141
-msgid "Email (private)"
-msgstr "Email (private)"
+#: Export/Pdf.php:55
+msgid "Telephone Work"
+msgstr "Telephone Work"
 
-#: js/ContactGrid.js:143
-msgid "URL (private)"
-msgstr "URL (private)"
+#: Export/Pdf.php:58
+msgid "Telephone Cellphone"
+msgstr "Telephone Cellphone"
 
-#: js/ContactGrid.js:144
-msgid "Note"
-msgstr "Note"
+#: Export/Pdf.php:61
+msgid "Telephone Car"
+msgstr "Telephone Car"
 
-#: js/ContactGrid.js:145
-msgid "Timezone"
-msgstr "Timezone"
+#: Export/Pdf.php:64
+msgid "Telephone Fax"
+msgstr "Telephone Fax"
 
-#: js/ContactGrid.js:146
-msgid "Geo"
-msgstr "Geo"
+#: Export/Pdf.php:67
+msgid "Telephone Page"
+msgstr "Telephone Page"
 
-#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
-#, python-brace-format
-msgid "Export {0}"
-msgid_plural "Export {0}"
-msgstr[0] "Export {0}"
-msgstr[1] "Export {0}"
+#: Export/Pdf.php:70
+msgid "URL"
+msgstr "URL"
 
-#: js/ContactGrid.js:168
-msgid "Export as PDF"
-msgstr "Export as PDF"
+#: Export/Pdf.php:73
+msgid "Role"
+msgstr "Role"
 
-#: js/ContactGrid.js:175
-msgid "Export as CSV"
-msgstr "Export as CSV"
+#: Export/Pdf.php:79
+msgid "Assistant"
+msgstr "Assistant"
 
-#: js/ContactGrid.js:182
-msgid "Export as ODS"
-msgstr "Export as ODS"
+#: Export/Pdf.php:82
+msgid "Assistant Telephone"
+msgstr "Assistant Telephone"
 
-#: js/ContactGrid.js:189
-msgid "Export as XLS"
-msgstr "Export as XLS"
+#: Export/Pdf.php:86
+msgid "Private Contact Data"
+msgstr "Private Contact Data"
 
-#: js/ContactGrid.js:196
-msgid "Export as DOC"
-msgstr "Export as DOC"
+#: Export/Pdf.php:96
+msgid "Email Home"
+msgstr "Email Home"
 
-#: js/ContactGrid.js:203
-msgid "Export as ..."
-msgstr "Export as ..."
+#: Export/Pdf.php:99
+msgid "Telephone Home"
+msgstr "Telephone Home"
 
-#: js/ContactGrid.js:215
-msgid "Import contacts"
-msgstr "Import contacts"
+#: Export/Pdf.php:102
+msgid "Telephone Cellphone Private"
+msgstr "Telephone Cellphone Private"
 
-#: js/ContactGrid.js:304
-msgid "Contact of a user account"
-msgstr "Contact of a user account"
+#: Export/Pdf.php:105
+msgid "Telephone Fax Home"
+msgstr "Telephone Fax Home"
 
-#: Controller/Contact.php:330
-msgid "Uploaded new contact image."
-msgstr "Uploaded new contact image."
+#: Export/Pdf.php:108
+msgid "URL Home"
+msgstr "URL Home"
+
+#: Export/Pdf.php:112
+msgid "Other Data"
+msgstr "Other Data"
+
+#: Config.php:45
+msgid "Contact duplicate check fields"
+msgstr "Contact duplicate check fields"
+
+#: Config.php:47
+msgid ""
+"These fields are checked when a new contact is created. If a record with the "
+"same data in the fields is found, a duplicate exception is thrown."
+msgstr ""
+"These fields are checked when a new contact is created. If a record with the "
+"same data in the fields is found, a duplicate exception is thrown."
+
+#: Config.php:59
+msgid "Contact salutations available"
+msgstr "Contact salutations available"
+
+#: Config.php:61
+msgid ""
+"Possible contact salutations. Please note that additional values might "
+"impact other Addressbook systems on export or syncronisation."
+msgstr ""
+"Possible contact salutations. Please note that additional values might "
+"impact other Addressbook systems on export or syncronisation."
+
+#: Config.php:69
+msgid "Parsing rules for addresses"
+msgstr "Parsing rules for addresses"
+
+#: Config.php:71
+msgid "Path to a XML file with address parsing rules."
+msgstr "Path to a XML file with address parsing rules."
 
 #: Preference.php:28
 msgid "All contacts"
@@ -635,51 +716,28 @@ msgstr "Use this configuration for the contact XLS export."
 msgid "default"
 msgstr "default"
 
-#: Frontend/CardDAV/AllContacts.php:41
-msgid "All Contacts"
-msgstr "All Contacts"
-
-#: Config.php:45
-msgid "Contact duplicate check fields"
-msgstr "Contact duplicate check fields"
-
-#: Config.php:47
-msgid ""
-"These fields are checked when a new contact is created. If a record with the "
-"same data in the fields is found, a duplicate exception is thrown."
-msgstr ""
-"These fields are checked when a new contact is created. If a record with the "
-"same data in the fields is found, a duplicate exception is thrown."
-
-#: Config.php:59
-msgid "Contact salutations available"
-msgstr "Contact salutations available"
-
-#: Config.php:61
-msgid ""
-"Possible contact salutations. Please note that additional values might "
-"impact other Addressbook systems on export or syncronisation."
-msgstr ""
-"Possible contact salutations. Please note that additional values might "
-"impact other Addressbook systems on export or syncronisation."
-
-#: Config.php:69
-msgid "Parsing rules for addresses"
-msgstr "Parsing rules for addresses"
-
-#: Config.php:71
-msgid "Path to a XML file with address parsing rules."
-msgstr "Path to a XML file with address parsing rules."
+#: Controller.php:105
+#, python-format
+msgid "%s's personal addressbook"
+msgstr "%s's personal addressbook"
 
 #: Import/definitions/adb_outlook2007_de_import_csv.xml:14
 msgid "CSV Outlook 2007 German"
 msgstr "CSV Outlook 2007 German"
 
+#: Import/definitions/adb_mac_import_csv.xml:17
+msgid "Contact CSV import from mac address book"
+msgstr "Contact CSV import from mac address book"
+
 #: Import/definitions/adb_lxoffice_import_csv.xml:11
 #: Import/definitions/adb_tine_import_csv.xml:11
 msgid "Contact CSV import"
 msgstr "Contact CSV import"
 
+#: Import/definitions/adb_google_import_csv.xml:12
+msgid "Contact import from Google address book"
+msgstr "Contact import from Google address book"
+
 #: Import/definitions/adb_tine_import_csv.xml:18
 msgid "Import list (###CURRENTDATE###)"
 msgstr "Import list (###CURRENTDATE###)"
@@ -696,54 +754,6 @@ msgstr ""
 msgid "Contact CSV import from Outlook address book"
 msgstr "Contact CSV import from Outlook address book"
 
-#: Import/definitions/adb_google_import_csv.xml:12
-msgid "Contact import from Google address book"
-msgstr "Contact import from Google address book"
-
-#: Import/definitions/adb_mac_import_csv.xml:17
-msgid "Contact CSV import from mac address book"
-msgstr "Contact CSV import from mac address book"
-
 #: Import/definitions/adb_import_vcard.xml:14
 msgid "Contact VCARD import"
 msgstr "Contact VCARD import"
-
-#: Setup/setup.xml:654
-msgid "Internal Contacts"
-msgstr "Internal Contacts"
-
-#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
-msgid "Mr"
-msgstr "Mr"
-
-#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
-msgid "Ms"
-msgstr "Ms"
-
-#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
-msgid "All contacts I have read grants for"
-msgstr "All contacts I have read grants for"
-
-#: Setup/Initialize.php:145
-msgid "My company"
-msgstr "My company"
-
-#: Setup/Initialize.php:146
-msgid "All coworkers in my company"
-msgstr "All coworkers in my company"
-
-#: Setup/Initialize.php:159
-msgid "My contacts"
-msgstr "My contacts"
-
-#: Setup/Initialize.php:160
-msgid "All contacts in my Addressbooks"
-msgstr "All contacts in my Addressbooks"
-
-#: Setup/Initialize.php:172
-msgid "Last modified by me"
-msgstr "Last modified by me"
-
-#: Setup/Initialize.php:173
-msgid "All contacts that I have last modified"
-msgstr "All contacts that I have last modified"
index ca1ef7c..446f88a 100644 (file)
@@ -13,156 +13,89 @@ msgstr ""
 "X-Poedit-SourceCharset: utf-8\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 
-#: Export/Pdf.php:37
-msgid "Business Contact Data"
-msgstr ""
-
-#: Export/Pdf.php:39
-msgid "Organisation / Unit"
-msgstr ""
-
-#: Export/Pdf.php:44
-msgid "Business Address"
-msgstr ""
-
-#: Export/Pdf.php:52 js/ContactGrid.js:132
-msgid "Email"
-msgstr ""
-
-#: Export/Pdf.php:55
-msgid "Telephone Work"
-msgstr ""
-
-#: Export/Pdf.php:58
-msgid "Telephone Cellphone"
-msgstr ""
-
-#: Export/Pdf.php:61
-msgid "Telephone Car"
-msgstr ""
-
-#: Export/Pdf.php:64
-msgid "Telephone Fax"
-msgstr ""
-
-#: Export/Pdf.php:67
-msgid "Telephone Page"
-msgstr ""
-
-#: Export/Pdf.php:70
-msgid "URL"
-msgstr ""
-
-#: Export/Pdf.php:73
-msgid "Role"
-msgstr ""
-
-#: Export/Pdf.php:76 js/ContactEditDialog.js:146 js/Model.js:33
-#: js/ContactGrid.js:121
-msgid "Room"
-msgstr ""
-
-#: Export/Pdf.php:79
-msgid "Assistant"
-msgstr ""
-
-#: Export/Pdf.php:82
-msgid "Assistant Telephone"
-msgstr ""
-
-#: Export/Pdf.php:86
-msgid "Private Contact Data"
-msgstr ""
-
-#: Export/Pdf.php:88 js/ContactEditDialog.js:299 js/Model.js:43
-#: js/Model.js:158 js/Model.js:159 js/Model.js:160 js/Model.js:161
-#: js/Model.js:162
-msgid "Private Address"
-msgstr ""
-
-#: Export/Pdf.php:96
-msgid "Email Home"
+#: Frontend/CardDAV/AllContacts.php:41
+msgid "All Contacts"
 msgstr ""
 
-#: Export/Pdf.php:99
-msgid "Telephone Home"
+#: Frontend/Cli.php:166
+msgid ""
+"This contact has been automatically added by the system as an event attender"
 msgstr ""
 
-#: Export/Pdf.php:102
-msgid "Telephone Cellphone Private"
+#: Controller/Contact.php:330
+msgid "Uploaded new contact image."
 msgstr ""
 
-#: Export/Pdf.php:105
-msgid "Telephone Fax Home"
+#: Acl/Rights.php:105
+msgid "manage shared addressbooks"
 msgstr ""
 
-#: Export/Pdf.php:108
-msgid "URL Home"
+#: Acl/Rights.php:106
+msgid "Create new shared addressbook folders"
 msgstr ""
 
-#: Export/Pdf.php:112
-msgid "Other Data"
+#: Acl/Rights.php:109
+msgid "manage shared addressbook favorites"
 msgstr ""
 
-#: Export/Pdf.php:114 js/ContactEditDialog.js:173 js/Model.js:26
-#: js/Model.js:152 js/ContactGrid.js:147
-msgid "Birthday"
+#: Acl/Rights.php:110
+msgid "Create or update shared addressbook favorites"
 msgstr ""
 
-#: Export/Pdf.php:117 js/ContactEditDialog.js:167 js/Model.js:30
-#: js/Model.js:147 js/ContactGrid.js:119
-msgid "Job Title"
+#: Setup/setup.xml:654
+msgid "Internal Contacts"
 msgstr ""
 
-#: Export/Doc.php:69
-msgid "Dear Mister"
+#: Setup/Initialize.php:68 Setup/Update/Release5.php:194
+msgid "Mr"
 msgstr ""
 
-#: Export/Doc.php:71
-msgid "Dear Miss"
+#: Setup/Initialize.php:69 Setup/Update/Release5.php:195
+msgid "Ms"
 msgstr ""
 
-#: Export/Doc.php:73
-msgid "Dear"
+#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
+#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:117 js/Model.js:27
+#: js/Model.js:144 js/ContactGridDetailsPanel.js:131
+msgid "Company"
 msgstr ""
 
-#: Export/Doc.php:89
-msgid "Mister"
+#: Setup/Initialize.php:140 Setup/Update/Release3.php:37
+msgid "All contacts I have read grants for"
 msgstr ""
 
-#: Export/Doc.php:91
-msgid "Misses"
+#: Setup/Initialize.php:145
+msgid "My company"
 msgstr ""
 
-#: Controller.php:105
-#, python-format
-msgid "%s's personal addressbook"
+#: Setup/Initialize.php:146
+msgid "All coworkers in my company"
 msgstr ""
 
-#: Acl/Rights.php:105
-msgid "manage shared addressbooks"
+#: Setup/Initialize.php:159
+msgid "My contacts"
 msgstr ""
 
-#: Acl/Rights.php:106
-msgid "Create new shared addressbook folders"
+#: Setup/Initialize.php:160
+msgid "All contacts in my Addressbooks"
 msgstr ""
 
-#: Acl/Rights.php:109
-msgid "manage shared addressbook favorites"
+#: Setup/Initialize.php:172
+msgid "Last modified by me"
 msgstr ""
 
-#: Acl/Rights.php:110
-msgid "Create or update shared addressbook favorites"
+#: Setup/Initialize.php:173
+msgid "All contacts that I have last modified"
 msgstr ""
 
-#: js/CardDAVContainerPropertiesHookField.js:35
-msgid "CardDAV URL"
+#: js/Addressbook.js:22
+msgid "New Contact"
 msgstr ""
 
-#: js/ContactFilterModel.js:35 js/Model.js:95 js/Model.js:134
-#: js/ContactGrid.js:304
-msgid "Contact"
-msgid_plural "Contacts"
+#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
+msgid "Addressbook"
+msgid_plural "Addressbooks"
 msgstr[0] ""
 msgstr[1] ""
 
@@ -174,40 +107,33 @@ msgstr ""
 msgid "Personal Information"
 msgstr ""
 
-#: js/ContactEditDialog.js:91 js/Model.js:29 js/ContactGrid.js:103
+#: js/ContactEditDialog.js:91 js/ContactGrid.js:103 js/Model.js:29
 msgid "Salutation"
 msgstr ""
 
-#: js/ContactEditDialog.js:107 js/Model.js:22 js/Model.js:140
-#: js/ContactGrid.js:111
+#: js/ContactEditDialog.js:107 js/ContactGrid.js:111 js/Model.js:22
+#: js/Model.js:140
 msgid "Title"
 msgstr ""
 
-#: js/ContactEditDialog.js:112 js/Model.js:20 js/Model.js:141
-#: js/ContactGrid.js:114
+#: js/ContactEditDialog.js:112 js/ContactGrid.js:114 js/Model.js:20
+#: js/Model.js:141
 msgid "First Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:117 js/Model.js:21 js/Model.js:143
-#: js/ContactGrid.js:112
+#: js/ContactEditDialog.js:117 js/ContactGrid.js:112 js/Model.js:21
+#: js/Model.js:143
 msgid "Middle Name"
 msgstr ""
 
 #: js/ContactEditDialog.js:122 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:19 js/Model.js:142
-#: js/ContactGrid.js:113
+#: js/ContactEditDialog.js:456 js/ContactGrid.js:113 js/Model.js:19
+#: js/Model.js:142
 msgid "Last Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:128 js/ContactEditDialog.js:434
-#: js/ContactEditDialog.js:456 js/Model.js:27 js/Model.js:144
-#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:117
-#: Setup/Initialize.php:70 Setup/Update/Release5.php:196
-msgid "Company"
-msgstr ""
-
-#: js/ContactEditDialog.js:133 js/Model.js:28 js/Model.js:145
-#: js/ContactGrid.js:118
+#: js/ContactEditDialog.js:133 js/ContactGrid.js:118 js/Model.js:28
+#: js/Model.js:145
 msgid "Unit"
 msgstr ""
 
@@ -215,46 +141,59 @@ msgstr ""
 msgid "Suffix"
 msgstr ""
 
-#: js/ContactEditDialog.js:142 js/Model.js:31 js/Model.js:151
-#: js/ContactGrid.js:120
+#: js/ContactEditDialog.js:142 js/ContactGrid.js:120 js/Model.js:31
+#: js/Model.js:151
 msgid "Job Role"
 msgstr ""
 
-#: js/ContactEditDialog.js:162 js/Model.js:24 js/ContactGrid.js:116
+#: js/ContactEditDialog.js:146 js/ContactGrid.js:121 js/Model.js:33
+#: Export/Pdf.php:76
+msgid "Room"
+msgstr ""
+
+#: js/ContactEditDialog.js:162 js/ContactGrid.js:116 js/Model.js:24
 msgid "Display Name"
 msgstr ""
 
+#: js/ContactEditDialog.js:167 js/ContactGrid.js:119 js/Model.js:30
+#: js/Model.js:147 Export/Pdf.php:117
+msgid "Job Title"
+msgstr ""
+
+#: js/ContactEditDialog.js:173 js/ContactGrid.js:147 js/Model.js:26
+#: js/Model.js:152 Export/Pdf.php:114
+msgid "Birthday"
+msgstr ""
+
 #: js/ContactEditDialog.js:181
 msgid "Contact Information"
 msgstr ""
 
-#: js/ContactEditDialog.js:186 js/Model.js:51 js/Model.js:146
-#: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
-#: js/ContactGrid.js:133
+#: js/ContactEditDialog.js:186 js/ContactGrid.js:133 js/Model.js:51
+#: js/Model.js:146 js/ContactGridDetailsPanel.js:139
+#: js/ContactGridDetailsPanel.js:163
 msgid "Phone"
 msgstr ""
 
-#: js/ContactEditDialog.js:191 js/Model.js:52
+#: js/ContactEditDialog.js:191 js/ContactGrid.js:134 js/Model.js:52
 #: js/ContactGridDetailsPanel.js:140 js/ContactGridDetailsPanel.js:164
-#: js/ContactGrid.js:134
 msgid "Mobile"
 msgstr ""
 
-#: js/ContactEditDialog.js:196 js/Model.js:53
+#: js/ContactEditDialog.js:196 js/ContactGrid.js:135 js/Model.js:53
 #: js/ContactGridDetailsPanel.js:141 js/ContactGridDetailsPanel.js:165
-#: js/ContactGrid.js:135
 msgid "Fax"
 msgstr ""
 
-#: js/ContactEditDialog.js:201 js/Model.js:57 js/ContactGrid.js:138
+#: js/ContactEditDialog.js:201 js/ContactGrid.js:138 js/Model.js:57
 msgid "Phone (private)"
 msgstr ""
 
-#: js/ContactEditDialog.js:206 js/Model.js:59 js/ContactGrid.js:140
+#: js/ContactEditDialog.js:206 js/ContactGrid.js:140 js/Model.js:59
 msgid "Mobile (private)"
 msgstr ""
 
-#: js/ContactEditDialog.js:211 js/Model.js:58 js/ContactGrid.js:139
+#: js/ContactEditDialog.js:211 js/ContactGrid.js:139 js/Model.js:58
 msgid "Fax (private)"
 msgstr ""
 
@@ -267,9 +206,8 @@ msgstr ""
 msgid "E-Mail (private)"
 msgstr ""
 
-#: js/ContactEditDialog.js:229 js/Model.js:64
+#: js/ContactEditDialog.js:229 js/ContactGrid.js:142 js/Model.js:64
 #: js/ContactGridDetailsPanel.js:144 js/ContactGridDetailsPanel.js:168
-#: js/ContactGrid.js:142
 msgid "Web"
 msgstr ""
 
@@ -278,8 +216,8 @@ msgstr ""
 msgid "Company Address"
 msgstr ""
 
-#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302 js/Model.js:153
-#: js/Model.js:158 js/ContactGrid.js:122
+#: js/ContactEditDialog.js:273 js/ContactEditDialog.js:302
+#: js/ContactGrid.js:122 js/Model.js:153 js/Model.js:158
 msgid "Street"
 msgstr ""
 
@@ -287,8 +225,8 @@ msgstr ""
 msgid "Street 2"
 msgstr ""
 
-#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310 js/Model.js:154
-#: js/Model.js:159 js/ContactGrid.js:124
+#: js/ContactEditDialog.js:281 js/ContactEditDialog.js:310
+#: js/ContactGrid.js:124 js/Model.js:154 js/Model.js:159
 msgid "Region"
 msgstr ""
 
@@ -297,16 +235,21 @@ msgstr ""
 msgid "Postal Code"
 msgstr ""
 
-#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318 js/Model.js:156
-#: js/Model.js:161 js/ContactGrid.js:123
+#: js/ContactEditDialog.js:289 js/ContactEditDialog.js:318
+#: js/ContactGrid.js:123 js/Model.js:156 js/Model.js:161
 msgid "City"
 msgstr ""
 
-#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323 js/Model.js:157
-#: js/Model.js:162 js/ContactGrid.js:126
+#: js/ContactEditDialog.js:294 js/ContactEditDialog.js:323
+#: js/ContactGrid.js:126 js/Model.js:157 js/Model.js:162
 msgid "Country"
 msgstr ""
 
+#: js/ContactEditDialog.js:299 js/Model.js:43 js/Model.js:158 js/Model.js:159
+#: js/Model.js:160 js/Model.js:161 js/Model.js:162 Export/Pdf.php:88
+msgid "Private Address"
+msgstr ""
+
 #: js/ContactEditDialog.js:344 js/Model.js:68 js/Model.js:148
 msgid "Description"
 msgstr ""
@@ -333,23 +276,139 @@ msgid "Paste address"
 msgstr ""
 
 #: js/ContactEditDialog.js:491
-msgid "Please paste an address that should be parsed:"
+msgid "Please paste an address or a URI to a vcard that should be parsed:"
+msgstr ""
+
+#: js/ContactEditDialog.js:517
+msgid "Failed to parse address!"
+msgstr ""
+
+#: js/ContactEditDialog.js:517
+msgid "The address could not be read."
 msgstr ""
 
-#: js/ContactEditDialog.js:527
+#: js/ContactEditDialog.js:530
 msgid "End token mode"
 msgstr ""
 
-#: js/Addressbook.js:22
-msgid "New Contact"
+#: js/ContactGrid.js:101 js/Model.js:163
+msgid "Type"
+msgstr ""
+
+#: js/ContactGrid.js:102
+msgid "Tags"
+msgstr ""
+
+#: js/ContactGrid.js:115
+msgid "Full Name"
+msgstr ""
+
+#: js/ContactGrid.js:125
+msgid "Postalcode"
+msgstr ""
+
+#: js/ContactGrid.js:127
+msgid "Street (private)"
+msgstr ""
+
+#: js/ContactGrid.js:128
+msgid "City (private)"
+msgstr ""
+
+#: js/ContactGrid.js:129
+msgid "Region (private)"
+msgstr ""
+
+#: js/ContactGrid.js:130
+msgid "Postalcode (private)"
+msgstr ""
+
+#: js/ContactGrid.js:131
+msgid "Country (private)"
+msgstr ""
+
+#: js/ContactGrid.js:132 Export/Pdf.php:52
+msgid "Email"
+msgstr ""
+
+#: js/ContactGrid.js:136
+msgid "Car phone"
+msgstr ""
+
+#: js/ContactGrid.js:137
+msgid "Pager"
+msgstr ""
+
+#: js/ContactGrid.js:141
+msgid "Email (private)"
+msgstr ""
+
+#: js/ContactGrid.js:143
+msgid "URL (private)"
+msgstr ""
+
+#: js/ContactGrid.js:144
+msgid "Note"
+msgstr ""
+
+#: js/ContactGrid.js:145
+msgid "Timezone"
+msgstr ""
+
+#: js/ContactGrid.js:146
+msgid "Geo"
+msgstr ""
+
+#: js/ContactGrid.js:157 js/ContactGrid.js:158 js/ContactGrid.js:159
+#, python-brace-format
+msgid "Export {0}"
+msgid_plural "Export {0}"
+msgstr[0] ""
+msgstr[1] ""
+
+#: js/ContactGrid.js:168
+msgid "Export as PDF"
+msgstr ""
+
+#: js/ContactGrid.js:175
+msgid "Export as CSV"
+msgstr ""
+
+#: js/ContactGrid.js:182
+msgid "Export as ODS"
+msgstr ""
+
+#: js/ContactGrid.js:189
+msgid "Export as XLS"
+msgstr ""
+
+#: js/ContactGrid.js:196
+msgid "Export as DOC"
+msgstr ""
+
+#: js/ContactGrid.js:203
+msgid "Export as ..."
+msgstr ""
+
+#: js/ContactGrid.js:215
+msgid "Import contacts"
+msgstr ""
+
+#: js/ContactGrid.js:304
+msgid "Contact of a user account"
 msgstr ""
 
-#: js/Addressbook.js:32 js/Model.js:99 js/Model.js:208
-msgid "Addressbook"
-msgid_plural "Addressbooks"
+#: js/ContactGrid.js:304 js/Model.js:95 js/Model.js:134
+#: js/ContactFilterModel.js:35
+msgid "Contact"
+msgid_plural "Contacts"
 msgstr[0] ""
 msgstr[1] ""
 
+#: js/CardDAVContainerPropertiesHookField.js:35
+msgid "CardDAV URL"
+msgstr ""
+
 #: js/ListMemberFilterModel.js:37
 msgid "Member of List"
 msgstr ""
@@