Merge branch '2015.11-develop' into 2016.11
authorPhilipp Schüle <p.schuele@metaways.de>
Wed, 28 Sep 2016 08:14:55 +0000 (10:14 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 28 Sep 2016 08:14:55 +0000 (10:14 +0200)
24 files changed:
tests/tine20/Addressbook/Import/CsvTest.php
tests/tine20/Addressbook/Import/files/adb_import_csv_duplicate.xml
tests/tine20/Addressbook/Import/files/import_duplicate_1_cf.csv
tests/tine20/Addressbook/Import/files/import_duplicate_2_cf.csv
tests/tine20/Calendar/Frontend/ActiveSyncTest.php
tests/tine20/Calendar/Frontend/files/event_with_group_attendee.xml [new file with mode: 0644]
tests/tine20/HumanResources/JsonTests.php
tests/tine20/Timetracker/AbstractTest.php
tests/tine20/Timetracker/FilterTest.php
tests/tine20/Tinebase/WebDav/RootTest.php
tine20/ActiveSync/Frontend/Abstract.php
tine20/Calendar/Frontend/ActiveSync.php
tine20/Calendar/Model/Attender.php
tine20/Calendar/js/Printer/DaysView.js
tine20/HumanResources/Model/Employee.php
tine20/Setup/Server/Json.php
tine20/Setup/js/init.js
tine20/Timetracker/js/TimesheetGridPanel.js
tine20/Tinebase/Record/Abstract.php
tine20/Tinebase/User.php
tine20/Tinebase/WebDav/Collection/AbstractContainerTree.php
tine20/Tinebase/js/tineInit.js
tine20/Tinebase/js/ux/Printer/renderers/Base.js
tine20/bootstrap.php

index d9a63ad..7482e7d 100644 (file)
@@ -294,7 +294,7 @@ class Addressbook_Import_CsvTest extends ImportTestCase
         $result = $this->_doImport(array(), $definition);
         
         $this->assertEquals(1, $result['updatecount'], 'should have updated 1 contact');
-        $this->assertEquals(0, $result['totalcount']);
+        $this->assertEquals(0, $result['totalcount'], 'should have imported 0 records: ' . print_r($result['results']->toArray(), true));
         $this->assertEquals(0, $result['failcount']);
         $this->assertEquals('joerg@home.com', $result['results'][0]->email_home, 'duplicates resolving did not work: ' . print_r($result['results']->toArray(), true));
         $this->assertEquals('Jörg', $result['results'][0]->n_given, 'wrong encoding: ' . print_r($result['results']->toArray(), true));
@@ -309,6 +309,8 @@ class Addressbook_Import_CsvTest extends ImportTestCase
     {
         $this->_createCustomField('customfield1');
         $this->_createCustomField('customfield2');
+        // empty values: should not trigger record updates
+        $this->_createCustomField('customfield3');
 
         $result = $this->testImportDuplicateResolve(/* $withCustomfields */ true);
 
@@ -361,12 +363,6 @@ class Addressbook_Import_CsvTest extends ImportTestCase
         $this->assertEquals('Straßbough', $result['results'][1]['adr_one_locality'],
                 'should have changed the locality of contact #2: ' . print_r($result['results'][1]->toArray(), true));
         $this->assertEquals('Gartencenter Röhr & Vater', $result['results'][3]['n_family']);
-
-        // TODO this should be researched, imho the relation should not trigger an update of the record
-//        $this->assertEquals(1, $result['results'][3]['seq'], 'Wolfer has been updated - relations changed');
-//        $this->assertEquals('Weixdorf DD', $result['results'][0]['adr_one_locality'], 'locality should persist');
-//        $this->assertEquals('Gartencenter Röhr & Vater', $result['results'][4]['n_fileas']);
-//        $this->assertEquals('Straßback', $result['results'][5]['adr_one_locality']);
     }
 
     public function testSplitField()
index ed0b14f..80994f6 100644 (file)
             <source>customfield2</source>
             <destination>customfield2</destination>
         </field>
+        <field>
+            <source>customfield3</source>
+            <destination>customfield3</destination>
+        </field>
     </mapping>
 </config>
\ No newline at end of file
index f71acac..3066ef8 100644 (file)
@@ -1,3 +1,3 @@
-"org_name","salutation","title","n_given","n_family","role","adr_one_street","adr_one_postalcode","adr_one_locality","adr_one_countryname","tel_work","tel_fax","tel_cell","email","tel_fax_home","tel_home","email_home","customfield1","customfield2"\r
-"effect gmbh","Herr","Dr.","Jörg","Heinz","Geschäftsführer","Str. 25","21222","Köln","DE","022324321","","","joerg.heinz@honk.com","","","","cf1","cf2"\r
-"effect gmbh","Herr","","Hans","Fritz","Technical Lead","Str. 25","21222","Köln","DE","022324321","","","hans.fritz@honk.com","","","","cf1","cf2"\r
+"org_name","salutation","title","n_given","n_family","role","adr_one_street","adr_one_postalcode","adr_one_locality","adr_one_countryname","tel_work","tel_fax","tel_cell","email","tel_fax_home","tel_home","email_home","customfield1","customfield2","customfield3"
+"effect gmbh","Herr","Dr.","Jörg","Heinz","Geschäftsführer","Str. 25","21222","Köln","DE","022324321","","","joerg.heinz@honk.com","","","","cf1","cf2",""
+"effect gmbh","Herr","","Hans","Fritz","Technical Lead","Str. 25","21222","Köln","DE","022324321","","","hans.fritz@honk.com","","","","cf1","cf2",""
index 784eeb2..8651f2e 100644 (file)
@@ -1,3 +1,3 @@
-"org_name","salutation","title","n_given","n_family","role","adr_one_street","adr_one_postalcode","adr_one_locality","adr_one_countryname","tel_work","tel_fax","tel_cell","email","tel_fax_home","tel_home","email_home","customfield1","customfield2"\r
-"effect gmbh","Herr","Dr.","Jörg","Heinz","Geschäftsführer","Str. 25","21222","Köln","DE","022324321","","","joerg.heinz@honk.com","022324322","022324323","joerg@home.com","","cf2-2"\r
-"effect gmbh","Herr","","Hans","Fritz","Technical Lead","Str. 25","21222","Köln","DE","022324321","","","hans.fritz@honk.com","","","","cf1","cf2"\r
+"org_name","salutation","title","n_given","n_family","role","adr_one_street","adr_one_postalcode","adr_one_locality","adr_one_countryname","tel_work","tel_fax","tel_cell","email","tel_fax_home","tel_home","email_home","customfield1","customfield2","customfield3"
+"effect gmbh","Herr","Dr.","Jörg","Heinz","Geschäftsführer","Str. 25","21222","Köln","DE","022324321","","","joerg.heinz@honk.com","022324322","022324323","joerg@home.com","","cf2-2",""
+"effect gmbh","Herr","","Hans","Fritz","Technical Lead","Str. 25","21222","Köln","DE","022324321","","","hans.fritz@honk.com","","","","cf1","cf2",""
index 3a1cbc9..ed8aaa5 100644 (file)
@@ -494,7 +494,7 @@ Zeile 3</AirSyncBase:Data>
         $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
         
         $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
-        
+
         $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
         
         $this->assertEquals(0,         $syncrotonEvent->allDayEvent, 'alldayEvent');
@@ -1206,37 +1206,37 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMA
         $this->assertNotEmpty($syncrotonEvent->attendees, 'Events attendees not found in default calendar');
     }
 
