Merge branch '2016.11-develop' into 2017.02 2017.02
authorPhilipp Schüle <p.schuele@metaways.de>
Wed, 2 Aug 2017 07:57:09 +0000 (09:57 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 2 Aug 2017 07:57:09 +0000 (09:57 +0200)
Change-Id: I2777aae751aae52bf381399950e07c52fba7b9a1

55 files changed:
README.md
tests/tine20/Inventory/Import/CsvTest.php
tests/tine20/Timetracker/ControllerTest.php
tests/tine20/Tinebase/ApplicationTest.php
tine20/Addressbook/Controller/Contact.php
tine20/Addressbook/js/ContactGrid.js
tine20/Addressbook/translations/template.pot
tine20/Admin/Frontend/Cli.php
tine20/Admin/Setup/Update/Release9.php
tine20/CREDITS
tine20/Calendar/Config.php
tine20/Calendar/Convert/Event/VCalendar/BusyCal.php [new file with mode: 0644]
tine20/Calendar/Convert/Event/VCalendar/Factory.php
tine20/Calendar/Frontend/WebDAV/Container.php
tine20/CoreData/Setup/Update/Release0.php [new file with mode: 0644]
tine20/Felamimail/Config.php
tine20/Felamimail/Controller/Message/Send.php
tine20/Tasks/Convert/Task/VCalendar/DavDroid.php [new file with mode: 0644]
tine20/Tasks/Convert/Task/VCalendar/Factory.php
tine20/Timetracker/Acl/Rights.php
tine20/Timetracker/Backend/TimeaccountFavorites.php [new file with mode: 0644]
tine20/Timetracker/Config.php [new file with mode: 0644]
tine20/Timetracker/Controller.php
tine20/Timetracker/Controller/TimeaccountFavorites.php [new file with mode: 0644]
tine20/Timetracker/Export/definitions/ts_default_ods.xml
tine20/Timetracker/Frontend/Json.php
tine20/Timetracker/Model/TimeaccountFavorite.php [new file with mode: 0644]
tine20/Timetracker/Model/TimeaccountFavoriteFilter.php [new file with mode: 0644]
tine20/Timetracker/Model/TimeaccountGrants.php
tine20/Timetracker/Preference.php
tine20/Timetracker/Setup/Update/Release10.php
tine20/Timetracker/Setup/setup.xml
tine20/Timetracker/Timetracker.jsb2
tine20/Timetracker/js/TimeaccountFavoritesPanel.js [new file with mode: 0644]
tine20/Timetracker/js/TimeaccountGridPanel.js
tine20/Timetracker/js/TimeaccountWestPanel.js [new file with mode: 0644]
tine20/Timetracker/js/TimesheetEditDialog.js
tine20/Timetracker/js/TimesheetGridPanel.js
tine20/Timetracker/js/TimesheetWestPanel.js [new file with mode: 0644]
tine20/Timetracker/js/Timetracker.js
tine20/Tinebase/Config.php
tine20/Tinebase/Container.php
tine20/Tinebase/Frontend/Cli.php
tine20/Tinebase/Preference/Abstract.php
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/Server/WebDAV.php
tine20/Tinebase/Timemachine/Abstract.php
tine20/Tinebase/WebDav/Container/Abstract.php
tine20/Tinebase/css/widgets/PreviewPanel.css
tine20/Tinebase/js/LoginPanel.js
tine20/Tinebase/js/tineInit.js
tine20/Tinebase/js/widgets/mainscreen/WestPanel.js
tine20/Tinebase/js/widgets/tags/TagsPanel.js
tine20/composer.json
tine20/composer.lock

index db45790..3004e34 100644 (file)
--- a/README.md
+++ b/README.md
@@ -3,12 +3,13 @@
 Welcome to the Tine 2.0 Community Edition, the base of our popular [Tine 2.0 Business Edition](http://www.tine20.com).
 
 ## Official Community ressources
+* [Github Page](https://tine20.github.io/Tine-2.0-Open-Source-Groupware-and-CRM/)
 * [Documentation Wiki](https://www.tine20.org/wiki/)
 * [Issue tracker](https://forge.tine20.org/mantisbt/)
 * [Forum](https://www.tine20.org/forum/)
 * [Package Downloads](https://github.com/tine20/Tine-2.0-Open-Source-Groupware-and-CRM/releases)
 * [Twitter](https://twitter.com/tine20org)
-* [Riot/Matrix Chat](https://riot.im/app/#/room/#tine20:matrix.org)
+* [Matrix chat room #tine20:matrix.org](https://riot.im/app/#/room/#tine20:matrix.org)
 
 ## Free Software without compromise
 In contrast to the so called "open core" approach, where only a subset of the software is released as open source, 
@@ -49,7 +50,6 @@ a new major release of our business edition.
 Each [Tine 2.0 Business Edition](http://www.tine20.com) is maintained with security patches and bug fixes for at least two 
 years. Our [partners](https://www.tine20.com/partner/) offer a wide range of commercial support for different business needs.
 
-
 ## Licenses, Copyrights and Trademarks 
 Tine 2.0 - this community edition as well as the business edition are released under the terms of the AGPLv3 License mainly.
 
@@ -60,7 +60,6 @@ all Tine 2.0 specific source code it's possible to offer it with different licen
 The name "Tine 2.0" is our registered trademark. This is for your and our safety. We can use the name and logo without
 legal fraught from other parties.
 
-
 ## Contributors Guide
 To start developing with tine20 and GIT please visit
-* [Tine2.0 Contributors Guide](https://www.tine20.org/wiki/index.php/Developers/Getting_Started/Contributors_Guide)
\ No newline at end of file
+* [Tine2.0 Contributors Guide](https://www.tine20.org/wiki/index.php/Developers/Getting_Started/Contributors_Guide)
index 55eed14..46fa982 100644 (file)
@@ -83,6 +83,8 @@ class Inventory_Import_CsvTest extends PHPUnit_Framework_TestCase
      */
     public function testImportOfCSVWithHook ()
     {
+        $this->markTestSkipped('FIXME: repair this test - it fails on nightly build about 80% of the time');
+
         $filename = dirname(__FILE__) . '/files/inv_tine_import_csv.xml';
         $applicationId = Tinebase_Application::getInstance()->getApplicationByName('Inventory')->getId();
         $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile($filename, $applicationId);
index bae6817..3107e41 100644 (file)
@@ -583,4 +583,23 @@ class Timetracker_ControllerTest extends TestCase
         
         $this->assertEquals(10, count($result));
     }
+
+    /**
+     * Tests if a time account favorite can be created
+     */
+    public function testCreateTimeaccountFavorite()
+    {
+        $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->create($this->_getTimeaccount());
+        $user = Tinebase_Core::getUser();
+
+        $timeaccountFavorite = new Timetracker_Model_TimeaccountFavorite([
+            'account_id' => $user->accountId,
+            'timeaccount_id' => $timeaccount->getId()
+        ]);
+
+        $timeaccountCreated = Timetracker_Controller_TimeaccountFavorites::getInstance()->create($timeaccountFavorite);
+
+        static::assertEquals($timeaccountCreated->account_id, $user->getId());
+        static::assertEquals($timeaccountCreated->timeaccount_id, $timeaccount->id);
+    }
 }
index ed13688..789269f 100644 (file)
@@ -396,6 +396,7 @@ class Tinebase_ApplicationTest extends TestCase
                 'Timetracker_Model_TimeaccountGrants',
                 'Timetracker_Model_Timesheet',
                 'Timetracker_Model_Timeaccount',
+                'Timetracker_Model_TimeaccountFavorite'
             ),
             'Tinebase' => array(
                 'Tinebase_Model_AccessLog',
index b6fec6b..1fb9b93 100644 (file)
@@ -583,16 +583,24 @@ class Addressbook_Controller_Contact extends Tinebase_Controller_Record_Abstract
             $this->_setGeoData($_record);
         }
         
-        if (isset($_record->jpegphoto) && ! empty($_record->jpegphoto)) {
-            // add system note when jpegphoto gets updated
-            $translate = $translate = Tinebase_Translation::getTranslation('Addressbook');
-            $noteMessage = $translate->_('Uploaded new contact image.');
+        if (isset($_record->jpegphoto)){
+            // add system note when jpegphoto gets changed
+            $translate = Tinebase_Translation::getTranslation('Addressbook');
+            $noteMessage = "";
+
+            if (! empty($_record->jpegphoto)) {
+                // new or updated contact image supplied
+                $noteMessage = $translate->_('Uploaded new contact image.');
+            } else {
+                // contact image deleted
+                $noteMessage = $translate->_('Deleted contact image.');
+            }
             $traceException = new Exception($noteMessage);
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
                 . ' ' . $traceException);
             Tinebase_Notes::getInstance()->addSystemNote($_record, Tinebase_Core::getUser(), Tinebase_Model_Note::SYSTEM_NOTE_NAME_CHANGED, $noteMessage);
         }
-        
+
         if (isset($_oldRecord->type) && $_oldRecord->type == Addressbook_Model_Contact::CONTACTTYPE_USER) {
             $_record->type = Addressbook_Model_Contact::CONTACTTYPE_USER;
         }
index 87f880c..0a2fc1a 100644 (file)
@@ -247,6 +247,7 @@ Tine.Addressbook.ContactGridPanel.getBaseColumns = function(i18n) {
         { id: 'bday', header: i18n._('Birthday'), dataIndex: 'bday', renderer: Tine.Tinebase.common.dateRenderer },
         { id: 'memberroles', header: i18n._('List Roles'), dataIndex: 'memberroles', renderer: Tine.Addressbook.ListMemberRoleRenderer }
     ];
+
     if (Tine.Tinebase.appMgr.get('Addressbook').featureEnabled('featureIndustry')) {
         columns.push({ id: 'industry', header: i18n._('Industry'), dataIndex: 'industry', renderer: Tine.Tinebase.common.foreignRecordRenderer});
     }
index 3fcf1da..1159523 100644 (file)
@@ -13,344 +13,460 @@ msgstr ""
 "X-Poedit-SourceCharset: utf-8\n"
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 
-#: js/ContactFilterModel.js:35 js/ContactGrid.js:162 js/Model.js:100
-#: js/Model.js:185
-msgid "Contact"
-msgid_plural "Contacts"
+#: 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:306 js/ContactGrid.js:232
+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:165 js/Model.js:33
+#: js/ContactGrid.js:295 js/ContactEditDialog.js:186 js/ContactGrid.js:220
+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:326 js/Model.js:43 js/Model.js:179
+#: js/Model.js:180 js/Model.js:181 js/Model.js:182 js/Model.js:183
+#: js/ContactEditDialog.js:355 js/Model.js:210 js/Model.js:211 js/Model.js:212
+#: js/Model.js:213 js/Model.js:214 js/Printer/ContactRecord.js:72
+msgid "Private Address"
+msgstr ""
+
+#: Export/Pdf.php:96
+msgid "Email Home"
+msgstr ""
+
+#: Export/Pdf.php:99
+msgid "Telephone Home"
+msgstr ""
+
+#: Export/Pdf.php:102
+msgid "Telephone Cellphone Private"
+msgstr ""
+
+#: Export/Pdf.php:105
+msgid "Telephone Fax Home"
+msgstr ""
+
+#: Export/Pdf.php:108
+msgid "URL Home"
+msgstr ""
+
+#: Export/Pdf.php:112
+msgid "Other Data"
+msgstr ""
+
+#: Export/Pdf.php:114 js/ContactEditDialog.js:192 js/Model.js:26
+#: js/Model.js:173 js/ContactGrid.js:321 js/ContactEditDialog.js:221
+#: js/ContactGrid.js:247 js/Model.js:204
+msgid "Birthday"
+msgstr ""
+
+#: Export/Pdf.php:117 js/ContactEditDialog.js:186 js/Model.js:30
+#: js/Model.js:168 js/ContactGrid.js:293 js/ContactEditDialog.js:215
+#: js/ContactGrid.js:218 js/Model.js:199
+msgid "Job Title"
+msgstr ""
+
+#: Export/Doc.php:63
+msgid "Dear Mister"
+msgstr ""
+
+#: Export/Doc.php:65
+msgid "Dear Miss"
+msgstr ""
+
+#: Export/Doc.php:67
+msgid "Dear"
+msgstr ""
+
+#: Export/Doc.php:83
+msgid "Mister"
+msgstr ""
+
+#: Export/Doc.php:85
+msgid "Misses"
+msgstr ""
+
+#: Controller.php:129
+#, python-format
+msgid "%s's personal addressbook"
+msgstr ""
+
+#: Controller.php:184 js/Model.js:240 js/ListGridDetailsPanel.js:57
+#: js/ListGridDetailsPanel.js:83 js/Model.js:282
+msgid "Lists"
+msgstr ""
+
+#: Controller.php:193 js/Model.js:338 js/ContactGrid.js:322
+#: js/ListRoleGridPanel.js:50 js/ContactGrid.js:248 js/Model.js:380
+msgid "List Roles"
+msgstr ""
+
+#: Controller.php:201 js/Model.js:369 js/Model.js:411 Controller.php:202
+msgid "Industries"
+msgstr ""
+
+#: Acl/Rights.php:122
+msgid "manage shared addressbooks"
+msgstr ""
+
+#: Acl/Rights.php:123
+msgid "Create new shared addressbook folders"
+msgstr ""
+
+#: Acl/Rights.php:126
+msgid "manage shared addressbook favorites"
+msgstr ""
+
+#: Acl/Rights.php:127
+msgid "Create or update shared addressbook favorites"
+msgstr ""
+
+#: Acl/Rights.php:130
+msgid "Manage lists in CoreData"
+msgstr ""
+
+#: Acl/Rights.php:131
+msgid "View, create, delete or update lists in CoreData application"
+msgstr ""
+
+#: Acl/Rights.php:134
+msgid "Manage list roles in CoreData"
+msgstr ""
+
+#: Acl/Rights.php:135
+msgid "View, create, delete or update list roles in CoreData application"
+msgstr ""
+
+#: Model/Contact.php:557 js/ContactEditDialog.js:145 js/Model.js:369
+#: js/ContactEditDialog.js:209 js/ContactGrid.js:251 js/Model.js:411
+msgid "Industry"
+msgid_plural "Industries"
 msgstr[0] ""
 msgstr[1] ""
 
-#: js/MapPanel.js:40 js/ContactEditDialog.js:45 js/ContactEditDialog.js:54
-msgid "Map"
+#: js/ListSearchCombo.js:27
+msgid "Search for system groups ..."
 msgstr ""
 
-#: js/MapPanel.js:58
-msgid "Company address"
+#: js/ListSearchCombo.js:28
+msgid "Search for groups ..."
 msgstr ""
 
-#: js/MapPanel.js:65
-msgid "Private address"
+#: js/CardDAVContainerPropertiesHookField.js:35
+msgid "CardDAV URL"
 msgstr ""
 
-#: js/ListMemberFilterModel.js:37
-msgid "Member of List"
-msgstr ""
+#: js/ContactFilterModel.js:35 js/Model.js:97 js/Model.js:154
+#: js/ContactGrid.js:255 js/ContactGrid.js:162 js/Model.js:100 js/Model.js:185
+msgid "Contact"
+msgid_plural "Contacts"
+msgstr[0] ""
+msgstr[1] ""
 
-#: js/contactListsGridPanel.js:55 js/ContactGrid.js:200 js/Model.js:215
-#: js/ListGrid.js:100
-msgid "Type"
+#: js/ListRoleMemberFilterModel.js:37 js/Model.js:338 js/Model.js:380
+msgid "List Role"
+msgid_plural "List Roles"
+msgstr[0] ""
+msgstr[1] ""
+
+#: js/ListMemberRoleGridPanel.js:37 js/ListRoleGridPanel.js:71
+#: js/Printer/ListRecord.js:40
+msgid "Members"
 msgstr ""
 
-#: js/contactListsGridPanel.js:56 js/Model.js:19 js/Model.js:302
-#: js/ListRoleGridPanel.js:65 js/Addressbook.js:75 js/Addressbook.js:110
-#: js/ListEditDialog.js:98 js/ListEditDialog.js:181 js/ListGrid.js:102
-msgid "Name"
+#: js/ListMemberRoleGridPanel.js:207 js/ListMemberRoleGridPanel.js:221
+msgid "Remove Member"
 msgstr ""
 
-#: js/ContactEditDialog.js:64 js/ContactEditDialog.js:79 js/ContactGrid.js:231
-#: js/Model.js:51
-msgid "Preferred Address"
+#: js/ContactEditDialog.js:42 js/ContactEditDialog.js:51 js/MapPanel.js:40
+#: js/ContactEditDialog.js:45 js/ContactEditDialog.js:54
+msgid "Map"
 msgstr ""
 
-#: js/ContactEditDialog.js:114
+#: js/ContactEditDialog.js:80 js/ContactEditDialog.js:114
 msgid "Personal Information"
 msgstr ""
 
-#: js/ContactEditDialog.js:130 js/ContactGrid.js:202 js/Model.js:29
+#: js/ContactEditDialog.js:96 js/Model.js:29 js/Model.js:190
+#: js/ContactGrid.js:277 js/ContactEditDialog.js:130 js/ContactGrid.js:202
 #: js/Model.js:221
 msgid "Salutation"
 msgstr ""
 
-#: js/ContactEditDialog.js:146 js/ContactGrid.js:210 js/Model.js:22
+#: js/ContactEditDialog.js:112 js/Model.js:22 js/Model.js:161
+#: js/ContactGrid.js:285 js/ContactEditDialog.js:146 js/ContactGrid.js:210
 #: js/Model.js:192
 msgid "Title"
 msgstr ""
 
-#: js/ContactEditDialog.js:151 js/ContactGrid.js:213 js/Model.js:20
+#: js/ContactEditDialog.js:117 js/Model.js:20 js/Model.js:162
+#: js/ContactGrid.js:288 js/ContactEditDialog.js:151 js/ContactGrid.js:213
 #: js/Model.js:193
 msgid "First Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:156 js/ContactGrid.js:211 js/Model.js:21
+#: js/ContactEditDialog.js:122 js/Model.js:21 js/Model.js:164
+#: js/ContactGrid.js:286 js/ContactEditDialog.js:156 js/ContactGrid.js:211
 #: js/Model.js:195
 msgid "Middle Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:161 js/ContactGrid.js:212 js/Model.js:19
+#: js/ContactEditDialog.js:127 js/Model.js:19 js/Model.js:163
+#: js/ContactGrid.js:287 js/ContactEditDialog.js:161 js/ContactGrid.js:212
 #: js/Model.js:194
 msgid "Last Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:167 js/ContactGrid.js:216 js/Model.js:27
-#: js/Model.js:196 Config.php:165 Setup/Update/Release5.php:196
+#: js/ContactEditDialog.js:133 js/Model.js:27 js/Model.js:165
+#: js/ContactGridDetailsPanel.js:131 js/ContactGrid.js:291 Config.php:110
+#: Setup/Update/Release5.php:196 js/ContactEditDialog.js:167
+#: js/ContactGrid.js:216 js/Model.js:196 Config.php:165
 msgid "Company"
 msgstr ""
 
-#: js/ContactEditDialog.js:172 js/ContactGrid.js:217 js/Model.js:28
+#: js/ContactEditDialog.js:138 js/Model.js:28 js/Model.js:166
+#: js/ContactGrid.js:292 js/ContactEditDialog.js:172 js/ContactGrid.js:217
 #: js/Model.js:197
 msgid "Unit"
 msgstr ""
 
-#: js/ContactEditDialog.js:178 js/Model.js:23
+#: js/ContactEditDialog.js:157 js/Model.js:23 js/ContactEditDialog.js:178
 msgid "Suffix"
 msgstr ""
 
-#: js/ContactEditDialog.js:182 js/ContactGrid.js:219 js/Model.js:31
+#: js/ContactEditDialog.js:161 js/Model.js:31 js/Model.js:172
+#: js/ContactGrid.js:294 js/ContactEditDialog.js:182 js/ContactGrid.js:219
 #: js/Model.js:203
 msgid "Job Role"
 msgstr ""
 
-#: js/ContactEditDialog.js:186 js/ContactGrid.js:220 js/Model.js:33
-#: Export/Pdf.php:76
-msgid "Room"
-msgstr ""
-
-#: js/ContactEditDialog.js:203 js/ContactGrid.js:215 js/Model.js:24
+#: js/ContactEditDialog.js:181 js/Model.js:24 js/ContactGrid.js:290
+#: js/ContactEditDialog.js:203 js/ContactGrid.js:215
 msgid "Display Name"
 msgstr ""
 
-#: js/ContactEditDialog.js:209 js/ContactGrid.js:251 js/Model.js:411
-msgid "Industry"
-msgid_plural "Industries"
-msgstr[0] ""
-msgstr[1] ""
-
-#: js/ContactEditDialog.js:215 js/ContactGrid.js:218 js/Model.js:30
-#: js/Model.js:199 Export/Pdf.php:117
-msgid "Job Title"
-msgstr ""
-
-#: js/ContactEditDialog.js:221 js/ContactGrid.js:247 js/Model.js:26
-#: js/Model.js:204 Export/Pdf.php:114
-msgid "Birthday"
-msgstr ""
-
-#: js/ContactEditDialog.js:229 js/Printer/ContactRecord.js:42
+#: js/ContactEditDialog.js:200 js/ContactEditDialog.js:229
+#: js/Printer/ContactRecord.js:42
 msgid "Contact Information"
 msgstr ""
 
-#: js/ContactEditDialog.js:238 js/ContactGridDetailsPanel.js:117
-#: js/ContactGrid.js:233 js/Model.js:52 js/Model.js:198
+#: js/ContactEditDialog.js:209 js/Model.js:51 js/Model.js:167
+#: js/ContactGridDetailsPanel.js:139 js/ContactGridDetailsPanel.js:163
+#: js/ContactGrid.js:307 js/ContactEditDialog.js:238
+#: js/ContactGridDetailsPanel.js:117 js/ContactGrid.js:233 js/Model.js:52
+#: js/Model.js:198
 msgid "Phone"
 msgstr ""
 
+#: js/ContactEditDialog.js:214 js/Model.js:52 js/ContactGridDetailsPanel.js:140
+#: js/ContactGridDetailsPanel.js:164 js/ContactGrid.js:308
 #: js/ContactEditDialog.js:243 js/ContactGridDetailsPanel.js:121
 #: js/ContactGrid.js:234 js/Model.js:53
 msgid "Mobile"
 msgstr ""
 
+#: js/ContactEditDialog.js:219 js/Model.js:53 js/ContactGridDetailsPanel.js:141
+#: js/ContactGridDetailsPanel.js:165 js/ContactGrid.js:309
 #: js/ContactEditDialog.js:248 js/ContactGridDetailsPanel.js:125
 #: js/ContactGrid.js:235 js/Model.js:54
 msgid "Fax"
 msgstr ""
 
+#: js/ContactEditDialog.js:224 js/Model.js:57 js/ContactGrid.js:312
 #: js/ContactEditDialog.js:253 js/ContactGrid.js:238 js/Model.js:58
 msgid "Phone (private)"
 msgstr ""
 
+#: js/ContactEditDialog.js:229 js/Model.js:59 js/ContactGrid.js:314
 #: js/ContactEditDialog.js:258 js/ContactGrid.js:240 js/Model.js:60
 msgid "Mobile (private)"
 msgstr ""
 
+#: js/ContactEditDialog.js:234 js/Model.js:58 js/ContactGrid.js:313
 #: js/ContactEditDialog.js:263 js/ContactGrid.js:239 js/Model.js:59
 msgid "Fax (private)"
 msgstr ""
 
+#: js/ContactEditDialog.js:239 js/Model.js:62 js/Model.js:170
+#: js/ContactGridDetailsPanel.js:142 js/ContactGridDetailsPanel.js:166
 #: js/ContactEditDialog.js:268 js/ContactGridDetailsPanel.js:129 js/Model.js:63
 #: js/Model.js:201
 msgid "E-Mail"
 msgstr ""
 
-#: js/ContactEditDialog.js:274 js/Model.js:64
+#: js/ContactEditDialog.js:245 js/Model.js:63 js/ContactEditDialog.js:274
+#: js/Model.js:64
 msgid "E-Mail (private)"
 msgstr ""
 
+#: js/ContactEditDialog.js:252 js/Model.js:64 js/ContactGridDetailsPanel.js:144
+#: js/ContactGridDetailsPanel.js:168 js/ContactGrid.js:316
 #: js/ContactEditDialog.js:281 js/ContactGridDetailsPanel.js:135
 #: js/ContactGrid.js:242 js/Model.js:65
 msgid "Web"
 msgstr ""
 
-#: js/ContactEditDialog.js:326 js/Model.js:34 js/Model.js:205 js/Model.js:206
-#: js/Model.js:207 js/Model.js:208 js/Model.js:209
-#: js/Printer/ContactRecord.js:67
+#: js/ContactEditDialog.js:297 js/Model.js:34 js/Model.js:174 js/Model.js:175
+#: js/Model.js:176 js/Model.js:177 js/Model.js:178 js/ContactEditDialog.js:326
+#: js/Model.js:205 js/Model.js:206 js/Model.js:207 js/Model.js:208
+#: js/Model.js:209 js/Printer/ContactRecord.js:67
 msgid "Company Address"
 msgstr ""
 
-#: js/ContactEditDialog.js:329 js/ContactEditDialog.js:358
+#: js/ContactEditDialog.js:300 js/ContactEditDialog.js:329 js/Model.js:174
+#: js/Model.js:179 js/ContactGrid.js:296 js/ContactEditDialog.js:358
 #: js/ContactGrid.js:221 js/Model.js:205 js/Model.js:210
 msgid "Street"
 msgstr ""
 
-#: js/ContactEditDialog.js:333 js/ContactEditDialog.js:362
+#: js/ContactEditDialog.js:304 js/ContactEditDialog.js:333
+#: js/ContactEditDialog.js:362
 msgid "Street 2"
 msgstr ""
 
-#: js/ContactEditDialog.js:337 js/ContactEditDialog.js:366
+#: js/ContactEditDialog.js:308 js/ContactEditDialog.js:337 js/Model.js:175
+#: js/Model.js:180 js/ContactGrid.js:298 js/ContactEditDialog.js:366
 #: js/ContactGrid.js:223 js/Model.js:206 js/Model.js:211
 msgid "Region"
 msgstr ""
 
-#: js/ContactEditDialog.js:341 js/ContactEditDialog.js:370 js/Model.js:207
-#: js/Model.js:212
+#: js/ContactEditDialog.js:312 js/ContactEditDialog.js:341 js/Model.js:176
+#: js/Model.js:181 js/ContactEditDialog.js:370 js/Model.js:207 js/Model.js:212
 msgid "Postal Code"
 msgstr ""
 
-#: js/ContactEditDialog.js:345 js/ContactEditDialog.js:374
+#: js/ContactEditDialog.js:316 js/ContactEditDialog.js:345 js/Model.js:177
+#: js/Model.js:182 js/ContactGrid.js:297 js/ContactEditDialog.js:374
 #: js/ContactGrid.js:222 js/Model.js:208 js/Model.js:213
 msgid "City"
 msgstr ""
 
-#: js/ContactEditDialog.js:350 js/ContactEditDialog.js:379
+#: js/ContactEditDialog.js:321 js/ContactEditDialog.js:350 js/Model.js:178
+#: js/Model.js:183 js/ContactGrid.js:300 js/ContactEditDialog.js:379
 #: js/ContactGrid.js:225 js/Model.js:209 js/Model.js:214
 msgid "Country"
 msgstr ""
 
-#: js/ContactEditDialog.js:355 js/Model.js:43 js/Model.js:210 js/Model.js:211
-#: js/Model.js:212 js/Model.js:213 js/Model.js:214
-#: js/Printer/ContactRecord.js:72 Export/Pdf.php:88
-msgid "Private Address"
-msgstr ""
-
+#: js/ContactEditDialog.js:376 js/Addressbook.js:80 js/Addressbook.js:115
+#: js/Model.js:68 js/Model.js:169 js/Model.js:261 js/ListEditDialog.js:118
 #: js/ContactEditDialog.js:405 js/Model.js:69 js/Model.js:200 js/Model.js:303
 #: js/Addressbook.js:81 js/Addressbook.js:116 js/ListEditDialog.js:133
 msgid "Description"
 msgstr ""
 
+#: js/ContactEditDialog.js:390 js/ListEditDialog.js:132
 #: js/ContactEditDialog.js:419 js/ListEditDialog.js:147
 msgid "Enter description"
 msgstr ""
 
-#: js/ContactEditDialog.js:423 js/Model.js:83
-msgid "Groups"
+#: js/ContactEditDialog.js:428
+msgid "Export as pdf"
 msgstr ""
 
+#: js/ContactEditDialog.js:435 js/ContactEditDialog.js:479
 #: js/ContactEditDialog.js:466 js/ContactEditDialog.js:524
 msgid "Parse address"
 msgstr ""
 
-#: js/ContactEditDialog.js:474
-msgid "Print contact"
-msgstr ""
-
-#: js/ContactEditDialog.js:516
+#: js/ContactEditDialog.js:471 js/ContactEditDialog.js:516
 msgid "Paste address"
 msgstr ""
 
-#: js/ContactEditDialog.js:516
+#: js/ContactEditDialog.js:471 js/ContactEditDialog.js:516
 msgid "Please paste an address or a URI to a vcard that should be parsed:"
 msgstr ""
 
-#: js/ContactEditDialog.js:542
+#: js/ContactEditDialog.js:497 js/ContactEditDialog.js:542
 msgid "Failed to parse address!"
 msgstr ""
 
-#: js/ContactEditDialog.js:542
+#: js/ContactEditDialog.js:497 js/ContactEditDialog.js:542
 msgid "The address could not be read."
 msgstr ""
 
-#: js/ContactEditDialog.js:559
+#: js/ContactEditDialog.js:514 js/ContactEditDialog.js:559
 msgid "End token mode"
 msgstr ""
 
-#: js/ContactGridDetailsPanel.js:79 js/ContactGrid.js:187
-msgid "Business"
-msgstr ""
-
-#: js/ContactGridDetailsPanel.js:148 js/ContactGrid.js:189
-msgid "Private"
-msgstr ""
-
-#: js/ContactGrid.js:110
-msgid "Import contacts"
-msgstr ""
-
-#: js/ContactGrid.js:162
-msgid "Contact of a user account"
-msgstr ""
-
-#: js/ContactGrid.js:169
-msgid "No name"
-msgstr ""
-
-#: js/ContactGrid.js:191
-msgid "Not set"
-msgstr ""
-
-#: js/ContactGrid.js:201 js/ListGrid.js:101
-msgid "Tags"
-msgstr ""
-
-#: js/ContactGrid.js:214
-msgid "Full Name"
-msgstr ""
-
-#: js/ContactGrid.js:224
-msgid "Postalcode"
-msgstr ""
-
-#: js/ContactGrid.js:226
-msgid "Street (private)"
-msgstr ""
-
-#: js/ContactGrid.js:227
-msgid "City (private)"
-msgstr ""
-
-#: js/ContactGrid.js:228
-msgid "Region (private)"
-msgstr ""
-
-#: js/ContactGrid.js:229
-msgid "Postalcode (private)"
-msgstr ""
-
-#: js/ContactGrid.js:230
-msgid "Country (private)"
-msgstr ""
-
-#: js/ContactGrid.js:232 Export/Pdf.php:52
-msgid "Email"
-msgstr ""
-
-#: js/ContactGrid.js:236
-msgid "Car phone"
-msgstr ""
-
-#: js/ContactGrid.js:237
-msgid "Pager"
-msgstr ""
-
-#: js/ContactGrid.js:241
-msgid "Email (private)"
-msgstr ""
-
-#: js/ContactGrid.js:243
-msgid "URL (private)"
+#: js/Addressbook.js:22
+msgid "New Contact"
 msgstr ""
 
-#: js/ContactGrid.js:244
-msgid "Note"
-msgstr ""
+#: js/Addressbook.js:32 js/Model.js:101 js/Model.js:244 js/Model.js:295
+#: js/Model.js:104 js/Model.js:286 js/Model.js:337
+msgid "Addressbook"
+msgid_plural "Addressbooks"
+msgstr[0] ""
+msgstr[1] ""
 
-#: js/ContactGrid.js:245
-msgid "Timezone"
+#: js/Addressbook.js:67 js/Addressbook.js:102 js/ListRoleGridPanel.js:58
+#: js/Addressbook.js:68 js/Addressbook.js:103
+msgid "ID"
 msgstr ""
 
-#: js/ContactGrid.js:246
-msgid "Geo"
+#: js/Addressbook.js:74 js/Addressbook.js:109 js/Model.js:19 js/Model.js:260
+#: js/ListGrid.js:102 js/ListEditDialog.js:83 js/ListEditDialog.js:166
+#: js/ListRoleGridPanel.js:65 js/contactListsGridPanel.js:56 js/Model.js:302
+#: js/Addressbook.js:75 js/Addressbook.js:110 js/ListEditDialog.js:98
+#: js/ListEditDialog.js:181
+msgid "Name"
 msgstr ""
 
-#: js/ContactGrid.js:248 js/Model.js:380 js/ListRoleGridPanel.js:50
-#: Controller.php:193
-msgid "List Roles"
+#: js/ListMemberFilterModel.js:37
+msgid "Member of List"
 msgstr ""
 
-#: js/ListRoleMemberFilterModel.js:37 js/Model.js:380
-msgid "List Role"
-msgid_plural "List Roles"
-msgstr[0] ""
-msgstr[1] ""
-
 #: js/Model.js:34
 msgid "Street (Company Address)"
 msgstr ""
@@ -407,492 +523,541 @@ msgstr ""
 msgid "Country (Private Address)"
 msgstr ""
 
-#: js/Model.js:52
+#: js/Model.js:51 js/Model.js:52
 msgid "Company Communication"
 msgstr ""
 
-#: js/Model.js:58
+#: js/Model.js:57 js/Model.js:58
 msgid "Private Communication"
 msgstr ""
 
-#: js/Model.js:66
+#: js/Model.js:65 js/Model.js:66
 msgid "Web (private)"
 msgstr ""
 
-#: js/Model.js:100
+#: js/Model.js:97 js/ContactGridDetailsPanel.js:62 js/Model.js:100
 msgid "Contacts"
 msgstr ""
 
-#: js/Model.js:104 js/Model.js:286 js/Model.js:337 js/Addressbook.js:32
-msgid "Addressbook"
-msgid_plural "Addressbooks"
-msgstr[0] ""
-msgstr[1] ""
-
-#: js/Model.js:104 js/Model.js:286 js/Model.js:337
+#: js/Model.js:101 js/Model.js:244 js/Model.js:295 js/Model.js:104
+#: js/Model.js:286 js/Model.js:337
 msgid "Addressbooks"
 msgstr ""
 
-#: js/Model.js:170
-msgid "Resource"
-msgid_plural "Resources"
-msgstr[0] ""
-msgstr[1] ""
-
-#: js/Model.js:170
-msgid "Resources"
-msgstr ""
-
-#: js/Model.js:185 js/Model.js:296
+#: js/Model.js:154 js/Model.js:254 js/Model.js:185 js/Model.js:296
 msgid "User Account"
 msgstr ""
 
-#: js/Model.js:188
+#: js/Model.js:157 js/Model.js:188
 msgid "Quick Search"
 msgstr ""
 
-#: js/Model.js:216 js/Model.js:304
+#: js/Model.js:184 js/ListGrid.js:100 js/ContactGrid.js:275
+#: js/contactListsGridPanel.js:55 js/ContactGrid.js:200 js/Model.js:215
+msgid "Type"
+msgstr ""
+
+#: js/Model.js:185 js/Model.js:262 js/Model.js:216 js/Model.js:304
 msgid "Last Modified Time"
 msgstr ""
 
-#: js/Model.js:217 js/Model.js:305
+#: js/Model.js:186 js/Model.js:263 js/Model.js:217 js/Model.js:305
 msgid "Last Modified By"
 msgstr ""
 
-#: js/Model.js:218 js/Model.js:306
+#: js/Model.js:187 js/Model.js:264 js/Model.js:218 js/Model.js:306
 msgid "Creation Time"
 msgstr ""
 
-#: js/Model.js:219 js/Model.js:307
+#: js/Model.js:188 js/Model.js:265 js/Model.js:219 js/Model.js:307
 msgid "Created By"
 msgstr ""
 
-#: js/Model.js:282 js/Model.js:296 js/ListGridDetailsPanel.js:86
+#: js/Model.js:240 js/Model.js:254 js/ListGridDetailsPanel.js:86
+#: js/Model.js:282 js/Model.js:296
 msgid "List"
 msgid_plural "Lists"
 msgstr[0] ""
 msgstr[1] ""
 
-#: js/Model.js:282 js/ListGridDetailsPanel.js:57 js/ListGridDetailsPanel.js:83
-#: Controller.php:184
-msgid "Lists"
-msgstr ""
-
+#: js/Model.js:257 js/Model.js:322 js/Model.js:352 js/Model.js:383
 #: js/Model.js:299 js/Model.js:364 js/Model.js:394 js/Model.js:425
 msgid "Quick search"
 msgstr ""
 
-#: js/Model.js:333
+#: js/Model.js:291 js/Model.js:333
 msgid "Email Address"
 msgid_plural "Email Addresses"
 msgstr[0] ""
 msgstr[1] ""
 
-#: js/Model.js:333
+#: js/Model.js:291 js/Model.js:333
 msgid "Email Addresses"
 msgstr ""
 
-#: js/Model.js:411 Controller.php:202
-msgid "Industries"
+#: js/ListGridDetailsPanel.js:60
+msgid "Select list"
 msgstr ""
 
-#: js/ListMemberRoleGridPanel.js:37 js/ListRoleGridPanel.js:71
-#: js/Printer/ListRecord.js:40
-msgid "Members"
+#: js/ListGrid.js:101 js/ContactGrid.js:276 js/ContactGrid.js:201
+msgid "Tags"
 msgstr ""
 
-#: js/ListMemberRoleGridPanel.js:221
-msgid "Remove Member"
+#: js/ListGrid.js:103 js/ListEditDialog.js:89 js/ListEditDialog.js:104
+msgid "List type"
 msgstr ""
 
-#: js/ContactSearchCombo.js:68
-msgid "Search for users ..."
+#: js/ListGrid.js:104
+msgid "Emails"
 msgstr ""
 
-#: js/ContactSearchCombo.js:69
-msgid "Search for Contacts ..."
+#: js/ListGrid.js:146
+msgid "System Group"
 msgstr ""
 
-#: js/ListGridDetailsPanel.js:60
-msgid "Select list"
+#: js/ListGrid.js:146
+msgid "Group"
 msgstr ""
 
-#: js/CardDAVContainerPropertiesHookField.js:35
-msgid "CardDAV URL"
+#: js/ListEditDialog.js:71 js/ListEditDialog.js:86
+msgid "List Information"
 msgstr ""
 
-#: js/ListRoleGridPanel.js:58 js/Addressbook.js:68 js/Addressbook.js:103
-msgid "ID"
+#: js/ListEditDialog.js:166 js/ListEditDialog.js:181
+#, python-brace-format
+msgid "{0} must be given"
 msgstr ""
 
-#: js/Addressbook.js:22
-msgid "New Contact"
+#: js/MapPanel.js:58
+msgid "Company address"
 msgstr ""
 
-#: js/Printer/ListRecord.js:56
-msgid "Customfields"
+#: js/MapPanel.js:65
+msgid "Private address"
 msgstr ""
 
-#: js/Printer/ListRecord.js:67 js/Printer/ContactRecord.js:79
-msgid "Related to"
+#: js/ContactGridDetailsPanel.js:65
+msgid "Select contact"
 msgstr ""
 
-#: js/ListSearchCombo.js:27
-msgid "Search for system groups ..."
+#: js/ContactGridDetailsPanel.js:155 js/ContactGridDetailsPanel.js:148
+#: js/ContactGrid.js:189
+msgid "Private"
 msgstr ""
 
-#: js/ListSearchCombo.js:28
-msgid "Search for groups ..."
+#: js/ContactGridDetailsPanel.js:178
+msgid "Info"
 msgstr ""
 
-#: js/ListEditDialog.js:55
+#: js/ContactGridDetailsPanel.js:214
+msgid "Insecure link"
+msgstr ""
+
+#: js/ContactGridDetailsPanel.js:214
+msgid "Please review this link in edit dialog."
+msgstr ""
+
+#: js/ContactGrid.js:110 js/ContactGrid.js:111 js/ContactGrid.js:112
 #, python-brace-format
-msgid "Print {0}"
+msgid "Export {0}"
+msgid_plural "Export {0}"
+msgstr[0] ""
+msgstr[1] ""
+
+#: js/ContactGrid.js:121
+msgid "Export as PDF"
 msgstr ""
 
-#: js/ListEditDialog.js:86
-msgid "List Information"
+#: js/ContactGrid.js:128
+msgid "Export as CSV"
 msgstr ""
 
-#: js/ListEditDialog.js:104 js/ListGrid.js:103
-msgid "List type"
+#: js/ContactGrid.js:135
+msgid "Export as ODS"
 msgstr ""
 
-#: js/ListEditDialog.js:181
-#, python-brace-format
-msgid "{0} must be given"
+#: js/ContactGrid.js:142
+msgid "Export as XLS"
 msgstr ""
 
-#: js/ListGrid.js:104
-msgid "Emails"
+#: js/ContactGrid.js:149
+msgid "Export as DOC"
 msgstr ""
 
-#: js/ListGrid.js:146
-msgid "System Group"
+#: js/ContactGrid.js:156
+msgid "Export as ..."
 msgstr ""
 
-#: js/ListGrid.js:146
-msgid "Group"
+#: js/ContactGrid.js:168 js/ContactGrid.js:110
+msgid "Import contacts"
 msgstr ""
 
-#: Controller/Contact.php:589
-msgid "Uploaded new contact image."
+#: js/ContactGrid.js:255 js/ContactGrid.js:162
+msgid "Contact of a user account"
 msgstr ""
 
-#: Frontend/Cli.php:216
-msgid ""
-"This contact has been automatically added by the system as an event attender"
+#: js/ContactGrid.js:262 js/ContactGrid.js:169
+msgid "No name"
 msgstr ""
 
-#: Frontend/CardDAV/AllContacts.php:41
-msgid "All Contacts"
+#: js/ContactGrid.js:289 js/ContactGrid.js:214
+msgid "Full Name"
 msgstr ""
 
-#: Import/definitions/adb_outlook_import_csv.xml:10
-msgid "Import CSV formated contacts from Exchange / Outlook address book"
+#: js/ContactGrid.js:299 js/ContactGrid.js:224
+msgid "Postalcode"
 msgstr ""
 
-#: Import/definitions/adb_outlook_import_csv.xml:14
-msgid "Contact CSV import from Outlook address book"
+#: js/ContactGrid.js:301 js/ContactGrid.js:226
+msgid "Street (private)"
 msgstr ""
 
-#: Import/definitions/adb_mac_import_csv.xml:17
-msgid "Contact CSV import from mac address book"
+#: js/ContactGrid.js:302 js/ContactGrid.js:227
+msgid "City (private)"
 msgstr ""
 
-#: Import/definitions/adb_tine_import_csv.xml:11
-msgid "Tine 2.0 contact CSV import"
+#: js/ContactGrid.js:303 js/ContactGrid.js:228
+msgid "Region (private)"
 msgstr ""
 
-#: Import/definitions/adb_tine_import_csv.xml:13
-msgid "Import CSV formated contacts from Tine 2.0 address book"
+#: js/ContactGrid.js:304 js/ContactGrid.js:229
+msgid "Postalcode (private)"
 msgstr ""
 
-#: Import/definitions/adb_tine_import_csv.xml:19
-msgid "Import list (###CURRENTDATE###)"
+#: js/ContactGrid.js:305 js/ContactGrid.js:230
+msgid "Country (private)"
 msgstr ""
 
-#: Import/definitions/adb_tine_import_csv.xml:21
-msgid ""
-"Contacts imported on ###CURRENTDATE### at ###CURRENTTIME### by "
-"###USERFULLNAME###"
+#: js/ContactGrid.js:310 js/ContactGrid.js:236
+msgid "Car phone"
 msgstr ""
 
-#: Import/definitions/adb_outlook2007_de_import_csv.xml:10
-msgid "Import CSV formated contacts from Outlook 2007 German address book"
+#: js/ContactGrid.js:311 js/ContactGrid.js:237
+msgid "Pager"
+msgstr ""
+
+#: js/ContactGrid.js:315 js/ContactGrid.js:241
+msgid "Email (private)"
+msgstr ""
+
+#: js/ContactGrid.js:317 js/ContactGrid.js:243
+msgid "URL (private)"
+msgstr ""
+
+#: js/ContactGrid.js:318 js/ContactGrid.js:244
+msgid "Note"
+msgstr ""
+
+#: js/ContactGrid.js:319 js/ContactGrid.js:245
+msgid "Timezone"
+msgstr ""
+
+#: js/ContactGrid.js:320 js/ContactGrid.js:246
+msgid "Geo"
+msgstr ""
+
+#: js/ContactSearchCombo.js:68
+msgid "Search for users ..."
+msgstr ""
+
+#: js/ContactSearchCombo.js:69
+msgid "Search for Contacts ..."
+msgstr ""
+
+#: Controller/Contact.php:348 Controller/Contact.php:589
+msgid "Uploaded new contact image."
+msgstr ""
+
+#: Preference.php:28
+msgid "All contacts"
 msgstr ""
 
-#: Import/definitions/adb_outlook2007_de_import_csv.xml:15
-msgid "CSV Outlook 2007 German"
+#: Preference.php:81
+msgid "Default Addressbook"
 msgstr ""
 
-#: Import/definitions/adb_import_vcard.xml:9
-msgid "Import vCard formated contacts"
+#: Preference.php:82
+msgid "The default addressbook for new contacts"
 msgstr ""
 
-#: Import/definitions/adb_import_vcard.xml:15
-msgid "Contact VCARD import"
+#: Preference.php:85
+msgid "Default Favorite"
 msgstr ""
 
-#: Import/definitions/adb_google_import_csv.xml:11
-msgid "Import CSV formated contacts from Google address book"
+#: Preference.php:86
+msgid "The default favorite which is loaded on addressbook startup"
 msgstr ""
 
-#: Import/definitions/adb_google_import_csv.xml:13
-msgid "Contact import from Google address book"
+#: Preference.php:89
+msgid "Contacts ODS export configuration"
 msgstr ""
 
-#: Import/definitions/adb_lxoffice_import_csv.xml:11
-msgid "LX-Office CSV Contact import"
+#: Preference.php:90
+msgid "Use this configuration for the contact ODS export."
 msgstr ""
 
-#: Import/definitions/adb_lxoffice_import_csv.xml:13
-msgid "Import CSV formated contacts from LX-Office"
+#: Preference.php:93
+msgid "Contacts XLS export configuration"
 msgstr ""
 
-#: Config.php:95
-msgid "Enabled Features"
+#: Preference.php:94
+msgid "Use this configuration for the contact XLS export."
 msgstr ""
 
-#: Config.php:97
-msgid "Enabled Features in Calendar Application."
+#: Preference.php:165
+msgid "default"
 msgstr ""
 
-#: Config.php:105
-msgid "Addressbook List View"
+#: Frontend/Cli.php:114 Frontend/Cli.php:216
+msgid ""
+"This contact has been automatically added by the system as an event attender"
 msgstr ""
 
-#: Config.php:107
-msgid "Shows an additional view for lists inside the addressbook"
+#: Frontend/CardDAV/AllContacts.php:41
+msgid "All Contacts"
 msgstr ""
 
-#: Config.php:110
-msgid "Addressbook Industries"
+#: Config.php:67 Config.php:95
+msgid "Enabled Features"
 msgstr ""
 
-#: Config.php:111
-msgid "Add Industry field to Adressbook"
+#: Config.php:69 Config.php:97
+msgid "Enabled Features in Calendar Application."
 msgstr ""
 
-#: Config.php:114
-msgid "Manage resources in Addressbook"
+#: Config.php:76 Config.php:105
+msgid "Addressbook List View"
 msgstr ""
 
-#: Config.php:115
-msgid "Show the resources grid also inside the Addressbook"
+#: Config.php:77
+msgid "Shows an additional view for lists inside the addressbook)"
 msgstr ""
 
-#: Config.php:125
+#: Config.php:85 Config.php:125
 msgid "Contact duplicate check fields"
 msgstr ""
 
-#: Config.php:127
+#: Config.php:87 Config.php:127
 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 ""
 
-#: Config.php:144
-msgid "Contact Hidden Criteria"
-msgstr ""
-
-#: Config.php:146
-msgid "The contact is hidden if it is ... (one of: disabled, expired or never"
-msgstr ""
-
-#: Config.php:153
+#: Config.php:99 Config.php:153
 msgid "Contact salutations available"
 msgstr ""
 
-#: Config.php:155
+#: Config.php:101 Config.php:155
 msgid ""
 "Possible contact salutations. Please note that additional values might "
 "impact other Addressbook systems on export or syncronisation."
 msgstr ""
 
-#: Config.php:163 Setup/Update/Release5.php:194
+#: Config.php:108 Setup/Update/Release5.php:194 Config.php:163
 msgid "Mr"
 msgstr ""
 
-#: Config.php:164 Setup/Update/Release5.php:195
+#: Config.php:109 Setup/Update/Release5.php:195 Config.php:164
 msgid "Ms"
 msgstr ""
 
-#: Config.php:171
+#: Config.php:116 Config.php:171
 msgid "Parsing rules for addresses"
 msgstr ""
 
-#: Config.php:173
+#: Config.php:118 Config.php:173
 msgid "Path to a XML file with address parsing rules."
 msgstr ""
 
-#: Config.php:181
+#: Config.php:126 Config.php:181
 msgid "List types available"
 msgstr ""
 
-#: Config.php:183
+#: Config.php:128 Config.php:183
 msgid "List types available."
 msgstr ""
 
-#: Config.php:190
+#: Config.php:135 Config.php:190
 msgid "Department"
 msgstr ""
 
-#: Config.php:191
+#: Config.php:136 Config.php:191
 msgid "Mailing list"
 msgstr ""
 
-#: Config.php:196
+#: Config.php:141 Config.php:196
 msgid "Use Nominatim during contact import"
 msgstr ""
 
-#: Config.php:206 Config.php:208
-msgid "Sync Backends"
+#: Import/definitions/adb_outlook2007_de_import_csv.xml:10
+msgid "Import CSV formated contacts from Outlook 2007 German address book"
 msgstr ""
 
-#: Setup/Initialize.php:125 Setup/Update/Release3.php:37
-msgid "All contacts I have read grants for"
+#: Import/definitions/adb_outlook2007_de_import_csv.xml:15
+msgid "CSV Outlook 2007 German"
 msgstr ""
 
-#: Setup/Initialize.php:130
-msgid "My company"
+#: Import/definitions/adb_lxoffice_import_csv.xml:11
+msgid "LX-Office CSV Contact import"
 msgstr ""
 
-#: Setup/Initialize.php:131
-msgid "All coworkers in my company"
+#: Import/definitions/adb_lxoffice_import_csv.xml:13
+msgid "Import CSV formated contacts from LX-Office"
 msgstr ""
 
-#: Setup/Initialize.php:144
-msgid "My contacts"
+#: Import/definitions/adb_tine_import_csv.xml:11
+msgid "Tine 2.0 contact CSV import"
 msgstr ""
 
-#: Setup/Initialize.php:145
-msgid "All contacts in my Addressbooks"
+#: Import/definitions/adb_tine_import_csv.xml:13
+msgid "Import CSV formated contacts from Tine 2.0 address book"
 msgstr ""
 
-#: Setup/Initialize.php:157
-msgid "Last modified by me"
+#: Import/definitions/adb_tine_import_csv.xml:19
+msgid "Import list (###CURRENTDATE###)"
 msgstr ""
 
-#: Setup/Initialize.php:158
-msgid "All contacts that I have last modified"
+#: Import/definitions/adb_tine_import_csv.xml:21
+msgid ""
+"Contacts imported on ###CURRENTDATE### at ###CURRENTTIME### by "
+"###USERFULLNAME###"
 msgstr ""
 
-#: Setup/setup.xml:965
-msgid "Internal Contacts"
+#: Import/definitions/adb_outlook_import_csv.xml:10
+msgid "Import CSV formated contacts from Exchange / Outlook address book"
 msgstr ""
 
-#: Controller.php:129
-#, python-format
-msgid "%s's personal addressbook"
+#: Import/definitions/adb_outlook_import_csv.xml:14
+msgid "Contact CSV import from Outlook address book"
 msgstr ""
 
-#: Acl/Rights.php:122
-msgid "manage shared addressbooks"
+#: Import/definitions/adb_google_import_csv.xml:11
+msgid "Import CSV formated contacts from Google address book"
 msgstr ""
 
-#: Acl/Rights.php:123
-msgid "Create new shared addressbook folders"
+#: Import/definitions/adb_google_import_csv.xml:13
+msgid "Contact import from Google address book"
 msgstr ""
 
-#: Acl/Rights.php:126
-msgid "manage shared addressbook favorites"
+#: Import/definitions/adb_mac_import_csv.xml:17
+msgid "Contact CSV import from mac address book"
 msgstr ""
 
-#: Acl/Rights.php:127
-msgid "Create or update shared addressbook favorites"
+#: Import/definitions/adb_import_vcard.xml:9
+msgid "Import vCard formated contacts"
 msgstr ""
 
-#: Acl/Rights.php:130
-msgid "Manage lists in CoreData"
+#: Import/definitions/adb_import_vcard.xml:15
+msgid "Contact VCARD import"
 msgstr ""
 
-#: Acl/Rights.php:131
-msgid "View, create, delete or update lists in CoreData application"
+#: Setup/setup.xml:936 Setup/setup.xml:965
+msgid "Internal Contacts"
 msgstr ""
 
-#: Acl/Rights.php:134
-msgid "Manage list roles in CoreData"
+#: Setup/Initialize.php:117 Setup/Update/Release3.php:37
+#: Setup/Initialize.php:125
+msgid "All contacts I have read grants for"
 msgstr ""
 
-#: Acl/Rights.php:135
-msgid "View, create, delete or update list roles in CoreData application"
+#: Setup/Initialize.php:122 Setup/Initialize.php:130
+msgid "My company"
 msgstr ""
 
-#: Export/Pdf.php:37
-msgid "Business Contact Data"
+#: Setup/Initialize.php:123 Setup/Initialize.php:131
+msgid "All coworkers in my company"
 msgstr ""
 
-#: Export/Pdf.php:39
-msgid "Organisation / Unit"
+#: Setup/Initialize.php:136 Setup/Initialize.php:144
+msgid "My contacts"
 msgstr ""
 
-#: Export/Pdf.php:44
-msgid "Business Address"
+#: Setup/Initialize.php:137 Setup/Initialize.php:145
+msgid "All contacts in my Addressbooks"
 msgstr ""
 
-#: Export/Pdf.php:55
-msgid "Telephone Work"
+#: Setup/Initialize.php:149 Setup/Initialize.php:157
+msgid "Last modified by me"
 msgstr ""
 
-#: Export/Pdf.php:58
-msgid "Telephone Cellphone"
+#: Setup/Initialize.php:150 Setup/Initialize.php:158
+msgid "All contacts that I have last modified"
 msgstr ""
 
-#: Export/Pdf.php:61
-msgid "Telephone Car"
+#: js/ContactEditDialog.js:64 js/ContactEditDialog.js:79 js/ContactGrid.js:231
+#: js/Model.js:51
+msgid "Preferred Address"
 msgstr ""
 
-#: Export/Pdf.php:64
-msgid "Telephone Fax"
+#: js/ContactEditDialog.js:423 js/Model.js:83
+msgid "Groups"
 msgstr ""
 
-#: Export/Pdf.php:67
-msgid "Telephone Page"
+#: js/ContactEditDialog.js:474
+msgid "Print contact"
 msgstr ""
 
-#: Export/Pdf.php:70
-msgid "URL"
+#: js/ContactGridDetailsPanel.js:79 js/ContactGrid.js:187
+msgid "Business"
 msgstr ""
 
-#: Export/Pdf.php:73
-msgid "Role"
+#: js/ContactGrid.js:191
+msgid "Not set"
 msgstr ""
 
-#: Export/Pdf.php:79
-msgid "Assistant"
+#: js/Model.js:170
+msgid "Resource"
+msgid_plural "Resources"
+msgstr[0] ""
+msgstr[1] ""
+
+#: js/Model.js:170
+msgid "Resources"
 msgstr ""
 
-#: Export/Pdf.php:82
-msgid "Assistant Telephone"
+#: js/Printer/ListRecord.js:56
+msgid "Customfields"
 msgstr ""
 
-#: Export/Pdf.php:86
-msgid "Private Contact Data"
+#: js/Printer/ListRecord.js:67 js/Printer/ContactRecord.js:79
+msgid "Related to"
 msgstr ""
 
-#: Export/Pdf.php:96
-msgid "Email Home"
+#: js/ListEditDialog.js:55
+#, python-brace-format
+msgid "Print {0}"
 msgstr ""
 
-#: Export/Pdf.php:99
-msgid "Telephone Home"
+#: Config.php:107
+msgid "Shows an additional view for lists inside the addressbook"
 msgstr ""
 
-#: Export/Pdf.php:102
-msgid "Telephone Cellphone Private"
+#: Config.php:110
+msgid "Addressbook Industries"
 msgstr ""
 
-#: Export/Pdf.php:105
-msgid "Telephone Fax Home"
+#: Config.php:111
+msgid "Add Industry field to Adressbook"
 msgstr ""
 
-#: Export/Pdf.php:108
-msgid "URL Home"
+#: Config.php:114
+msgid "Manage resources in Addressbook"
 msgstr ""
 
-#: Export/Pdf.php:112
-msgid "Other Data"
+#: Config.php:115
+msgid "Show the resources grid also inside the Addressbook"
+msgstr ""
+
+#: Config.php:144
+msgid "Contact Hidden Criteria"
+msgstr ""
+
+#: Config.php:146
+msgid "The contact is hidden if it is ... (one of: disabled, expired or never"
+msgstr ""
+
+#: Config.php:206 Config.php:208
+msgid "Sync Backends"
 msgstr ""
 
 #: Export/definitions/adb_default_ods.xml:7
@@ -926,43 +1091,3 @@ msgstr ""
 #: Export/definitions/adb_doc_letter.xml:6
 msgid "Word letter"
 msgstr ""
-
-#: Preference.php:28
-msgid "All contacts"
-msgstr ""
-
-#: Preference.php:81
-msgid "Default Addressbook"
-msgstr ""
-
-#: Preference.php:82
-msgid "The default addressbook for new contacts"
-msgstr ""
-
-#: Preference.php:85
-msgid "Default Favorite"
-msgstr ""
-
-#: Preference.php:86
-msgid "The default favorite which is loaded on addressbook startup"
-msgstr ""
-
-#: Preference.php:89
-msgid "Contacts ODS export configuration"
-msgstr ""
-
-#: Preference.php:90
-msgid "Use this configuration for the contact ODS export."
-msgstr ""
-
-#: Preference.php:93
-msgid "Contacts XLS export configuration"
-msgstr ""
-
-#: Preference.php:94
-msgid "Use this configuration for the contact XLS export."
-msgstr ""
-
-#: Preference.php:165
-msgid "default"
-msgstr ""
index 219d384..0e3e9f9 100644 (file)
@@ -40,7 +40,7 @@ class Admin_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
     );
 
     /**
-     * create system groups for addressbook lists that dont have a system group
+     * create system groups for addressbook lists that don't have a system group
      *
      * @param $_opts
      */
index a28f933..a71878a 100644 (file)
@@ -12,7 +12,7 @@ class Admin_Setup_Update_Release9 extends Setup_Update_Abstract
 {
     /**
      * update to 10.0
-     * 
+     *
      * @return void
      */
     public function update_0()
index 324e8fd..bafccf8 100644 (file)
@@ -8,6 +8,7 @@ Andre Mueller
 Anne Wieland
 Antonio Carlos da Silva
 артур Мудрых
+APV - Technische Produkte GmbH
 Aurimas Driskius
 Bastiën Zittema
 Benjamin Klein
index bf68a72..1830e1d 100644 (file)
@@ -77,7 +77,14 @@ class Calendar_Config extends Tinebase_Config_Abstract
      * @var string
      */
     const MAX_FILTER_PERIOD_CALDAV = 'maxfilterperiodcaldav';
-    
+
+    /**
+     * MAX_FILTER_PERIOD_CALDAV_SYNCTOKEN
+     *
+     * @var string
+     */
+    const MAX_FILTER_PERIOD_CALDAV_SYNCTOKEN = 'maxfilterperiodcaldavsynctoken';
+
     /**
      * MAX_NOTIFICATION_PERIOD_FROM
      * 
@@ -328,6 +335,17 @@ class Calendar_Config extends Tinebase_Config_Abstract
             'setBySetupModule'      => TRUE,
             'default'               => 2,
         ),
+        self::MAX_FILTER_PERIOD_CALDAV_SYNCTOKEN => array(
+            //_('Filter timeslot for CalDAV events with SyncToken')
+            'label'                 => 'Filter timeslot for CalDAV events with SyncToken',
+            //_('For how long in the past (in months) the events should be synchronized.')
+            'description'           => 'For how long in the past (in months) the events should be synchronized.',
+            'type'                  => Tinebase_Config_Abstract::TYPE_INT,
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => FALSE,
+            'setBySetupModule'      => TRUE,
+            'default'               => 100,
+        ),
         self::MAX_NOTIFICATION_PERIOD_FROM => array(
         //_('Timeslot for event notifications')
             'label'                 => 'Timeslot for event notifications',
diff --git a/tine20/Calendar/Convert/Event/VCalendar/BusyCal.php b/tine20/Calendar/Convert/Event/VCalendar/BusyCal.php
new file mode 100644 (file)
index 0000000..cbb76a8
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Calendar
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Lars Kneschke <l.kneschke@metaways.de>
+ * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * class to convert a Mac OS X VCALENDAR to Tine 2.0 Calendar_Model_Event and back again
+ *
+ * @package     Calendar
+ * @subpackage  Convert
+ */
+class Calendar_Convert_Event_VCalendar_BusyCal extends Calendar_Convert_Event_VCalendar_Abstract
+{
+       // BusyCal-2.6.6
+    const HEADER_MATCH = '/^BusyCal-(?P<version>\S+)/';
+    
+    protected $_supportedFields = array(
+        'seq',
+        'dtend',
+        'transp',
+        'class',
+        'description',
+        #'geo',
+        'location',
+        'priority',
+        'summary',
+        'url',
+        'alarms',
+        'tags',
+        'dtstart',
+        'exdate',
+        'rrule',
+        'recurid',
+        'is_all_day_event',
+        'rrule_until',
+        'originator_tz',
+    );
+    
+    /**
+     * get attendee array for given contact
+     * 
+     * @param  \Sabre\VObject\Property\ICalendar\CalAddress  $calAddress  the attendee row from the vevent object
+     * @return array
+     */
+    protected function _getAttendee(\Sabre\VObject\Property\ICalendar\CalAddress $calAddress)
+    {
+        
+        $newAttendee = parent::_getAttendee($calAddress);
+        
+        // beginning with mavericks iCal adds organiser as attendee without role
+        // so we remove attendee without role 
+        // @TODO check if this attendee is currentuser & organizer?
+        if (version_compare($this->_version, '10.9', '>=')) {
+            if (! isset($calAddress['ROLE'])) {
+                return NULL;
+            }
+        }
+        
+        return $newAttendee;
+    }
+
+    /**
+     * do version specific magic here
+     *
+     * @param \Sabre\VObject\Component\VCalendar $vcalendar
+     * @return \Sabre\VObject\Component\VCalendar | null
+     */
+    protected function _findMainEvent(\Sabre\VObject\Component\VCalendar $vcalendar)
+    {
+        $return = parent::_findMainEvent($vcalendar);
+
+        // NOTE 10.7 and 10.10 sometimes write access into calendar property
+        if (isset($vcalendar->{'X-CALENDARSERVER-ACCESS'})) {
+            foreach ($vcalendar->VEVENT as $vevent) {
+                $vevent->{'X-CALENDARSERVER-ACCESS'} = $vcalendar->{'X-CALENDARSERVER-ACCESS'};
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * parse VEVENT part of VCALENDAR
+     *
+     * @param  \Sabre\VObject\Component\VEvent  $vevent  the VEVENT to parse
+     * @param  Calendar_Model_Event             $event   the Tine 2.0 event to update
+     * @param  array                            $options
+     */
+    protected function _convertVevent(\Sabre\VObject\Component\VEvent $vevent, Calendar_Model_Event $event, $options)
+    {
+        $return = parent::_convertVevent($vevent, $event, $options);
+
+        // NOTE: 10.7 sometimes uses (internal?) int's
+        if (isset($vevent->{'X-CALENDARSERVER-ACCESS'}) && (int) (string) $vevent->{'X-CALENDARSERVER-ACCESS'} > 0) {
+            $event->class = (int) (string) $vevent->{'X-CALENDARSERVER-ACCESS'} == 1 ?
+                Calendar_Model_Event::CLASS_PUBLIC :
+                Calendar_Model_Event::CLASS_PRIVATE;
+        }
+
+        return $return;
+    }
+
+    /**
+     * iCal supports manged attachments
+     *
+     * @param Calendar_Model_Event          $event
+     * @param Tinebase_Record_RecordSet     $attachments
+     */
+    protected function _manageAttachmentsFromClient($event, $attachments)
+    {
+        $event->attachments = $attachments;
+    }
+}
index 309a0ee..79217fb 100644 (file)
@@ -26,7 +26,8 @@ class Calendar_Convert_Event_VCalendar_Factory
     const CLIENT_EMCLIENT7   = 'emclient7';
     const CLIENT_TINE        = 'tine';
     const CLIENT_DAVDROID    = 'davdroid';
-       const CLIENT_CALDAVSYNCHRONIZER = 'caldavsynchronizer';
+    const CLIENT_BUSYCAL        = 'busycal';
+    const CLIENT_CALDAVSYNCHRONIZER = 'caldavsynchronizer';
     
     /**
      * cache parsed user-agent strings
@@ -50,7 +51,9 @@ class Calendar_Convert_Event_VCalendar_Factory
                 
             case Calendar_Convert_Event_VCalendar_Factory::CLIENT_IPHONE:
                 return new Calendar_Convert_Event_VCalendar_Iphone($_version);
-
+                
+            case Calendar_Convert_Event_VCalendar_Factory::CLIENT_BUSYCAL:
+                return new Calendar_Convert_Event_VCalendar_BusyCal($_version);
                 
             case Calendar_Convert_Event_VCalendar_Factory::CLIENT_KDE:
                 return new Calendar_Convert_Event_VCalendar_KDE($_version);
@@ -101,6 +104,11 @@ class Calendar_Convert_Event_VCalendar_Factory
             $backend = Calendar_Convert_Event_VCalendar_Factory::CLIENT_IPHONE;
             $version = $matches['version'];
         
+        // BusyCal
+        } elseif (preg_match(Calendar_Convert_Event_VCalendar_BusyCal::HEADER_MATCH, $_userAgent, $matches)) {
+            $backend = Calendar_Convert_Event_VCalendar_Factory::CLIENT_BUSYCAL;
+            $version = $matches['version'];
+                
         // KDE
         } elseif (preg_match(Calendar_Convert_Event_VCalendar_KDE::HEADER_MATCH, $_userAgent, $matches)) {
             $backend = Calendar_Convert_Event_VCalendar_Factory::CLIENT_KDE;
@@ -135,7 +143,6 @@ class Calendar_Convert_Event_VCalendar_Factory
         } elseif (preg_match(Calendar_Convert_Event_VCalendar_CalDAVSynchronizer::HEADER_MATCH, $_userAgent, $matches)) {
             $backend = Calendar_Convert_Event_VCalendar_Factory::CLIENT_CALDAVSYNCHRONIZER;
             $version = $matches['version'];
-                       
         } else {
             $backend = Calendar_Convert_Event_VCalendar_Factory::CLIENT_GENERIC;
             $version = null;
@@ -152,18 +159,38 @@ class Calendar_Convert_Event_VCalendar_Factory
     /**
      * parse useragent and return backend and version
      *
-     * @return array
+     * @param string $_userAgent
+     * @return boolean
      */
     static public function supportsSyncToken($_userAgent)
     {
-        list($backend, $version) = self::parseUserAgent($_userAgent);
-        switch($backend)
-        {
-            case self::CLIENT_MACOSX:
-                if (version_compare($version, '10.9', '>='))
-                    return true;
-                break;
+        $result = false;
+        if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
+            list($backend, $version) = self::parseUserAgent($_userAgent);
+            switch ($backend) {
+                case self::CLIENT_MACOSX:
+                    if (version_compare($version, '10.9', '>=')) {
+                        $result = true;
+                    }
+                    break;
+                case self::CLIENT_THUNDERBIRD:
+                    if (version_compare($version, '4', '>=')) {
+                        $result = true;
+                    }
+                    break;
+            }
+
+            if ($result) {
+                if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
+                    Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
+                        ' Client ' . $backend . ' version ' . $version . ' supports SyncToken.');
+            }
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
         }
-        return false;
+        return $result;
     }
 }
index 0c65959..4ec6924 100644 (file)
@@ -31,8 +31,6 @@ class Calendar_Frontend_WebDAV_Container extends Tinebase_WebDav_Container_Abstr
      */
     protected $_calendarQueryCache = null;
 
-
-
     /**
      * (non-PHPdoc)
      * @see Sabre\DAV\Collection::getChild()
@@ -170,7 +168,6 @@ class Calendar_Frontend_WebDAV_Container extends Tinebase_WebDav_Container_Abstr
         
         $properties = array(
             '{http://calendarserver.org/ns/}getctag' => $ctags,
-            '{DAV:}sync-token'  => Tinebase_WebDav_Plugin_SyncToken::SYNCTOKEN_PREFIX . $ctags,
             'id'                => $this->_container->getId(),
             'uri'               => $this->_useIdAsName == true ? $this->_container->getId() : $this->_container->name,
             '{DAV:}resource-id' => 'urn:uuid:' . $this->_container->getId(),
@@ -184,7 +181,14 @@ class Calendar_Frontend_WebDAV_Container extends Tinebase_WebDav_Container_Abstr
             '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-description'             => 'Calendar ' . $this->_container->name,
             '{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-timezone'                => Tinebase_WebDav_Container_Abstract::getCalendarVTimezone($this->_application)
         );
-        
+        if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
+            $properties['{DAV:}sync-token'] = Tinebase_WebDav_Plugin_SyncToken::SYNCTOKEN_PREFIX . $ctags;
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
+        }
         if (!empty(Tinebase_Core::getUser()->accountEmailAddress)) {
             $properties['{' . Sabre\CalDAV\Plugin::NS_CALDAV . '}calendar-user-address-set'    ] = new Sabre\DAV\Property\HrefList(array('mailto:' . Tinebase_Core::getUser()->accountEmailAddress), false);
         }
@@ -469,7 +473,14 @@ class Calendar_Frontend_WebDAV_Container extends Tinebase_WebDav_Container_Abstr
      */
     public function supportsSyncToken()
     {
-        return true;
+        if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
+            return true;
+        }
+        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
+        return false;
     }
 
     /**
diff --git a/tine20/CoreData/Setup/Update/Release0.php b/tine20/CoreData/Setup/Update/Release0.php
new file mode 100644 (file)
index 0000000..e6bd960
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     CoreData
+ * @subpackage  Setup
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL3
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author      Stefanie Stamer <s.stamer@metaways.de>
+ */
+
+class CoreData_Setup_Update_Release0 extends Setup_Update_Abstract
+{
+    /**
+     * update to 10.0
+     * @return void
+     */
+    public function update_0()
+    {
+        $this->setApplicationVersion('CoreData', '10.0');
+    }
+}
index f608d93..87f7ba9 100644 (file)
@@ -87,6 +87,14 @@ class Felamimail_Config extends Tinebase_Config_Abstract
     const VACATION_CUSTOM_MESSAGE_ALLOWED = 'vacationMessageCustomAllowed';
 
     /**
+     * allow self signed tls cert for IMAP connection
+     *
+     * @see 0009676: activate certificate check for TLS/SSL
+     * @var string
+     */
+    const IMAP_ALLOW_SELF_SIGNED_TLS_CERT = 'imapAllowSelfSignedTlsCert';
+
+    /**
      * (non-PHPdoc)
      * @see tine20/Tinebase/Config/Definition::$_properties
      */
@@ -189,6 +197,16 @@ class Felamimail_Config extends Tinebase_Config_Abstract
             'setBySetupModule'      => TRUE,
             'default'               => null,
         ),
+        self::IMAP_ALLOW_SELF_SIGNED_TLS_CERT => array(
+            //_('Allow self signed TLS cert for IMAP connection')
+            'label'                 => 'Allow self signed TLS cert for IMAP connection',
+            'description'           => '',
+            'type'                  => Tinebase_Config_Abstract::TYPE_BOOL,
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => FALSE,
+            'setBySetupModule'      => TRUE,
+            'default'               => false,
+        ),
         self::SIEVE_REDIRECT_ONLY_INTERNAL => array(
             //_('Sieve Redirect Only Internal')
             'label'                 => 'Sieve Redirect Only Internal',
@@ -210,7 +228,7 @@ class Felamimail_Config extends Tinebase_Config_Abstract
             'setByAdminModule'      => true,
             'setBySetupModule'      => false,
             'default'               => null,
-        )
+        ),
     );
     
     /**
index 7b4a125..d0ae03c 100644 (file)
@@ -996,7 +996,7 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
     /**
      * get max attachment size for outgoing mails
      * 
-     * - currently it is set to memory_limit / 10
+     * - currently it is set to memory_limit
      * - returns size in Bytes
      * 
      * @return integer
@@ -1013,6 +1013,6 @@ class Felamimail_Controller_Message_Send extends Felamimail_Controller_Message
             $configuredMemoryLimit = '512M';
         }
         
-        return Tinebase_Helper::convertToBytes($configuredMemoryLimit) / 10;
+        return Tinebase_Helper::convertToBytes($configuredMemoryLimit);
     }
 }
diff --git a/tine20/Tasks/Convert/Task/VCalendar/DavDroid.php b/tine20/Tasks/Convert/Task/VCalendar/DavDroid.php
new file mode 100644 (file)
index 0000000..c29383c
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Calendar
+ * @subpackage  Convert
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Ingo Ratsdorf <ingo@envirology.co.nz>
+ * @copyright   Copyright (c) 2011-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * class to convert a iphone vcalendar to event model and back again
+ *
+ * @package     Tasks
+ * @subpackage  Convert
+ */
+class Tasks_Convert_Task_VCalendar_DavDroid extends Tasks_Convert_Task_VCalendar_Abstract
+{
+    // DAVdroid/0.7.2
+    const HEADER_MATCH = '/DAVdroid\/(?P<version>.*)/';
+    
+    protected $_supportedFields = array(
+        'seq',
+        'dtstart',
+        #'transp',
+        'class',
+        'description',
+        'geo',
+        'location',
+        'priority',
+        'summary',
+        'url',
+        'alarms',
+        'tags',
+        'status',
+        'due',
+        'percent',
+        'completed',
+        'originator_tz'
+    );
+}
index 611fb86..1f6f77b 100644 (file)
@@ -24,7 +24,8 @@ class Tasks_Convert_Task_VCalendar_Factory
     const CLIENT_MACOSX      = 'macosx';
     const CLIENT_THUNDERBIRD = 'thunderbird';
     const CLIENT_EMCLIENT    = 'emclient';
-       const CLIENT_CALDAVSYNCHRONIZER = 'caldavsynchronizer';
+    const CLIENT_CALDAVSYNCHRONIZER = 'caldavsynchronizer';
+    const CLIENT_DAVDROID    = 'davdroid';
     
     /**
      * cache parsed user-agent strings
@@ -60,10 +61,12 @@ class Tasks_Convert_Task_VCalendar_Factory
                  
             case Tasks_Convert_Task_VCalendar_Factory::CLIENT_EMCLIENT:
                 return new Tasks_Convert_Task_VCalendar_EMClient($_version);
-                
-                       case Calendar_Convert_Event_VCalendar_Factory::CLIENT_CALDAVSYNCHRONIZER:
+
+            case Calendar_Convert_Event_VCalendar_Factory::CLIENT_CALDAVSYNCHRONIZER:
                 return new Tasks_Convert_Task_VCalendar_CalDAVSynchronizer($_version);
-                
+
+            case Tasks_Convert_Task_VCalendar_Factory::CLIENT_DAVDROID:
+                return new Tasks_Convert_Task_VCalendar_DavDroid($_version);
         }
                return new Tasks_Convert_Task_VCalendar_Generic($_version);
     }
@@ -99,16 +102,21 @@ class Tasks_Convert_Task_VCalendar_Factory
             $backend = Tasks_Convert_Task_VCalendar_Factory::CLIENT_THUNDERBIRD;
             $version = $matches['version'];
         
-         // EMClient       
+        // EMClient       
         } elseif (preg_match(Tasks_Convert_Task_VCalendar_EMClient::HEADER_MATCH, $_userAgent, $matches)) {
             $backend = Tasks_Convert_Task_VCalendar_Factory::CLIENT_EMCLIENT;
             $version = $matches['version'];
-                       
-               // CalDAVSynchronizer
+
+        // CalDAVSynchronizer
         } elseif (preg_match(Tasks_Convert_Task_VCalendar_CalDAVSynchronizer::HEADER_MATCH, $_userAgent, $matches)) {
             $backend = Tasks_Convert_Task_VCalendar_Factory::CLIENT_CALDAVSYNCHRONIZER;
             $version = $matches['version'];
         
+        // DavDroid
+        } elseif (preg_match(Tasks_Convert_Task_VCalendar_DavDroid::HEADER_MATCH, $_userAgent, $matches)) {
+            $backend = Tasks_Convert_Task_VCalendar_Factory::CLIENT_DAVDROID;
+            $version = $matches['version'];
+        
         } else {
             $backend = Tasks_Convert_Task_VCalendar_Factory::CLIENT_GENERIC;
             $version = null;
index c0e453a..ac9324f 100644 (file)
@@ -59,7 +59,7 @@ class Timetracker_Acl_Rights extends Tinebase_Acl_Rights_Abstract
      *
      * disabled. use the singleton
      */
-    private function __clone() 
+    protected function __clone()
     {
     }
     
@@ -67,7 +67,7 @@ class Timetracker_Acl_Rights extends Tinebase_Acl_Rights_Abstract
      * the constructor
      *
      */
-    private function __construct()
+    protected function __construct()
     {
         
     }    
@@ -132,7 +132,7 @@ class Timetracker_Acl_Rights extends Tinebase_Acl_Rights_Abstract
             self::MANAGE_SHARED_TIMESHEET_FAVORITES => array(
                 'text'          => $translate->_('Manage shared timesheet favorites'),
                 'description'   => $translate->_('Create or update shared timesheet favorites'),
-            ),
+            )
         );
         
         $rightDescriptions = array_merge($rightDescriptions, parent::getTranslatedRightDescriptions());
diff --git a/tine20/Timetracker/Backend/TimeaccountFavorites.php b/tine20/Timetracker/Backend/TimeaccountFavorites.php
new file mode 100644 (file)
index 0000000..98d6a9b
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Timetracker
+ * @subpackage  Backend
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * backend for TimeaccountFavorites
+ *
+ * @package     Timetracker
+ * @subpackage  Backend
+ */
+class Timetracker_Backend_TimeaccountFavorites extends Tinebase_Backend_Sql_Abstract
+{
+    /**
+     * @var bool
+     */
+    protected $_modlogActive = true;
+
+    /**
+     * @var string
+     */
+    protected $_modelName = 'Timetracker_Model_TimeaccountFavorite';
+
+    /**
+     * @var string
+     */
+    protected $_tableName = 'timetracker_timeaccount_fav';
+}
diff --git a/tine20/Timetracker/Config.php b/tine20/Timetracker/Config.php
new file mode 100644 (file)
index 0000000..e3afb47
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Sales
+ * @subpackage  Config
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Sales config class
+ *
+ * @package     Timetracker
+ * @subpackage  Config
+ *
+ */
+class Timetracker_Config extends Tinebase_Config_Abstract
+{
+    /**
+     * Feature bookmark for timeaccounts
+     */
+    const FEATURE_TIMEACCOUNT_BOOKMARK = 'featureTimeaccountBookmark';
+
+    /**
+     * @var array
+     */
+    protected static $_properties = array(
+        self::ENABLED_FEATURES => array(
+            //_('Enabled Features')
+            'label' => 'Enabled Features',
+            //_('Enabled Features in Timetracker Application.')
+            'description' => 'Enabled Features in Timetracker Application.',
+            'type' => 'object',
+            'class' => 'Tinebase_Config_Struct',
+            'clientRegistryInclude' => true,
+            'content' => array(
+                self::FEATURE_TIMEACCOUNT_BOOKMARK => array(
+                    'label' => 'Timeaccount Bookmarks', //_('Timeaccount Bookmarks')
+                    'description' => 'Add timeaccounts as favorite to speedup timesheet creation.', //_('Add timeaccounts as favorite to speedup timesheet creation.)
+                ),
+            ),
+            'default' => array(
+                self::FEATURE_TIMEACCOUNT_BOOKMARK => true,
+            ),
+        )
+    );
+    /**
+     * holds the instance of the singleton
+     *
+     * @var Tinebase_Config
+     */
+    private static $_instance = null;
+    /**
+     * (non-PHPdoc)
+     * @see tine20/Tinebase/Config/Abstract::$_appName
+     */
+    protected $_appName = 'Timetracker';
+
+    /**
+     * the constructor
+     *
+     * don't use the constructor. use the singleton
+     */
+    protected function __construct()
+    {
+    }
+
+    /**
+     * Returns instance of Tinebase_Config
+     *
+     * @return Tinebase_Config
+     */
+    public static function getInstance()
+    {
+        if (self::$_instance === null) {
+            self::$_instance = new self();
+        }
+
+        return self::$_instance;
+    }
+
+    /**
+     * (non-PHPdoc)
+     * @see tine20/Tinebase/Config/Abstract::getProperties()
+     */
+    public static function getProperties()
+    {
+        return self::$_properties;
+    }
+
+    /**
+     * the constructor
+     *
+     * don't use the constructor. use the singleton
+     */
+    protected function __clone()
+    {
+    }
+}
index a3f95dc..f3bc458 100644 (file)
@@ -36,14 +36,14 @@ class Timetracker_Controller extends Tinebase_Controller_Abstract
      *
      * don't use the constructor. use the singleton 
      */
-    private function __construct() {
+    protected function __construct() {
     }
     
     /**
      * don't clone. Use the singleton.
      *
      */
-    private function __clone() 
+    protected function __clone()
     {
     }
     
diff --git a/tine20/Timetracker/Controller/TimeaccountFavorites.php b/tine20/Timetracker/Controller/TimeaccountFavorites.php
new file mode 100644 (file)
index 0000000..e247131
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Timeaccount controller for Timetracker application
+ *
+ * @package     Timetracker
+ * @subpackage  Controller
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ *
+ */
+
+/**
+ * TimeaccountFavorites controller class for Timetracker application
+ *
+ * @package     Timetracker
+ * @subpackage  Controller
+ */
+class Timetracker_Controller_TimeaccountFavorites extends Tinebase_Controller_Record_Abstract
+{
+    /**
+     * holds the instance of the singleton
+     *
+     * @var Timetracker_Controller_Timeaccount
+     */
+    private static $_instance = null;
+
+    /**
+     * Disable container acl checks, favorites does not have any container.
+     *
+     * @var bool
+     */
+    protected $_doContainerACLChecks = false;
+
+    /**
+     * Timetracker_Controller_TimeaccountFavorites constructor.
+     * @throws \Tinebase_Exception_Backend_Database
+     */
+    protected function __construct()
+    {
+        $this->_applicationName = 'Timetracker';
+        $this->_backend = new Timetracker_Backend_TimeaccountFavorites();
+        $this->_modelName = 'Timetracker_Model_TimeaccountFavorite';
+        $this->_purgeRecords = false;
+        $this->_resolveCustomFields = true;
+    }
+
+    private function __clone()
+    {
+    }
+
+    /**
+     * the singleton pattern
+     *
+     * @return Timetracker_Controller_Timeaccount
+     */
+    public static function getInstance()
+    {
+        if (self::$_instance === null) {
+            self::$_instance = new self();
+        }
+
+        return self::$_instance;
+    }
+}
index 9340910..42cf675 100644 (file)
@@ -17,8 +17,6 @@
             <identifier>description</identifier>
             <header>Description</header>
             <type>string</type>
-            <maxcharacters>250</maxcharacters>
-            <maxlines>30</maxlines>
         </column>
         <column>
             <identifier>timeaccount_number</identifier>
index 3f29af7..01ba4fe 100644 (file)
@@ -234,6 +234,98 @@ class Timetracker_Frontend_Json extends Tinebase_Frontend_Json_Abstract
          }
     }
 
+    /**
+     * Return registry data for timeaccount favorites
+     *
+     * @return array
+     * @throws \Tinebase_Exception_InvalidArgument
+     */
+    public function getTimeAccountFavoriteRegistry()
+    {
+        $appPrefs = Tinebase_Core::getPreference($this->_applicationName);
+
+        // Get preference
+        $quickTagPreferences = $appPrefs->search(
+            new Tinebase_Model_PreferenceFilter([
+                'name' => Timetracker_Preference::QUICKTAG
+            ])
+        );
+
+        // There could be only one result, if not do nothing.
+        if ($quickTagPreferences->count() !== 1) {
+            return null;
+        }
+
+        $quickTagPreference = $quickTagPreferences->getFirstRecord();
+
+        if ($quickTagPreference->value === false) {
+            return null;
+        }
+
+        // Resolve tag by it's id
+        $tag = Tinebase_Tags::getInstance()->get($quickTagPreference->value);
+
+        $pref = array();
+        $pref['quicktagId'] = $quickTagPreference->value;
+        $pref['quicktagName'] = $tag->name;
+
+        return $pref;
+    }
+
+    /**
+     * Return registry data
+     *
+     * @return array
+     * @throws \Tinebase_Exception_InvalidArgument
+     */
+    public function getRegistryData()
+    {
+        $registry = [];
+
+        if (Timetracker_Config::getInstance()->featureEnabled(Timetracker_Config::FEATURE_TIMEACCOUNT_BOOKMARK)) {
+            $registry = array_merge($registry, $this->getOwnTimeAccountBookmarks());
+        }
+
+        $timeaccountFavorites = $this->getTimeAccountFavoriteRegistry();
+
+        if ($timeaccountFavorites !== null) {
+            $registry = array_merge($registry, $this->getTimeAccountFavoriteRegistry());
+        }
+
+        return $registry;
+    }
+
+    /**
+     * @return array
+     */
+    protected function getOwnTimeAccountBookmarks() {
+        $ownFavoritesFilter = new Timetracker_Model_TimeaccountFavoriteFilter([
+            'account_id' => Tinebase_Core::getUser()->accountId,
+        ]);
+
+        $timeAccountFavs = Timetracker_Controller_TimeaccountFavorites::getInstance()->search($ownFavoritesFilter);
+        $timeAccountFavsArray = [];
+
+        foreach($timeAccountFavs as $timeAccountFav) {
+            $timeaccount = Timetracker_Controller_Timeaccount::getInstance()->get($timeAccountFav->timeaccount_id);
+
+            // timeaccount will be used to set the defaults for opening new timesheet record in frontend
+            // Resolve here to save loading time
+            $timeAccountFavsArray[] = [
+                'timeaccount' => $timeaccount->toArray(),
+                'favId' => $timeAccountFav->id,
+                'text' => $timeaccount->title,
+                'leaf' => true,
+                'iconCls' => 'task'
+            ];
+        }
+
+        $pref = array();
+        $pref['timeaccountFavorites'] = $timeAccountFavsArray;
+
+        return $pref;
+    }
+
     /************************************** public API **************************************/
 
     /**
@@ -334,4 +426,37 @@ class Timetracker_Frontend_Json extends Tinebase_Frontend_Json_Abstract
     {
         return $this->_delete($ids, $this->_timeaccountController);
     }
+
+    /**
+     * Add given timeaccount id as a users favorite
+     *
+     * @param $timeaccountId
+     * @return Timetracker_Model_Timeaccount
+     */
+    public function addTimeAccountFavorite($timeaccountId)
+    {
+        $timeaccount = new Timetracker_Model_TimeaccountFavorite();
+        $timeaccount->timeaccount_id = $timeaccountId;
+        $timeaccount->account_id = Tinebase_Core::getUser()->accountId;
+
+        Timetracker_Controller_TimeaccountFavorites::getInstance()->create($timeaccount);
+
+        return $this->getOwnTimeAccountBookmarks();
+    }
+
+    /**
+     * Delete given timeaccount favorite
+     *
+     * @param $favId
+     * @return Tinebase_Record_RecordSet
+     * @throws \Tinebase_Exception
+     */
+    public function deleteTimeAccountFavorite($favId)
+    {
+        Timetracker_Controller_TimeaccountFavorites::getInstance()->delete([
+            $favId
+        ]);
+
+        return $this->getOwnTimeAccountBookmarks();
+    }
 }
diff --git a/tine20/Timetracker/Model/TimeaccountFavorite.php b/tine20/Timetracker/Model/TimeaccountFavorite.php
new file mode 100644 (file)
index 0000000..96fef40
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+/**
+ * class to hold Timeaccount data
+ *
+ * @package     Timetracker
+ * @subpackage  Model
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * class to hold Timeaccount data
+ *
+ * @package     Timetracker
+ * @subpackage  Model
+ */
+class Timetracker_Model_TimeaccountFavorite extends Tinebase_Record_Abstract
+{
+    /**
+     * key in $_validators/$_properties array for the filed which
+     * represents the identifier
+     *
+     * @var string
+     */
+    protected $_identifier = 'id';
+
+    /**
+     * application the record belongs to
+     *
+     * @var string
+     */
+    protected $_application = 'Timetracker';
+
+    /**
+     * Validators
+     *
+     * @var array
+     */
+    protected $_validators = array (
+        // tine 2.0 generic fields
+        'id'                    => array(Zend_Filter_Input::ALLOW_EMPTY => true, Zend_Filter_Input::DEFAULT_VALUE => NULL),
+        'created_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'creation_time'         => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'last_modified_by'      => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'last_modified_time'    => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'is_deleted'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'deleted_time'          => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+        'deleted_by'            => array(Zend_Filter_Input::ALLOW_EMPTY => true),
+
+        // model specific fields
+        'account_id'      => array(Zend_Filter_Input::ALLOW_EMPTY => false, Zend_Filter_Input::DEFAULT_VALUE => NULL),
+        'timeaccount_id'  => array('presence' => 'required')
+    );
+}
diff --git a/tine20/Timetracker/Model/TimeaccountFavoriteFilter.php b/tine20/Timetracker/Model/TimeaccountFavoriteFilter.php
new file mode 100644 (file)
index 0000000..f404fb4
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Tine 2.0
+ *
+ * @package     Timetracker
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 2007-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+/**
+ * Timeaccount favorite filter Class
+ *
+ * @package Timetracker
+ */
+class Timetracker_Model_TimeaccountFavoriteFilter extends Tinebase_Model_Filter_FilterGroup implements Tinebase_Model_Filter_AclFilter
+{
+    /**
+     * @var string
+     */
+    protected $_className = 'Timetracker_Model_TimeaccountFavoriteFilter';
+
+    /**
+     * @var string
+     */
+    protected $_applicationName = 'Timetracker';
+
+    /**
+     * @var string
+     */
+    protected $_modelName = 'TimeaccountFavorite';
+
+    /**
+     * @var array filter model fieldName => definition
+     */
+    protected $_filterModel = array(
+        'account_id'      => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'timeaccount_id'  => array('filter' => 'Tinebase_Model_Filter_Id'),
+        'id'              => array('filter' => 'Tinebase_Model_Filter_Id'),
+    );
+}
index c8ee0ed..06a6965 100644 (file)
@@ -68,6 +68,7 @@ class Timetracker_Model_TimeaccountGrants extends Tinebase_Model_Grants
             self::VIEW_ALL,
             self::BOOK_ALL,
             self::MANAGE_BILLABLE,
+            self::GRANT_EDIT,
             Tinebase_Model_Grants::GRANT_EXPORT,
             Tinebase_Model_Grants::GRANT_ADMIN,
         );
index c3572b0..8cdc696 100644 (file)
@@ -28,6 +28,11 @@ class Timetracker_Preference extends Tinebase_Preference_Abstract
     const TSODSEXPORTCONFIG = 'tsOdsExportConfig';
 
     /**
+     * Quicktag
+     */
+    const QUICKTAG = 'quickTag';
+
+    /**
      * application
      *
      * @var string
@@ -45,6 +50,7 @@ class Timetracker_Preference extends Tinebase_Preference_Abstract
     {
         $allPrefs = array(
             self::TSODSEXPORTCONFIG,
+            self::QUICKTAG
         );
             
         return $allPrefs;
@@ -64,6 +70,10 @@ class Timetracker_Preference extends Tinebase_Preference_Abstract
                 'label'         => $translate->_('Timesheets ODS export configuration'),
                 'description'   => $translate->_('Use this configuration for the timesheet ODS export.'),
             ),
+            self::QUICKTAG => array(
+                'label'         => $translate->_('A Tag which is available in context menu for fast assignment'),
+                'description'   => $translate->_('Quick Tag allows to simply assign a predefined tag by the context menu.')
+            )
         );
         
         return $prefDescriptions;
@@ -83,6 +93,9 @@ class Timetracker_Preference extends Tinebase_Preference_Abstract
             case self::TSODSEXPORTCONFIG:
                 $preference->value      = 'ts_default_ods';
                 break;
+            case self::QUICKTAG:
+                $preference->value      = 'false';
+                break;
             default:
                 throw new Tinebase_Exception_NotFound('Default preference with name ' . $_preferenceName . ' not found.');
         }
@@ -116,6 +129,24 @@ class Timetracker_Preference extends Tinebase_Preference_Abstract
                     $result[] = array('default', $translate->_('default'));
                 }
                 break;
+
+            case self::QUICKTAG:
+                // Get all shared tags
+                $tagController = Tinebase_Tags::getInstance();
+                $filter = new Tinebase_Model_TagFilter(array(
+                    'type' => Tinebase_Model_Tag::TYPE_SHARED,
+                ));
+                $tags = $tagController->searchTags($filter);
+
+                $availableTags = array();
+
+                /* @var $tag Tinebase_Model_Tag */
+                foreach($tags as $tag) {
+                    $availableTags[] = array($tag->id, $tag->name);
+                }
+
+                return $availableTags;
+                break;
             default:
                 $result = parent::_getSpecialOptions($_value);
         }
index 9f29b2c..51042e2 100644 (file)
@@ -17,23 +17,25 @@ class Timetracker_Setup_Update_Release10 extends Setup_Update_Abstract
      */
     public function update_0()
     {
-        $declaration = new Setup_Backend_Schema_Index_Xml('
-            <index>
-                <name>description</name>
-                <fulltext>true</fulltext>
-                <field>
+        if ($this->getTableVersion('timetracker_timesheet') < 6) {
+            $declaration = new Setup_Backend_Schema_Index_Xml('
+                <index>
                     <name>description</name>
-                </field>
-            </index>
-        ');
+                    <fulltext>true</fulltext>
+                    <field>
+                        <name>description</name>
+                    </field>
+                </index>
+            ');
 
-        try {
-            $this->_backend->addIndex('timetracker_timesheet', $declaration);
-        } catch (Exception $e) {
-            Tinebase_Exception::log($e);
-        }
+            try {
+                $this->_backend->addIndex('timetracker_timesheet', $declaration);
+            } catch (Exception $e) {
+                Tinebase_Exception::log($e);
+            }
 
-        $this->setTableVersion('timetracker_timesheet', '6');
+            $this->setTableVersion('timetracker_timesheet', '6');
+        }
         $this->setApplicationVersion('Timetracker', '10.1');
     }
 
@@ -44,23 +46,24 @@ class Timetracker_Setup_Update_Release10 extends Setup_Update_Abstract
      */
     public function update_1()
     {
-        $declaration = new Setup_Backend_Schema_Index_Xml('
-            <index>
-                <name>description</name>
-                <fulltext>true</fulltext>
-                <field>
+        if ($this->getTableVersion('timetracker_timesheet') < 11) {
+            $declaration = new Setup_Backend_Schema_Index_Xml('
+                <index>
                     <name>description</name>
-                </field>
-            </index>
-        ');
+                    <fulltext>true</fulltext>
+                    <field>
+                        <name>description</name>
+                    </field>
+                </index>
+            ');
 
-        try {
-            $this->_backend->addIndex('timetracker_timeaccount', $declaration);
-        } catch (Exception $e) {
-            Tinebase_Exception::log($e);
+            try {
+                $this->_backend->addIndex('timetracker_timeaccount', $declaration);
+            } catch (Exception $e) {
+                Tinebase_Exception::log($e);
+            }
+            $this->setTableVersion('timetracker_timeaccount', '11');
         }
-
-        $this->setTableVersion('timetracker_timeaccount', '11');
         $this->setApplicationVersion('Timetracker', '10.2');
     }
 
@@ -68,4 +71,127 @@ class Timetracker_Setup_Update_Release10 extends Setup_Update_Abstract
     {
         $this->setApplicationVersion('Timetracker', '10.3');
     }
+
+    /**
+     * update to 10.4
+     *
+     * @return void
+     */
+    public function update_3()
+    {
+        if (! $this->_backend->tableExists('timetracker_timeaccount_fav')) {
+            $declaration = new Setup_Backend_Schema_Table_Xml('<table>
+                <name>timetracker_timeaccount_fav</name>
+                <version>1</version>
+                <declaration>
+                    <field>
+                        <name>id</name>
+                        <type>text</type>
+                        <length>40</length>
+                        <notnull>true</notnull>
+                    </field>
+                    <field>
+                        <name>account_id</name>
+                        <type>text</type>
+                        <length>40</length>
+                        <notnull>true</notnull>
+                    </field>
+                    <field>
+                        <name>timeaccount_id</name>
+                        <type>text</type>
+                        <length>40</length>
+                        <notnull>false</notnull>
+                    </field>
+                    <field>
+                        <name>created_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>creation_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <field>
+                        <name>last_modified_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>last_modified_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <field>
+                        <name>is_deleted</name>
+                        <type>boolean</type>
+                        <default>false</default>
+                    </field>
+                    <field>
+                        <name>deleted_by</name>
+                        <type>text</type>
+                        <length>40</length>
+                    </field>
+                    <field>
+                        <name>deleted_time</name>
+                        <type>datetime</type>
+                    </field>
+                    <index>
+                        <name>id</name>
+                        <primary>true</primary>
+                        <field>
+                            <name>id</name>
+                        </field>
+                    </index>
+                    <index>
+                        <name>timesheet_favorites--timesheet_id::id</name>
+                        <field>
+                            <name>timeaccount_id</name>
+                        </field>
+                        <foreign>true</foreign>
+                        <reference>
+                            <table>timetracker_timeaccount</table>
+                            <field>id</field>
+                        </reference>
+                    </index>
+                    <index>
+                        <name>timesheet_favorites--account_id::id</name>
+                        <field>
+                            <name>account_id</name>
+                        </field>
+                        <foreign>true</foreign>
+                        <reference>
+                            <table>accounts</table>
+                            <field>id</field>
+                            <ondelete>CASCADE</ondelete>
+                        </reference>
+                    </index>
+                </declaration>
+            </table>');
+
+            $this->_backend->createTable($declaration, 'Timetracker', 'timetracker_timeaccount_fav');
+            $this->setTableVersion('timetracker_timeaccount_fav', 1);
+        }
+        $this->setApplicationVersion('Timetracker', '10.4');
+    }
+
+    /**
+     * 0012470: Don't shorten description in export
+     */
+    public function update_4()
+    {
+        Setup_Controller::getInstance()->createImportExportDefinitions(Tinebase_Application::getInstance()->getApplicationByName('Timetracker'));
+        $this->setApplicationVersion('Timetracker', '10.5');
+    }
+
+    /**
+     * update to 10.6
+     *
+     * Add fulltext index to field description of timesheet
+     * - re-run update 0 + 1 because 2017.02 added update 2 + 3
+     */
+    public function update_5()
+    {
+        $this->update_0();
+        $this->update_1();
+        $this->setApplicationVersion('Timetracker', '10.6');
+    }
 }
index d3b6dd3..f11a258 100644 (file)
@@ -2,7 +2,7 @@
 <application>
     <name>Timetracker</name>
     <!-- gettext('Timetracker') -->   
-    <version>10.3</version>
+    <version>10.6</version>
     <order>60</order>
     <status>enabled</status>
     <depends>
                 </index>
             </declaration>
         </table>
+        <table>
+            <name>timetracker_timeaccount_fav</name>
+            <version>1</version>
+            <declaration>
+                <field>
+                    <name>id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>account_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>true</notnull>
+                </field>
+                <field>
+                    <name>timeaccount_id</name>
+                    <type>text</type>
+                    <length>40</length>
+                    <notnull>false</notnull>
+                </field>
+                <field>
+                    <name>created_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>creation_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>last_modified_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>last_modified_time</name>
+                    <type>datetime</type>
+                </field>
+                <field>
+                    <name>is_deleted</name>
+                    <type>boolean</type>
+                    <default>false</default>
+                </field>
+                <field>
+                    <name>deleted_by</name>
+                    <type>text</type>
+                    <length>40</length>
+                </field>
+                <field>
+                    <name>deleted_time</name>
+                    <type>datetime</type>
+                </field>
+                <index>
+                    <name>id</name>
+                    <primary>true</primary>
+                    <field>
+                        <name>id</name>
+                    </field>
+                </index>
+                <index>
+                    <name>timesheet_favorites--timesheet_id::id</name>
+                    <field>
+                        <name>timeaccount_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>timetracker_timeaccount</table>
+                        <field>id</field>
+                    </reference>
+                </index>
+                <index>
+                    <name>timesheet_favorites--account_id::id</name>
+                    <field>
+                        <name>account_id</name>
+                    </field>
+                    <foreign>true</foreign>
+                    <reference>
+                        <table>accounts</table>
+                        <field>id</field>
+                        <ondelete>CASCADE</ondelete>
+                    </reference>
+                </index>
+            </declaration>
+        </table>
     </tables>
 </application>
 
index 8fcb2d8..e330d58 100644 (file)
           "path": "js/"
         },
         {
+          "text": "TimeaccountFavoritesPanel.js",
+          "path": "js/"
+        },
+        {
+          "text": "TimesheetWestPanel.js",
+          "path": "js/"
+        },
+        {
+          "text": "TimeaccountWestPanel.js",
+          "path": "js/"
+        },
+        {
           "text": "TimeaccountPickerCombo.js",
           "path": "js/"
         },
diff --git a/tine20/Timetracker/js/TimeaccountFavoritesPanel.js b/tine20/Timetracker/js/TimeaccountFavoritesPanel.js
new file mode 100644 (file)
index 0000000..f62ec09
--- /dev/null
@@ -0,0 +1,186 @@
+/**
+ * Tine 2.0
+ *
+ * @package     Timetracker
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2016 bitExpert AG (http://bitexpert.de)
+ *
+ */
+
+Ext.ns('Tine', 'Tine.Timetracker');
+
+/**
+ * @namespace   Tine.Timetracker
+ * @class       Tine.Timetracker.TreePanel
+ * @extends     Tine.Tinebase.Application
+ * Timetracker TreePanel<br>
+ *
+ * @author Michael Spahn <M.Spahn@bitExpert.de>
+ *
+ */
+Tine.Timetracker.TimeaccountFavoritesPanel = function (config) {
+    Ext.apply(this, config);
+    Tine.Timetracker.TimeaccountFavoritesPanel.superclass.constructor.call(this);
+};
+
+Ext.extend(Tine.Timetracker.TimeaccountFavoritesPanel, Ext.tree.TreePanel, {
+    /**
+     * Autoscrolling
+     */
+    autoScroll: true,
+
+    /**
+     * Border
+     */
+    border: false,
+
+    /**
+     * Context menu for leaf
+     */
+    contextMenuLeaf: null,
+
+    /**
+     * Delete fav action
+     */
+    action_delete: null,
+
+    /**
+     * The current node of context menu
+     */
+    ctxNode: null,
+
+    /**
+     * init this treePanel
+     */
+    initComponent: function () {
+        if (!this.app) {
+            this.app = Tine.Tinebase.appMgr.get('Timetracker');
+        }
+
+        var favorites = this.getTimetrackerNodes();
+
+        this.root = {
+            path: '/',
+            cls: 'tinebase-tree-hide-collapsetool',
+            text: this.app.i18n._('Timeaccount Favorites'),
+            iconCls: 'folder',
+            expanded: true,
+            children: favorites
+        };
+
+        this.initContextMenu();
+
+        this.on('dblclick', this.onDoubleClick, this);
+        this.on('contextmenu', this.onContextMenu, this);
+
+        Tine.Timetracker.TimeaccountFavoritesPanel.superclass.initComponent.call(this);
+    },
+
+    /**
+     * Set up context menu
+     */
+    initContextMenu: function () {
+        this.action_delete = new Ext.Action({
+            text: String.format(i18n.ngettext('Delete {0}', 'Delete {0}', 1), this.app.i18n._('Favorite')),
+            iconCls: 'action_delete',
+            handler: this.deleteNode,
+            scope: this,
+            allowMultiple: false
+        });
+
+        this.contextMenuLeaf = new Ext.menu.Menu({
+            items: [
+                this.action_delete
+            ]
+        });
+    },
+
+    /**
+     * Delete selected favorite
+     *
+     * @returns {boolean}
+     */
+    deleteNode: function() {
+        if (!this.ctxNode.attributes && !this.ctxNode.attributes.favId) {
+            return false;
+        }
+
+        Ext.Ajax.request({
+            url: 'index.php',
+            scope: this,
+            params: {
+                method: 'Timetracker.deleteTimeAccountFavorite',
+                favId: this.ctxNode.attributes.favId
+            },
+            success : function(_result, _request) {
+                if (_result.responseText) {
+                    Tine.Timetracker.registry.set('timeaccountFavorites', JSON.parse(_result.responseText).timeaccountFavorites);
+
+                    var rootNode = Ext.getCmp('TimeaccountFavoritesPanel').getRootNode();
+
+                    if (!rootNode.hasChildNodes()) {
+                        rootNode.removeAll();
+                    }
+
+                    rootNode.removeAll();
+                    rootNode.appendChild(Tine.Timetracker.registry.get('timeaccountFavorites'));
+
+                    rootNode.expand();
+                }
+            },
+            failure: function(result) {
+                Tine.Tinebase.ExceptionHandler.handleRequestException(result);
+            }
+        });
+    },
+
+    /**
+     * @param node
+     * @param event
+     * @returns {boolean}
+     */
+    onContextMenu: function (node, event) {
+        if (!node.leaf) {
+            return false;
+        }
+
+        this.ctxNode = node;
+
+        this.contextMenuLeaf.showAt(event.getXY());
+    },
+
+    /**
+     * get core data nodes
+     ** @returns Array
+     */
+    getTimetrackerNodes: function () {
+        if (!Tine.Timetracker.registry.get('timeaccountFavorites')) {
+            return [];
+        }
+
+        return Tine.Timetracker.registry.get('timeaccountFavorites');
+    },
+
+    /**
+     * @param node
+     * @param e
+     * @returns {boolean}
+     */
+    onDoubleClick: function (node, e) {
+        if (!node.leaf) {
+            return false;
+        }
+
+        var record = new Tine.Timetracker.Model.Timesheet(Tine.Timetracker.Model.Timesheet.getDefaultData(), 0);
+        record.set('timeaccount_id', node.attributes.timeaccount);
+
+        var popupWindow = Tine.Timetracker.TimesheetEditDialog.openWindow({
+                plugins: null,
+                record: record,
+                recordId: null
+            }
+        );
+    }
+});
index 22f3be3..442933b 100644 (file)
@@ -45,15 +45,75 @@ Tine.Timetracker.TimeaccountGridPanel = Ext.extend(Tine.widgets.grid.GridPanel,
         operator: 'equals',
         value: true
     }],
-    
+    action_bookmark: null,
+
     initComponent: function() {
         Tine.Timetracker.TimeaccountGridPanel.superclass.initComponent.call(this);
-        
+
         this.action_addInNewWindow.setDisabled(! Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts'));
         this.action_editInNewWindow.requiredGrant = 'editGrant';
     },
 
     /**
+     * @private
+     */
+    initActions: function() {
+        Tine.Timetracker.TimesheetGridPanel.superclass.initActions.call(this);
+
+        this.action_bookmark = new Ext.Action({
+            text: i18n._('Bookmark Timeaccount'),
+            iconCls: 'action_add',
+            requiredGrant: 'readGrant',
+            scope: this,
+            disabled: true,
+            allowMultiple: false,
+            visible: false,
+            handler: this.addBookmark
+        });
+
+        // register actions in updater
+        this.actionUpdater.addActions([
+            this.action_bookmark
+        ]);
+    },
+
+    addBookmark: function () {
+        var grid = this,
+            app = this.app,
+            selectionModel = grid.selectionModel;
+
+        Ext.each(selectionModel.getSelections(), function (record) {
+            Ext.Ajax.request({
+                url: 'index.php',
+                scope: this,
+                params: {
+                    method: 'Timetracker.addTimeAccountFavorite',
+                    timeaccountId: record.get('id')
+                },
+                success : function(_result, _request) {
+                    if (_result.responseText) {
+                        Tine.Timetracker.registry.set('timeaccountFavorites', JSON.parse(_result.responseText).timeaccountFavorites);
+
+                        var rootNode = Ext.getCmp('TimeaccountFavoritesPanel').getRootNode();
+
+                        if (!rootNode.hasChildNodes()) {
+                            rootNode.removeAll();
+                        }
+
+                        rootNode.removeAll();
+                        rootNode.appendChild(Tine.Timetracker.registry.get('timeaccountFavorites'));
+
+                        rootNode.expand();
+                    }
+                },
+                failure: function(result) {
+                    Tine.Tinebase.ExceptionHandler.handleRequestException(result);
+                }
+            });
+        });
+    },
+
+    /**
      * add custom items to context menu
      *
      * @return {Array}
@@ -62,7 +122,7 @@ Tine.Timetracker.TimeaccountGridPanel = Ext.extend(Tine.widgets.grid.GridPanel,
         var items = [
             '-', {
                 text: Tine.Tinebase.appMgr.get('Timetracker').i18n._('Close Timeaccount'),
-                iconCls: 'action_edit',
+                iconCls: 'action_close',
                 scope: this,
                 disabled: !Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts'),
                 itemId: 'closeAccount',
@@ -70,6 +130,10 @@ Tine.Timetracker.TimeaccountGridPanel = Ext.extend(Tine.widgets.grid.GridPanel,
             }
         ];
 
+        if (this.app.featureEnabled('featureTimeaccountBookmark')) {
+            items.push(this.action_bookmark);
+        }
+
         return items;
     },
 
diff --git a/tine20/Timetracker/js/TimeaccountWestPanel.js b/tine20/Timetracker/js/TimeaccountWestPanel.js
new file mode 100644 (file)
index 0000000..2519642
--- /dev/null
@@ -0,0 +1,38 @@
+/* 
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Philipp Schüle <p.schuele@metaways.de>
+ * @copyright   Copyright (c) 216 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+Ext.ns('Tine.Timetracker');
+
+/**
+ * CoreData west panel
+ *
+ * @namespace   Tine.Timetracker
+ * @class       Tine.Timetracker.TimeaccountWestPanel
+ * @extends     Tine.widgets.mainscreen.TimeaccountWestPanel
+ *
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ *
+ * @constructor
+ * @xtype       tine.timetracker.TimeaccountWestPanel
+ */
+Tine.Timetracker.TimeaccountWestPanel = Ext.extend(Tine.widgets.mainscreen.WestPanel, {
+    hasContainerTreePanel: false,
+
+    getAdditionalItems: function () {
+        var items = [];
+
+        // if (this.app.featureEnabled('featureTimeaccountBookmark')) {
+        //     items.push(new Tine.Timetracker.TimeaccountFavoritesPanel({
+        //         height: 'auto',
+        //         id: 'TimeaccountFavoritesPanel'
+        //     }));
+        // }
+
+        return items;
+    }
+});
index 82562fa..84da88a 100644 (file)
@@ -97,13 +97,17 @@ Tine.Timetracker.TimesheetEditDialog = Ext.extend(Tine.widgets.dialog.EditDialog
             this.getForm().findField('billed_in').setDisabled(! (grants.adminGrant || manageRight));
         }
 
-        if (timeaccount) {
+        if (timeaccount && timeaccount.data) {
             notBillable = notBillable || timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0";
             
             // clearable depends on timeaccount is_billable as well (changed by ps / 2009-09-01, behaviour was inconsistent)
             notClearable = notClearable || timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0";
+
+            if (timeaccount.data.is_billable == "0" || timeaccount.get('is_billable') == "0") {
+                this.getForm().findField('is_billable').setValue(false);
+            }
         }
-        
+
         this.getForm().findField('is_billable').setDisabled(notBillable);
         this.getForm().findField('is_cleared').setDisabled(notClearable);
         
index e5f388b..acd5517 100644 (file)
@@ -4,7 +4,7 @@
  * @package     Timetracker
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
- * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
  *
  */
  
@@ -29,6 +29,26 @@ Ext.namespace('Tine.Timetracker');
  * Create a new Tine.Timetracker.TimesheetGridPanel
  */
 Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
+    /**
+     * record class
+     * @cfg {Tine.Timetracker.Model.Timesheet} recordClass
+     */
+    recordClass: Tine.Timetracker.Model.Timesheet,
+
+    /**
+     * @private grid cfg
+     */
+    defaultSortInfo: {field: 'start_date', direction: 'DESC'},
+    gridConfig: {
+        autoExpandColumn: 'description'
+    },
+    copyEditAction: true,
+    multipleEdit: true,
+    multipleEditRequiredRight: 'manage_timeaccounts',
+
+    /**
+     * @private
+     */
 
     /**
      * activates copy action
@@ -50,7 +70,8 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         
         // only eval grants in action updater if user does not have the right to manage timeaccounts
         this.evalGrants = ! Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts');
-        
+
+
         Tine.Timetracker.TimesheetGridPanel.superclass.initComponent.call(this);
     },
 
@@ -158,9 +179,9 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                         '<div class="bordercorner_3"></div>',
                         '<div class="bordercorner_4"></div>',
                         '<div class="preview-panel-declaration">' /* + this.app.i18n._('Description') */ + '</div>',
-                        '<div class="preview-panel-timesheet-description preview-panel-left" ext:qtip="{[this.encode(values.description)]}">',
+                        '<div class="preview-panel-timesheet-description preview-panel-left">',
                             '<span class="preview-panel-nonbold">',
-                             '{[this.encode(values.description, "longtext")]}',
+                             '{[this.encode(values.description)]}',
                             '<br/>',
                             '</span>',
                         '</div>',
@@ -223,6 +244,31 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
      * @private
      */
     initActions: function() {
+        var hiddenQuickTag = false,
+            quicktagName,
+            quicktagId;
+
+        quicktagId = Tine.Timetracker.registry.get('quicktagId');
+        quicktagName = Tine.Timetracker.registry.get('quicktagName');
+
+        if (!quicktagId || !quicktagName) {
+            hiddenQuickTag = true;
+        }
+
+        this.actions_massQuickTag = new Ext.Action({
+            hidden: hiddenQuickTag,
+            requiredGrant: 'editGrant',
+            text: String.format(
+                this.app.i18n._('Assign \'{0}\' Tag'),
+                quicktagName
+            ),
+            disabled: true,
+            allowMultiple: true,
+            handler: this.onApplyQuickTag.createDelegate(this),
+            iconCls: 'action_tag',
+            scope: this
+        });
+
         this.actions_export = new Ext.Action({
             text: this.app.i18n._('Export Timesheets'),
             iconCls: 'action_export',
@@ -260,13 +306,52 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         
         // register actions in updater
         this.actionUpdater.addActions([
-            this.actions_export
+            this.actions_export,
+            this.actions_massQuickTag
         ]);
         
         Tine.Timetracker.TimesheetGridPanel.superclass.initActions.call(this);
     },
 
     /**
+     * Apply quick tag to current selection
+     */
+    onApplyQuickTag: function() {
+        var quickTagId,
+            filter,
+            filterModel,
+            me;
+
+        me = this;
+
+        // Tag to assign
+        quickTagId = Tine.Timetracker.registry.get('quicktagId');
+
+        // Get filter model for current selection
+        filter = this.selectionModel.getSelectionFilter();
+        filterModel = this.recordClass.getMeta('appName') + '_Model_' +  this.recordClass.getMeta('modelName') + 'Filter';
+
+        // Send request to backend
+        Ext.Ajax.request({
+            scope: this,
+            timeout: 1800000,
+            success: function(response, options) {
+                // In case of success, just reload grid
+                me.getStore().reload();
+            },
+            params: {
+                method: 'Tinebase.attachTagToMultipleRecords',
+                filterData: filter,
+                filterName: filterModel,
+                tag: quickTagId
+            },
+            failure: function(response, options) {
+                Tine.Tinebase.ExceptionHandler.handleRequestException(response, options);
+            }
+        });
+    },
+
+    /**
      * check user exportGrant for timeaccounts
      * NOTE: manage_timeaccounts ALWAYS allows to export
      *
@@ -276,21 +361,57 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
      * @returns {boolean}
      */
     updateExportAction: function(action, grants, records) {
-        var exportGrant = true;
-        if (! Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts')) {
-            Ext.each(records, function (record) {
-                var c = record.get('timeaccount_id').container_id;
-                if (c.hasOwnProperty('account_grants')) {
-                    if (!c.account_grants.exportGrant) {
-                        exportGrant = false;
-                        return false;
-                    }
-                }
-            });
+        // export should be allowed always if user is allowed to manage timeaccounts
+        if (Tine.Tinebase.common.hasRight('manage', 'Timetracker', 'timeaccounts')) {
+            action.setDisabled(false);
+
+            // stop further events
+            return false;
         }
 
-        var disable = ! exportGrant;
+        // By default disallow export, this apply for example, if there is no selection yet
+        // E.g. filter changes and so on
+        var exportGrant = false;
+
+        // We need to go through all timeaccounts and check if the user is trying to export a timesheet of a timeaccount
+        // where he has no permission to export.
+        Ext.each(records, function (record) {
+            var timeaccount = record.get('timeaccount_id');
+            var c = timeaccount.container_id;
+            if (c.hasOwnProperty('account_grants')) {
+                var grants = c.account_grants;
+
+                if (!grants.exportGrant) {
+                    exportGrant = false;
+
+                    // stop loop
+                    return false;
+                } else {
+                    // If there was at least one selection which had the exportGrant, allow to export
+                    exportGrant = true;
+                }
+            }
+        });
+
+        var disable = !exportGrant;
         action.setDisabled(disable);
+
+        // stop further events
         return false;
+    },
+    
+    /**
+     * add custom items to context menu
+     * 
+     * @return {Array}
+     */
+    getContextMenuItems: function() {
+        var items = [
+            '-',
+            this.actions_massQuickTag,
+            this.actions_export
+        ];
+        
+        return items;
     }
 });
diff --git a/tine20/Timetracker/js/TimesheetWestPanel.js b/tine20/Timetracker/js/TimesheetWestPanel.js
new file mode 100644 (file)
index 0000000..200d6cb
--- /dev/null
@@ -0,0 +1,36 @@
+/* 
+ * Tine 2.0
+ * 
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * @copyright   Copyright (c) 2016 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+
+Ext.ns('Tine.Timetracker');
+
+/**
+ * CoreData west panel
+ * 
+ * @namespace   Tine.Timetracker
+ * @class       Tine.Timetracker.TimesheetWestPanel
+ * @extends     Tine.widgets.mainscreen.TimesheetWestPanel
+ * 
+ * @author      Michael Spahn <M.Spahn@bitExpert.de>
+ * 
+ * @constructor
+ * @xtype       tine.timetracker.TimesheetWestPanel
+ */
+Tine.Timetracker.TimesheetWestPanel = Ext.extend(Tine.widgets.mainscreen.WestPanel, {
+    getAdditionalItems: function() {
+        var items = [];
+
+        if (this.app.featureEnabled('featureTimeaccountBookmark')) {
+            items.push(new Tine.Timetracker.TimeaccountFavoritesPanel({
+                height: 'auto',
+                id: 'TimeaccountFavoritesPanel'
+            }));
+        }
+
+        return items;
+    }
+});
index cea1963..09e18d6 100644 (file)
@@ -53,7 +53,7 @@ Tine.widgets.grid.RendererManager.register('Timetracker', 'Timeaccount', 'is_ope
 // add renderer for invoice position gridpanel
 Tine.Timetracker.HumanHourRenderer = function(value) {
     return Ext.util.Format.round(value, 2);
-}
+};
 
 Tine.Timetracker.registerRenderers = function() {
     
@@ -76,9 +76,4 @@ Tine.Timetracker.registerAccountables = function() {
     Tine.Sales.AccountableRegistry.register('Timetracker', 'Timeaccount');
 };
 
-Tine.Timetracker.registerAccountables();
-
-// disables container tree in WestPanel
-Tine.Timetracker.TimeaccountWestPanel = Ext.extend(Tine.widgets.mainscreen.WestPanel, {
-    hasContainerTreePanel: false
-});
\ No newline at end of file
+Tine.Timetracker.registerAccountables();
\ No newline at end of file
index 5a8c589..ef42f37 100644 (file)
@@ -493,6 +493,13 @@ class Tinebase_Config extends Tinebase_Config_Abstract
     const VERSION_CHECK = 'versionCheck';
 
     /**
+     * WEBDAV_SYNCTOKEN_ENABLED
+     *
+     * @var string
+     */
+    const WEBDAV_SYNCTOKEN_ENABLED = 'webdavSynctokenEnabled';
+
+    /**
      * @var string
      */
     const REPLICATION_MASTER = 'replicationMaster';
@@ -1535,6 +1542,17 @@ class Tinebase_Config extends Tinebase_Config_Abstract
             'setBySetupModule'      => FALSE,
             'default'               => NULL,
         ),
+        self::WEBDAV_SYNCTOKEN_ENABLED => array(
+        //_('Enable SyncToken plugin')
+            'label'                 => 'Enable SyncToken plugin',
+        //_('Enable the use of the SyncToken plugin.')
+            'description'           => 'Enable the use of the SyncToken plugin.',
+            'type'                  => 'bool',
+            'clientRegistryInclude' => FALSE,
+            'setByAdminModule'      => FALSE,
+            'setBySetupModule'      => FALSE,
+            'default'               => TRUE,
+        ),
         self::CURRENCY_SYMBOL => array(
             //_('currency symbol')
             'label' => 'urrency symbol',
index 86d9c0a..7076d25 100644 (file)
@@ -373,23 +373,6 @@ class Tinebase_Container extends Tinebase_Backend_Sql_Abstract implements Tineba
     }
 
     /**
-     * get containers with bad names (name == uuid)
-     * 
-     * @return Tinebase_Record_RecordSet
-     */
-    public function getContainersWithBadNames()
-    {
-        $select = $this->_getSelect();
-        $select->where($this->_db->quoteIdentifier('uuid') . ' = ' . $this->_db->quoteIdentifier('name'));
-        $stmt = $this->_db->query('/*' . __FUNCTION__ . '*/' . $select);
-        $rows = $stmt->fetchAll(Zend_Db::FETCH_ASSOC);
-        
-        $result = new Tinebase_Record_RecordSet('Tinebase_Model_Container', $rows, TRUE);
-        
-        return $result;
-    }
-    
-    /**
      * return all container, which the user has the requested right for
      *
      * used to get a list of all containers accesssible by the current user
index e2345a8..4140e0d 100644 (file)
@@ -1217,74 +1217,7 @@ class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
         
         exit($result);
     }
-    
-    /**
-     * repairs container names
-     * 
-     * @param Zend_Console_Getopt $opts
-     */
-    public function repairContainerName($opts)
-    {
-        if (! $this->_checkAdminRight()) {
-            return FALSE;
-        }
-        $dryrun = $opts->d;
-        
-        $this->_addOutputLogWriter();
-        $args = $this->_parseArgs($opts);
-        
-        $containersWithBadNames = Tinebase_Container::getInstance()->getContainersWithBadNames();
-        
-        $locale = Tinebase_Translation::getLocale((isset($args['locale']) ?$args['locale'] : 'auto'));
 
-        if ($dryrun) {
-            print_r($containersWithBadNames->toArray());
-            echo "Using Locale " . $locale . "\n";
-        }
-        
-        $appContainerNames = array(
-            'Calendar' => 'calendar',
-            'Tasks'    => 'tasks',
-            'Addressbook'    => 'addressbook',
-        );
-        
-        foreach ($containersWithBadNames as $container) {
-            if (empty($container->owner_id)) {
-                if ($dryrun) {
-                    echo "Don't rename shared container " . $container->id . "\n";
-                }
-                continue;
-            }
-            $app = Tinebase_Application::getInstance()->getApplicationById($container->application_id);
-            $appContainerName = isset($appContainerNames[$app->name]) ? $appContainerNames[$app->name] : "container";
-            $translation = Tinebase_Translation::getTranslation($app->name, $locale);
-            $account = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $container->owner_id);
-            $newName = $newBaseName = sprintf($translation->_("%s's personal " . $appContainerName), $account->accountFullName);
-            
-            $count = 1;
-            do {
-                try {
-                    Tinebase_Container::getInstance()->getContainerByName($app->name, $newName, Tinebase_Model_Container::TYPE_PERSONAL, $container->owner_id);
-                    $found = true;
-                    $newName = $newBaseName . ' ' . ++$count;
-                } catch (Tinebase_Exception_NotFound $tenf) {
-                    $found = false;
-                }
-                
-            } while ($found);
-            if ($dryrun) {
-                echo "Rename container id " . $container->id . ' to ' . $newName . "\n";
-            } else {
-                
-                $container->name = $newName;
-                Tinebase_Container::getInstance()->update($container);
-            }
-        }
-        
-        $result = 0;
-        exit($result);
-    }
-    
     /**
      * transfer relations
      * 
index 0b6e2b2..9ff1527 100644 (file)
@@ -439,7 +439,7 @@ abstract class Tinebase_Preference_Abstract extends Tinebase_Backend_Sql_Abstrac
             if ($_value !== Tinebase_Model_Preference::DEFAULT_VALUE) {
                 // no preference yet -> create
                 $preference = new Tinebase_Model_Preference(array(
-                    'application_id'    => $appId = Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId(),
+                    'application_id'    => Tinebase_Application::getInstance()->getApplicationByName($this->_application)->getId(),
                     'name'              => $_preferenceName,
                     'value'             => $_value,
                     'account_id'        => $_accountId,
index 13c7230..bf91c85 100644 (file)
@@ -862,14 +862,14 @@ abstract class Tinebase_Record_Abstract implements Tinebase_Record_Interface
      * returns a random 40-character hexadecimal number to be used as 
      * universal identifier (UID)
      * 
-     * @param int|boolean $_length the length of the uid, defaults to 40
+     * @param int|null $_length the length of the uid, defaults to 40
      * @return string 40-character hexadecimal number
      */
-    public static function generateUID($_length = false)
+    public static function generateUID($_length = null)
     {
-        $uid = sha1(mt_rand(). microtime());
+        $uid = sha1(mt_rand() . microtime());
         
-        if ($_length !== false) {
+        if ($_length && $_length > 0) {
             $uid = substr($uid, 0, $_length);
         }
         
index 0bd4ba5..378d2b1 100644 (file)
@@ -37,6 +37,23 @@ class Tinebase_Server_WebDAV extends Tinebase_Server_Abstract implements Tinebas
             $this->_body = fopen('php://temp', 'r+');
             fwrite($this->_body, $request->getContent());
             rewind($this->_body);
+            /*
+             * JN: dirty hack for native Windows 7 & 10 webdav client (after early 2017):
+             * client sends empty request instead empty xml-sceleton -> inject it here
+             */
+            $broken_user_agent_preg = '/^Microsoft-WebDAV-MiniRedir\/[6,10]/';
+            if (isset($_SERVER['HTTP_USER_AGENT']) && (preg_match($broken_user_agent_preg, $_SERVER['HTTP_USER_AGENT']) === 1) ) {
+                if ($request->getContent() == '') {
+                    $broken_user_agent_body = '<?xml version="1.0" encoding="utf-8" ?><D:propfind xmlns:D="DAV:"><D:prop>';
+                    $broken_user_agent_body.= '<D:creationdate/><D:displayname/><D:getcontentlength/><D:getcontenttype/><D:getetag/><D:getlastmodified/><D:resourcetype/>';
+                    $broken_user_agent_body.= '</D:prop></D:propfind>';
+                    fwrite($this->_body, $broken_user_agent_body);
+                    rewind($this->_body);
+                    if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
+                        Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " broken userAgent detected: " .
+                        $_SERVER['HTTP_USER_AGENT'] . " --> inserted xml body");
+                }
+            }
         }
         
         try {
@@ -147,7 +164,14 @@ class Tinebase_Server_WebDAV extends Tinebase_Server_Abstract implements Tinebas
         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_PrincipalSearch());
         self::$_server->addPlugin(new Tinebase_WebDav_Plugin_ExpandedPropertiesReport());
         self::$_server->addPlugin(new \Sabre\DAV\Browser\Plugin());
-        self::$_server->addPlugin(new Tinebase_WebDav_Plugin_SyncToken());
+        if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
+            self::$_server->addPlugin(new Tinebase_WebDav_Plugin_SyncToken());
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
+        }
         self::$_server->addPlugin(new Calendar_Frontend_CalDAV_SpeedUpPropfindPlugin());
 
         $contentType = self::$_server->httpRequest->getHeader('Content-Type');
index 43ba503..bb55e63 100644 (file)
@@ -118,7 +118,9 @@ abstract class Tinebase_Timemachine_Abstract implements Tinebase_Timemachine_Int
      * @return Tinebase_Record
      * @access public
      */
-    public function getRecord( $_id,  Tinebase_DateTime $_at );
+    public function getRecord( $_id,  Tinebase_DateTime $_at )
+    {
+    }
     
     /**
      * Returns a set of records as they where at a given point in history
@@ -128,7 +130,9 @@ abstract class Tinebase_Timemachine_Abstract implements Tinebase_Timemachine_Int
      * @return Tinebase_Record_RecordSet
      * @access public
      */
-    public function getRecords( array $_ids,  Tinebase_DateTime $_at );
+    public function getRecords( array $_ids,  Tinebase_DateTime $_at )
+    {
+    }
     
     /**
      * Returns instance of Tinebase_Timemachine_ModificationLog
index bd109d4..ac5512f 100644 (file)
@@ -388,7 +388,14 @@ abstract class Tinebase_WebDav_Container_Abstract extends \Sabre\DAV\Collection
                     break;
 
                 case '{DAV:}sync-token':
-                    $response[$prop] = $this->getSyncToken();
+                    if (Tinebase_Config::getInstance()->get(Tinebase_Config::WEBDAV_SYNCTOKEN_ENABLED)) {
+                        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport enabled');
+                        $response[$prop] = $this->getSyncToken();
+                    } else {
+                        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
+                            Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' SyncTokenSupport disabled');
+                    }
                     break;
                     
                 default:
index 2965508..ed8b417 100644 (file)
@@ -1,11 +1,12 @@
-.preview-panel{
-    padding:5px;
-    white-space:normal;
-    height:103px;
-    background-color:#e5edf8;
-    position:relative;
-    display:inline;
-    }
+.preview-panel {
+    padding: 5px;
+    white-space: normal;
+    height: 100%;
+    background-color: #e5edf8;
+    position: relative;
+    display: inline;
+}
+
 .preview-panel img {
     vertical-align: middle;
     -moz-border-radius: 8px;
 .preview-panel-left{
     float:left;
     font-size:10px;
+}
+
+.preview-panel-left {
+    float: left;
+    font-size: 10px;
     color: #404040;
-    font-family:verdana;
+    font-family: verdana;
     line-height: 16px;
-    }
-.preview-panel-right{
-    float:right;
-    font-size:10px;
+    overflow-y: auto;
+}
+
+.preview-panel-right {
+    float: right;
+    font-size: 10px;
     color: #404040;
-    font-family:verdana;
+    font-family: verdana;
     line-height: 16px;
-    }
-.preview-panel-bold{
+}
+
+.preview-panel-bold {
     font-weight: bold;
     }
 .preview-panel-image{
     width:360px;
     margin:5px 2px 5px 5px;
     }
+
+.preview-panel-image {
+    vertical-align: middle;
+    margin: 5px 2px 5px 5px;
+    width: 90px;
+    height: 113px;
+    padding: 0px
+}
+
+.preview-panel-office {
+    width: 360px;
+    margin: 5px 2px 5px 5px;
+}
+
+.preview-panel-privat {
+    width: 360px;
+    margin: 5px 2px 5px 5px;
+}
+
 .preview-panel-description {
     width: 200px;
     position: relative;
     height: 97px;
-    margin:5px 2px 5px 5px;
+    margin: 5px 2px 5px 5px;
     padding: 5px;
     border: solid 3px #e5e6fe;
-    position: relative;
-    }
+    overflow: hidden;
+}
+
 .preview-panel-description,
 .preview-panel-description a,
 .preview-panel-description a:link,
 .preview-panel-description a:visited,
 .preview-panel-description a:hover,
 .preview-panel-description a:active {
-     color:#a0a0a0;
-     text-decoration: none;
-    }
+    color: #a0a0a0;
+    text-decoration: none;
+}
 
-    
-.preview-panel-address{
-    width:150px;
+.preview-panel-address {
+    width: 150px;
     padding: 5px;
-    }
-.preview-panel-contact{
-    width:180px;
+}
+
+.preview-panel-contact {
+    width: 180px;
     padding: 5px;
-    }
-.bordercorner_1{
- background-image: url(../../../images/preview/bordercorner_blue_1.gif);
+}
+
+.bordercorner_1 {
+    background-image: url(../../../images/preview/bordercorner_blue_1.gif);
     background-repeat: no-repeat;
     background-position: left top;
     position: absolute;
     left: 0;
     width: 4px;
     height: 4px;
-    }
-.bordercorner_2{
+}
+
+.bordercorner_2 {
     background-image: url(../../../images/preview/bordercorner_blue_2.gif);
     background-repeat: no-repeat;
     background-position: left top;
     right: 0;
     width: 4px;
     height: 4px;
-    }
-.bordercorner_3{
+}
+
+.bordercorner_3 {
     background-image: url(../../../images/preview/bordercorner_blue_3.gif);
     background-repeat: no-repeat;
     background-position: left top;
     bottom: 0;
     width: 4px;
     height: 4px;
-    }
-.bordercorner_4{
+}
+
+.bordercorner_4 {
     background-image: url(../../../images/preview/bordercorner_blue_4.gif);
     background-repeat: no-repeat;
     background-position: left top;
     bottom: 0;
     width: 4px;
     height: 4px;
-    } 
-.preview-panel-adressbook-nobreak{
-    min-width:1075px;
 }
-.bordercorner_gray_1{
-     background-image: url(../../../images/preview/bordercorner_gray_1.gif);
+
+.preview-panel-adressbook-nobreak {
+    min-width: 1075px;
+}
+
+.bordercorner_gray_1 {
+    background-image: url(../../../images/preview/bordercorner_gray_1.gif);
     background-repeat: no-repeat;
     background-position: left top;
     position: absolute;
     left: -3px;
     width: 5px;
     height: 5px;
-    }
-.bordercorner_gray_2{
+}
+
+.bordercorner_gray_2 {
     background-image: url(../../../images/preview/bordercorner_gray_2.gif);
     background-repeat: no-repeat;
     background-position: left top;
     right: -3px;
     width: 5px;
     height: 5px;
-    }
-.bordercorner_gray_3{
+}
+
+.bordercorner_gray_3 {
     background-image: url(../../../images/preview/bordercorner_gray_3.gif);
     background-repeat: no-repeat;
     background-position: left top;
     bottom: -3px;
     width: 5px;
     height: 5px;
-    }
-.bordercorner_gray_4{
+}
+
+.bordercorner_gray_4 {
     background-image: url(../../../images/preview/bordercorner_gray_4.gif);
     background-repeat: no-repeat;
     background-position: left top;
     bottom: -3px;
     width: 5px;
     height: 5px;
-    }
-.preview-panel-declaration{
+}
+
+.preview-panel-declaration {
     color: #a0a0a0;
     font-size: 18px;
     position: absolute;
     font-weight: bold;
     left: 5px;
     bottom: 6px;
-    }
-.preview-panel-symbolcompare{
-    color:#a0a0a0;
-    width:45px;
+}
+
+.preview-panel-symbolcompare {
+    color: #a0a0a0;
+    width: 45px;
     text-align: right;
     display: block;
     float: left;
     overflow: hidden;
     padding-right: 5px;
-    }
+}
index 148720f..0c37f11 100644 (file)
@@ -497,8 +497,8 @@ Tine.Tinebase.LoginPanel = Ext.extend(Ext.Panel, {
                         this.onLogin.call(this.scope, response);
                     } else {
                         var modSsl = Tine.Tinebase.registry.get('modSsl');
-                        var resultMsg = modSsl ? i18n._('There was an error verifying your certificate!!!') :
-                            i18n._('Your username and/or your password are wrong!!!');
+                        var resultMsg = modSsl ? i18n._('There was an error verifying your certificate!') :
+                            i18n._('Your username and/or your password are wrong!');
                         Ext.MessageBox.show({
                             title: i18n._('Login failure'),
                             msg: resultMsg,
index 7b9b314..e18f4e5 100644 (file)
@@ -53,7 +53,7 @@ Tine.clientVersion.releaseTime      = 'none';
  */
 Tine.logo = 'images/tine_logo.png';
 Tine.favicon;
-Tine.title = 'Tine 2.0 \u00ae';
+Tine.title = 'Tine 2.0 \u263c';
 Tine.weburl = 'http://www.tine20.com/1/welcome-community/';
 Tine.helpUrl = 'https://wiki.tine20.org/Main_Page';
 Tine.bugreportUrl = 'https://api.tine20.net/bugreport.php';
index 04a8cd9..c4d7edf 100644 (file)
@@ -198,6 +198,7 @@ Ext.extend(Tine.widgets.mainscreen.WestPanel, Ext.ux.Portal, {
      */
     getContainerTreePanel: function() {
         var panelName = this.app.getMainScreen().getActiveContentType() + 'TreePanel';
+
         if (! this[panelName]) {
             if (Tine[this.app.appName].hasOwnProperty(panelName)) {
                 this[panelName] = new Tine[this.app.appName][panelName]({app: this.app});
@@ -351,7 +352,7 @@ Ext.extend(Tine.widgets.mainscreen.WestPanel, Ext.ux.Portal, {
                 }, this);
             }, this);
         }
-        
+
         return this.portalColumn;
     },
     
index 78dd99d..8a5b436 100644 (file)
@@ -89,7 +89,7 @@ Tine.widgets.tags.TagPanel = Ext.extend(Ext.Panel, {
                 paging : {}
             }
         });
-        
+
         this.searchField = new Tine.widgets.tags.TagCombo({
             app: this.app,
             onlyUsableTags: true,
@@ -269,9 +269,9 @@ Tine.widgets.tags.TagPanel = Ext.extend(Ext.Panel, {
                             
                             // @todo use correct strings: Realy -> Really / disapear -> disappear
                             Ext.MessageBox.confirm(
-                                i18n.ngettext('Realy Delete Selected Tag?', 'Realy Delete Selected Tags?', selectedTags.length),
-                                i18n.ngettext('the selected tag will be deleted and disapear for all entries',
-                                                        'The selected tags will be removed and disapear for all entries', selectedTags.length), 
+                                i18n.ngettext('Really delete selected tag?', 'Really delete selected tags?', selectedTags.length),
+                                i18n.ngettext('The selected tag will be deleted and disappear for all entries',
+                                                        'The selected tags will be removed and disappear for all entries', selectedTags.length), 
                                 function(btn) {
                                     if (btn == 'yes'){
                                         Ext.MessageBox.wait(i18n._('Please wait a moment...'), i18n.ngettext('Deleting Tag', 'Deleting Tags', selectedTags.length));
index 520342a..cdd1433 100644 (file)
@@ -24,7 +24,7 @@
     "require": {
         "zendframework/zendframework1": "1.12.20pl14 as 1.12.20",
         "tine20/composerapploader": "1.0.*",
-        "syncroton/syncroton": "1.3.*",
+        "syncroton/syncroton": "dev-master#7a130976f93cce5035ec9a455f65b5b7134a7142",
         "ezyang/htmlpurifier": "v4.6.0",
         "phpoffice/phpexcel": "1.8.*",
         "phpoffice/phpword": "v0.13.*",
index 0070b3f..f9a064c 100644 (file)
         },
         {
             "name": "doctrine/dbal",
-            "version": "v2.5.4",
+            "version": "v2.5.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/dbal.git",
-                "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769"
+                "reference": "9f8c05cd5225a320d56d4bfdb4772f10d045a0c9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/dbal/zipball/abbdfd1cff43a7b99d027af3be709bc8fc7d4769",
-                "reference": "abbdfd1cff43a7b99d027af3be709bc8fc7d4769",
+                "url": "https://api.github.com/repos/doctrine/dbal/zipball/9f8c05cd5225a320d56d4bfdb4772f10d045a0c9",
+                "reference": "9f8c05cd5225a320d56d4bfdb4772f10d045a0c9",
                 "shasum": ""
             },
             "require": {
             },
             "require-dev": {
                 "phpunit/phpunit": "4.*",
-                "symfony/console": "2.*"
+                "symfony/console": "2.*||^3.0"
             },
             "suggest": {
                 "symfony/console": "For helpful console commands such as SQL execution and import of files."
         },
         {
             "name": "doctrine/orm",
-            "version": "v2.5.4",
+            "version": "v2.5.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/doctrine2.git",
-                "reference": "bc4ddbfb0114cb33438cc811c9a740d8aa304aab"
+                "reference": "73e4be7c7b3ba26f96b781a40b33feba4dfa6d45"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/bc4ddbfb0114cb33438cc811c9a740d8aa304aab",
-                "reference": "bc4ddbfb0114cb33438cc811c9a740d8aa304aab",
+                "url": "https://api.github.com/repos/doctrine/doctrine2/zipball/73e4be7c7b3ba26f96b781a40b33feba4dfa6d45",
+                "reference": "73e4be7c7b3ba26f96b781a40b33feba4dfa6d45",
                 "shasum": ""
             },
             "require": {
         },
         {
             "name": "symfony/console",
-            "version": "v3.1.2",
+            "version": "v3.1.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "747154aa69b0f83cd02fc9aa554836dee417631a"
+                "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/747154aa69b0f83cd02fc9aa554836dee417631a",
-                "reference": "747154aa69b0f83cd02fc9aa554836dee417631a",
+                "url": "https://api.github.com/repos/symfony/console/zipball/8ea494c34f0f772c3954b5fbe00bffc5a435e563",
+                "reference": "8ea494c34f0f772c3954b5fbe00bffc5a435e563",
                 "shasum": ""
             },
             "require": {
         },
         {
             "name": "syncroton/syncroton",
-            "version": "1.3.3",
+            "version": "dev-master",
             "source": {
                 "type": "git",
                 "url": "http://git.syncroton.org/Syncroton",
-                "reference": "fbd03e07c494556f291f67841111dc5321c07893"
+                "reference": "7a130976f93cce5035ec9a455f65b5b7134a7142"
             },
             "require": {
                 "php": ">=5.3.0",
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.8",
+            "version": "v2.8.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "dba4bb5846798cd12f32e2d8f3f35d77045773c8"
+                "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/dba4bb5846798cd12f32e2d8f3f35d77045773c8",
-                "reference": "dba4bb5846798cd12f32e2d8f3f35d77045773c8",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/e7540734bad981fe59f8ef14b6fc194ae9df8d9c",
+                "reference": "e7540734bad981fe59f8ef14b6fc194ae9df8d9c",
                 "shasum": ""
             },
             "require": {