-    public function testUpdateEntriesIPhone()
+    public function testUpdateEntriesIPhone($syncrotonFolder = null)
     {
-        // create event
-        $syncrotonFolder = $this->testCreateFolder();
-        $syncrotonFolder2 = $this->testCreateFolder();
-        
-        //make $syncrotonFolder2 the default
-        Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
-        
+        // ensure folder is default
+        $syncrotonFolder = $syncrotonFolder ? $syncrotonFolder : $this->testCreateFolder();
+        Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder->serverId);
+
+        // create event with external attendee in default calendar
+        $xml = new SimpleXMLElement(file_get_contents(__DIR__ . '/files/event_with_attendee.xml'));
+        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
         $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
-        list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
+        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
 
-        // update event
+        // assert external attendee is present
         $syncrotonEventtoUpdate = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
+        $this->assertEquals(2, count($syncrotonEventtoUpdate->attendees), 'event: ' . var_export($syncrotonEventtoUpdate->attendees, TRUE));
+
+        // update event in non default folder
         $syncrotonEventtoUpdate->exceptions[0]->subject = 'update';
         $syncrotonEventtoUpdate->exceptions[0]->startTime->addHour(1);
         $syncrotonEventtoUpdate->exceptions[0]->endTime->addHour(1);
-
         $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
         $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
         $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEventtoUpdate);
 
-        // assert update
+        // assert update in default folder
         $updatedSyncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
         $this->assertEquals('update', $updatedSyncrotonEvent->exceptions[0]->subject);
-        $this->assertNotEquals($syncrotonEvent->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
-        $this->assertEquals($syncrotonEventtoUpdate->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
+        $this->assertEquals('2012-11-25 15:00:00', $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
+        $this->assertEquals(2, count($updatedSyncrotonEvent->attendees), 'event: ' . var_export($updatedSyncrotonEvent->attendees, TRUE));
 
-        // assert attendee are preserved
-        $updatedEvent = Calendar_Controller_MSEventFacade::getInstance()->get($serverId);
-        $this->assertNotEmpty($updatedEvent, 'attendee must be preserved during update');
+        return array($serverId, $updatedSyncrotonEvent);
     }
 
     /**
@@ -1247,21 +1247,21 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMA
      */
     public function testUpdateEntriesIPhoneNonDefaultFolder()
     {
-        // create event in folder1
+        // create event in default folder
         $syncrotonFolder = $this->testCreateFolder();
-        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
-        list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
+        list($serverId, $syncrotonEvent) = $this->testUpdateEntriesIPhone($syncrotonFolder);
 
         // create new default folder2
         $syncrotonFolder2 = $this->testCreateFolder();
         Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
 
-        // load event in non default folder2
+        // load event in non default folder
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
         $syncrotonEventtoUpdate = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
         $this->assertEmpty($syncrotonEventtoUpdate->attendees, 'attendee in non default folders must be removed for ios');
 
         // update event in non default folder
-        $syncrotonEventtoUpdate->exceptions[0]->subject = 'update';
+        $syncrotonEventtoUpdate->exceptions[0]->subject = 'update2';
         $syncrotonEventtoUpdate->exceptions[0]->startTime->addHour(1);
         $syncrotonEventtoUpdate->exceptions[0]->endTime->addHour(1);
 
@@ -1269,13 +1269,44 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAMA
         $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
         $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEventtoUpdate);
 
-        // assert update from non default folder
+        // assert update from default folder
         $updatedSyncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder2->serverId)), $serverId);
-        $this->assertEquals('update', $updatedSyncrotonEvent->exceptions[0]->subject);
+        $this->assertEquals('update2', $updatedSyncrotonEvent->exceptions[0]->subject);
         $this->assertNotEquals($syncrotonEvent->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
         $this->assertEquals($syncrotonEventtoUpdate->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
+        $this->assertEquals(2, count($updatedSyncrotonEvent->attendees), 'event: ' . var_export($updatedSyncrotonEvent->attendees, TRUE));
+    }
+
+    public function testCreateUpdateGroupEvents()
+    {
+        $syncrotonFolder = $this->testCreateFolder();
+        Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder->serverId);
+
+        $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
+        $defaultUserGroupMembers = Tinebase_Group::getInstance()->getGroupMembers($defaultUserGroup->getId());
+
+        // create event with default user group (without explicit groupmembers)
+        $xml = new SimpleXMLElement(str_replace(array(
+            'usersgroupid',
+        ), $defaultUserGroup->getId(), file_get_contents(__DIR__ . '/files/event_with_group_attendee.xml')));
+        $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
+        $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
+
+        // assert created group & groupmembers
+        $syncrotonEventtoUpdate = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
+        $this->assertCount(count($defaultUserGroupMembers) + 1, $syncrotonEventtoUpdate->attendees, 'groupmembers not resolved');
+        $this->assertCount(count($defaultUserGroupMembers) + 1, $syncrotonEventtoUpdate->exceptions[0]->attendees, 'groupmembers not resolved');
+
+        // update event
+        $syncrotonEventtoUpdate->exceptions[0]->subject = 'update';
+        $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
+        $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
+        $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEventtoUpdate);
 
-        // assert attendee are preserved
-        $this->assertNotEmpty($updatedSyncrotonEvent->attendees, 'attendee must be preserved during update');
+        // assert updated group & groupmembers
+        $updatedSyncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
+        $this->assertCount(count($defaultUserGroupMembers) + 1, $updatedSyncrotonEvent->attendees, 'groupmembers not resolved');
+        $this->assertCount(count($defaultUserGroupMembers) + 1, $updatedSyncrotonEvent->exceptions[0]->attendees, 'groupmembers not resolved');
     }
 }
diff --git a/tests/tine20/Calendar/Frontend/files/event_with_group_attendee.xml b/tests/tine20/Calendar/Frontend/files/event_with_group_attendee.xml
new file mode 100644 (file)
index 0000000..b7bbaa2
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?><!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
+<Sync xmlns="uri:AirSync" xmlns:Calendar="uri:Calendar">
+    <Collections>
+        <Collection>
+            <Class>Calendar</Class>
+            <SyncKey>9</SyncKey>
+            <CollectionId>41</CollectionId>
+            <DeletesAsMoves/>
+            <GetChanges/>
+            <WindowSize>50</WindowSize>
+            <Options><FilterType>5</FilterType></Options>
+            <Commands>
+                <Change>
+                    <ServerId>6de7cb687964dc6eea109cd81750177979362217</ServerId>
+                    <ApplicationData>
+                        <Calendar:Timezone>xP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoAAAAFAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAFAAIAAAAAAAAAxP///w==</Calendar:Timezone>
+                        <Calendar:AllDayEvent>0</Calendar:AllDayEvent>
+                        <Calendar:BusyStatus>2</Calendar:BusyStatus>
+                        <Calendar:DtStamp>20121125T150537Z</Calendar:DtStamp>
+                        <Calendar:EndTime>20121123T160000Z</Calendar:EndTime>
+                        <Calendar:Sensitivity>2</Calendar:Sensitivity>
+                        <Calendar:Subject>Repeat</Calendar:Subject>
+                        <Calendar:StartTime>20121123T130000Z</Calendar:StartTime>
+                        <Calendar:UID>6de7cb687964dc6eea109cd81750177979362217</Calendar:UID>
+                        <Calendar:MeetingStatus>1</Calendar:MeetingStatus>
+                        <Calendar:Attendees>
+                            <Calendar:Attendee>
+                                <Calendar:Name>Users</Calendar:Name>
+                                <Calendar:Email>usersgroupid</Calendar:Email>
+                            </Calendar:Attendee>
+                        </Calendar:Attendees>
+                        <Calendar:Recurrence>
+                            <Calendar:Type>0</Calendar:Type><Calendar:Interval>1</Calendar:Interval><Calendar:Until>20121128T225959Z</Calendar:Until>
+                        </Calendar:Recurrence>
+                        <Calendar:Exceptions>
+                            <Calendar:Exception>
+                                <Calendar:Deleted>0</Calendar:Deleted>
+                                <Calendar:ExceptionStartTime>20121125T130000Z</Calendar:ExceptionStartTime>
+                                <Calendar:StartTime>20121125T140000Z</Calendar:StartTime>
+                                <Calendar:EndTime>20121125T170000Z</Calendar:EndTime>
+                                <Calendar:Subject>Repeat mal anders</Calendar:Subject>
+                                <Calendar:BusyStatus>2</Calendar:BusyStatus>
+                                <Calendar:AllDayEvent>0</Calendar:AllDayEvent>
+                                <Calendar:Attendees>
+                                    <Calendar:Attendee>
+                                        <Calendar:Name>Users</Calendar:Name>
+                                        <Calendar:Email>usersgroupid</Calendar:Email>
+                                    </Calendar:Attendee>
+                                </Calendar:Attendees>
+                            </Calendar:Exception>
+                            <Calendar:Exception>
+                                <Calendar:Deleted>1</Calendar:Deleted><Calendar:ExceptionStartTime>20121124T130000Z</Calendar:ExceptionStartTime></Calendar:Exception>
+                        </Calendar:Exceptions>
+                        <Calendar:Reminder>15</Calendar:Reminder>
+                        <Body xmlns="uri:AirSyncBase"><Type>1</Type><Data>Hello</Data></Body>
+                    </ApplicationData>
+                </Change>
+            </Commands>
+        </Collection>
+    </Collections>
+</Sync>
\ No newline at end of file
index 12136d4..8aad76e 100644 (file)
@@ -181,6 +181,18 @@ class HumanResources_JsonTests extends HumanResources_TestCase
     }
 
     /**
+     * @see 0012228: employee bday should be saved as datetime
+     */
+    public function testBirthday()
+    {
+        $e = $this->_getEmployee();
+        $datetime = new Tinebase_DateTime('2009-03-02 00:00:00');
+        $e->bday = $datetime;
+        $savedEmployee = $this->_json->saveEmployee($e->toArray());
+        $this->assertEquals($datetime->toString(), $savedEmployee['bday']);
+    }
+
+    /**
      * Tests the duplicate check
      */
     public function testDuplicateException()
index 2f2366d..db8f181 100644 (file)
@@ -11,7 +11,7 @@
 /**
  * Timetracker_AbstractTest Test class
  */
-abstract class Timetracker_AbstractTest extends PHPUnit_Framework_TestCase
+abstract class Timetracker_AbstractTest extends TestCase
 {
     /**
      * @var Timetracker_Frontend_Json
index 39726d2..46cb442 100644 (file)
@@ -184,4 +184,15 @@ class Timetracker_FilterTest extends Timetracker_AbstractTest
         
         $this->assertEquals(1, $results->count());
     }
+    
+    /**
+     * tests if the Timeaccount Filter is there
+     */
+    public function testTimeaccountfilterWithoutAdmin()
+    {
+        $this->_removeRoleRight('Timetracker', Tinebase_Acl_Rights::ADMIN);
+        $this->assertFalse(Tinebase_Core::getUser()->hasRight('Timetracker', Tinebase_Acl_Rights::ADMIN));
+        $config = Tinebase_ModelConfiguration::getFrontendConfigForModels(array('Timetracker_Model_Timesheet'));
+        $this->assertTrue(isset($config['Timesheet']['filterModel']['timeaccount_id']));
+    }
 }
index 02a0380..e9731a0 100644 (file)
@@ -147,4 +147,14 @@ class Tinebase_WebDav_RootTest extends TestCase
         
         return $this->_webdavTree;
     }
+
+    /**
+     * @see 0012216: Caldav Directory calendars not found
+     */
+    public function testCalendarRoot()
+    {
+        $calendarRoot = new Calendar_Frontend_WebDAV(\Sabre\CalDAV\Plugin::CALENDAR_ROOT, true);
+        $children = $calendarRoot->getChildren();
+        $this->assertTrue(count($children) > 0 && $children[0] instanceof Calendar_Frontend_WebDAV);
+    }
 }
index 18f10b2..69af485 100644 (file)
@@ -265,7 +265,8 @@ abstract class ActiveSync_Frontend_Abstract implements Syncroton_Data_IData
     {
         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) 
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " create entry");
-        
+
+        $this->_assertContentControllerParams($folderId);
         $entry = $this->toTineModel($entry);
         
         // container_id gets set to personal folder in application specific controller if missing
@@ -372,6 +373,7 @@ abstract class ActiveSync_Frontend_Abstract implements Syncroton_Data_IData
             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " update CollectionId: $folderId Id: $serverId");
         
         try {
+            $this->_assertContentControllerParams($folderId);
             $oldEntry = $this->_contentController->get($serverId);
         } catch (Tinebase_Exception_NotFound $tenf) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
index 14224bc..481d0f6 100644 (file)
@@ -578,8 +578,11 @@ class Calendar_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract implemen
                     if ($entry && 
                         $this->_device->devicetype === Syncroton_Model_Device::TYPE_IPHONE &&
                         $this->_syncFolderId       !== $this->_getDefaultContainerId()) {
-                            // keep attendees as the are / they were not sent to the device before
-                            continue;
+
+                        if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(
+                            __METHOD__ . '::' . __LINE__ . " keep attendees as the are / they were not sent to the device before ");
+
+                        continue;
                     }
 
                     $newAttendees = array();
@@ -603,7 +606,6 @@ class Calendar_Frontend_ActiveSync extends ActiveSync_Frontend_Abstract implemen
                             $lastName  = $attendee->name;
                         }
                         
-                        // @todo handle resources
                         $newAttendees[] = array(
                             'userType'  => Calendar_Model_Attender::USERTYPE_USER,
                             'firstName' => $firstName,
index 246fc3e..2eb6c81 100644 (file)
@@ -443,6 +443,19 @@ class Calendar_Model_Attender extends Tinebase_Record_Abstract
                     }
                 }
 
+                // does a list with this id exist?
+                if (! $attendeeId) {
+                    try {
+                        $group = Tinebase_Group::getInstance()->getGroupById($newAttendee['email']);
+                        if ($group) {
+                            $newAttendee['userType'] = Calendar_Model_Attender::USERTYPE_GROUP;
+                            $attendeeId = $group->getId();
+                        }
+                    } catch (Exception $e) {
+                        // do nothing
+                    }
+                }
+
                 if (! $attendeeId) {
                     // autocreate a contact if allowed
                     $contact = self::resolveEmailToContact($newAttendee, $_implicitAddMissingContacts);
index ad06d23..9135c63 100644 (file)
@@ -55,8 +55,9 @@ Tine.Calendar.Printer.DaysViewRenderer = Ext.extend(Tine.Calendar.Printer.BaseRe
             cropper = node.getElementsByClassName('cal-daysviewpanel-cropper')[0],
             body = node.getElementsByClassName('cal-daysviewpanel-body')[0],
             dayStartPx = view.getTimeOffset(view.dayStart),
+            dayEndPx = view.getTimeOffset(view.dayEnd),
             fullHeight = view.getTimeOffset(view.startDate.add(Date.DAY, 1).add(Date.MINUTE, -1)),
-            cropHeight = view.dayEndPx - dayStartPx + 20,
+            cropHeight = dayEndPx - dayStartPx + 20,
             scrollerHeight = view.cropDayTime ? cropHeight : fullHeight;
 
         daysViewPanel.id = this.panelId = Ext.id();
@@ -65,6 +66,7 @@ Tine.Calendar.Printer.DaysViewRenderer = Ext.extend(Tine.Calendar.Printer.BaseRe
         // @TODO: if you have a lot of allDayEvents, your scroller gets smaller,
         //        this might be confusing in print
         header.style.height = Math.max(header.firstChild.style.height, header.style.height);
+        scroller.style.height = scrollerHeight + 'px';
         scroller.style.width = null;
 
         return this.generateTitle(view) + node.innerHTML;
index 7be2096..d271435 100644 (file)
@@ -148,7 +148,7 @@ class HumanResources_Model_Employee extends Tinebase_Record_Abstract
             ),
             'bday' => array(
                 'label' => 'Birthday', //_('Birthday')
-                'type'  => 'date',
+                'type'  => 'datetime',
                 'group' => 'private',
             ),
             'bank_account_holder' => array(
index 71c06b5..2e9bf8f 100644 (file)
@@ -67,7 +67,7 @@ class Setup_Server_Json extends Tinebase_Server_Json
                 ));
             }
             
-            // check json key for all methods but some exceptoins
+            // check json key for all methods but some exceptions
             if (! in_array($method, $anonymnousMethods) && Setup_Core::configFileExists()
                      && ( empty($jsonKey) || $jsonKey != Setup_Core::get('jsonKey')
                             || !Setup_Core::isRegistered(Setup_Core::USER)
index e1583ee..9dcb533 100644 (file)
@@ -34,6 +34,7 @@ Tine.Tinebase.tineInit.initAjax = Tine.Tinebase.tineInit.initAjax.createIntercep
 Tine.Tinebase.tineInit.initRegistry = Tine.Tinebase.tineInit.initRegistry.createInterceptor(function () {
     Tine.Tinebase.tineInit.clearRegistry();
     Tine.Tinebase.tineInit.getAllRegistryDataMethod = 'Setup.getAllRegistryData';
+    Tine.Tinebase.tineInit.jsonKeyCookieId = 'TINE20SETUPJSONKEY';
     Tine.Tinebase.tineInit.stateful = false;
     
     return true;
index 0f0c365..8c3e9fa 100644 (file)
@@ -266,17 +266,28 @@ Tine.Timetracker.TimesheetGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         Tine.Timetracker.TimesheetGridPanel.superclass.initActions.call(this);
     },
 
+    /**
+     * check user exportGrant for timeaccounts
+     * NOTE: manage_timeaccounts ALWAYS allows to export
+     *
+     * @param action
+     * @param grants
+     * @param records
+     * @returns {boolean}
+     */
     updateExportAction: function(action, grants, records) {
         var exportGrant = true;
-        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;
+        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;
+                    }
                 }
-            }
-        });
+            });
+        }
 
         var disable = ! exportGrant;
         action.setDisabled(disable);
index 98b18c9..cdd9490 100644 (file)
@@ -950,7 +950,17 @@ abstract class Tinebase_Record_Abstract implements Tinebase_Record_Interface
             
             $ownField = $this->__get($fieldName);
             $recordField = $_record->$fieldName;
-            
+
+            if ($fieldName == 'customfields' && is_array($ownField) && is_array($recordField)) {
+                // special handling for customfields, remove empty customfields from array
+                foreach (array_keys($recordField, '', true) as $key) {
+                    unset($recordField[$key]);
+                }
+                foreach (array_keys($ownField, '', true) as $key) {
+                    unset($ownField[$key]);
+                }
+            }
+
             if (in_array($fieldName, $this->_datetimeFields)) {
                 if ($ownField instanceof DateTime
                     && $recordField instanceof DateTime) {
index 5f4a05b..7f5a290 100644 (file)
@@ -583,6 +583,8 @@ class Tinebase_User
                         . ' Removing/unset current jpegphoto');
                     $contact->jpegphoto = false;
                 }
+            } else {
+                $syncPhoto = false;
             }
             $diff = $contact->diff($originalContact, $syncPhoto ? array('n_fn') : array('jpegphoto', 'n_fn'));
             if (! $diff->isEmpty() || ($originalContact->jpegphoto === 0 && ! empty($contact->jpegphoto)) ) {
index 058d812..a2bcafc 100644 (file)
@@ -268,7 +268,7 @@ abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\D
      *
      * @return \Sabre\DAV\INode[]
      */
-    function getChildren()
+    public function getChildren()
     {
         $children = array();
         
@@ -280,7 +280,11 @@ abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\D
                 
                 if ($this->_hasPersonalFolders) {
                     $children[] = $this->getChild(
-                        $this->_useIdAsName ? Tinebase_Core::getUser()->contact_id : $this->_useLoginAsFolderName() ? Tinebase_Core::getUser()->accountLoginName : Tinebase_Core::getUser()->accountDisplayName
+                        $this->_useIdAsName ?
+                            Tinebase_Core::getUser()->contact_id :
+                            ($this->_useLoginAsFolderName() ?
+                                Tinebase_Core::getUser()->accountLoginName :
+                                Tinebase_Core::getUser()->accountDisplayName)
                     );
                     
                     $otherUsers = Tinebase_Container::getInstance()->getOtherUsers(Tinebase_Core::getUser(), $this->_getApplicationName(), array(
@@ -291,7 +295,9 @@ abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\D
                     foreach ($otherUsers as $user) {
                         if ($user->contact_id && $user->visibility === Tinebase_Model_User::VISIBILITY_DISPLAYED) {
                             try {
-                                $folderId = $this->_useIdAsName ? $user->contact_id : $this->_useLoginAsFolderName() ? $user->accountLoginName : $user->accountDisplayName;
+                                $folderId = $this->_useIdAsName ?
+                                    $user->contact_id :
+                                    ($this->_useLoginAsFolderName() ? $user->accountLoginName : $user->accountDisplayName);
 
                                 $children[] = $this->getChild($folderId);
                             } catch (\Sabre\DAV\Exception\NotFound $sdavenf) {
@@ -536,7 +542,7 @@ abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\D
                             $user = $this->_getUser(Tinebase_Helper::array_value(1, $this->_getPathParts()));
                             $contact = Addressbook_Controller_Contact::getInstance()->get($user->contact_id);
                         } catch (Tinebase_Exception_NotFound $tenf) {
-                            continue;
+                            continue 2;
                         }
                         
                         $response[$property] = $contact->n_fileas;
@@ -742,7 +748,7 @@ abstract class Tinebase_WebDav_Collection_AbstractContainerTree extends \Sabre\D
 
     protected function _getUser($_id)
     {
-        $classCacheId = ($this->_useIdAsName ? 'contact_id' : $this->_useLoginAsFolderName() ? 'accountLoginName' : 'accountDisplayName') . $_id;
+        $classCacheId = ($this->_useIdAsName ? 'contact_id' : ($this->_useLoginAsFolderName() ? 'accountLoginName' : 'accountDisplayName')) . $_id;
 
         if (isset(self::$_classCache[__FUNCTION__][$classCacheId])) {
             return self::$_classCache[__FUNCTION__][$classCacheId];
index 4a60a5a..6a8d4e9 100644 (file)
@@ -84,6 +84,11 @@ Tine.Tinebase.tineInit = {
     stateful: true,
 
     /**
+     * @cfg {String} jsonKeyCookieId
+     */
+    jsonKeyCookieId: 'TINE20JSONKEY',
+
+    /**
      * @cfg {String} requestUrl
      */
     requestUrl: 'index.php',
@@ -356,9 +361,9 @@ Tine.Tinebase.tineInit = {
         Ext.Ajax.on('beforerequest', function (connection, options) {
 
             var jsonKey = Tine.Tinebase.registry && Tine.Tinebase.registry.get ? Tine.Tinebase.registry.get('jsonKey') : '';
-            if (Tine.Tinebase.tineInit.jsonKeyCookieProvider.get('TINE20JSONKEY')) {
-                var cookieJsonKey = Tine.Tinebase.tineInit.jsonKeyCookieProvider.get('TINE20JSONKEY');
-                Tine.Tinebase.tineInit.jsonKeyCookieProvider.clear('TINE20JSONKEY');
+            if (Tine.Tinebase.tineInit.jsonKeyCookieProvider.get(this.jsonKeyCookieId)) {
+                var cookieJsonKey = Tine.Tinebase.tineInit.jsonKeyCookieProvider.get(this.jsonKeyCookieId);
+                Tine.Tinebase.tineInit.jsonKeyCookieProvider.clear(this.jsonKeyCookieId);
                 // NOTE cookie reset is not always working in IE, so we need to check jsonKey again
                 if (cookieJsonKey && cookieJsonKey != "null") {
                     jsonKey = cookieJsonKey;
@@ -417,6 +422,8 @@ Tine.Tinebase.tineInit = {
                 json: options.jsonData
             };
         });
+
+
         
         /**
          * inspect completed responses => staus code == 200
index f47d439..ddeae40 100644 (file)
@@ -105,6 +105,10 @@ Ext.ux.Printer.BaseRenderer = Ext.extend(Object, {
     doc.write(this.generateHTML(component));
     doc.close();
 
+    // resize to full height as browser might print only the visible area
+    var totalHeight = Ext.fly(doc.body).getHeight();
+    Ext.fly(frame).setStyle('height', totalHeight+'px');
+
     this.doPrintOnStylesheetLoad.defer(10, this, [frame.contentWindow, component]);
   },
   
index 76aa852..3550c68 100644 (file)
@@ -30,7 +30,10 @@ if (PHP_VERSION_ID > 50600) {
         iconv_set_encoding('internal_encoding', "UTF-8");
     }
 }
-mb_internal_encoding("UTF-8");
+
+if (extension_loaded('mbstring')) {
+    mb_internal_encoding("UTF-8");
+}
 
 // intialize composers autoloader
 $autoloader = require __DIR__ . '/vendor/autoload.php';