0010078: caldav client / import caldav calendars/events via CLI
authorPaul Mehrer <p.mehrer@metaways.de>
Fri, 27 Jun 2014 08:01:26 +0000 (10:01 +0200)
committerPhilipp Schüle <p.schuele@metaways.de>
Thu, 4 Sep 2014 09:26:36 +0000 (11:26 +0200)
* class Calendar_Import_CalDav added, it is the caldav client
* added method importCalDav to Calendar_Frontend_Cli
* adjusted usage message in tine20.php

https://forge.tine20.org/mantisbt/view.php?id=10078

Change-Id: Ieb74a353e073e97cc1b362a701a5c1f9b0498997
Reviewed-on: http://gerrit.tine20.com/customers/646
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Calendar/Convert/Event/VCalendar/Abstract.php
tine20/Calendar/Frontend/Cli.php
tine20/Calendar/Frontend/WebDAV/Event.php
tine20/Calendar/Import/CalDav/Client.php [new file with mode: 0644]
tine20/Calendar/Import/CalDav/Decorator/Abstract.php [new file with mode: 0644]
tine20/Calendar/Import/CalDav/Decorator/MacOSX.php [new file with mode: 0644]
tine20/Calendar/Import/CalDav/SabreAttachProperty.php [new file with mode: 0644]
tine20/Tinebase/Import/CalDav/Client.php [new file with mode: 0644]
tine20/Tinebase/Import/CalDav/GroupMemberSet.php [new file with mode: 0644]
tine20/Tinebase/Model/ContainerFilter.php
tine20/tine20.php [changed mode: 0644->0755]

index a1b9bc3..5734461 100644 (file)
@@ -722,7 +722,9 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
             $email = $calAddress['EMAIL']->getValue();
         } else {
             if (!preg_match('/(?P<protocol>mailto:|urn:uuid:)(?P<email>.*)/i', $calAddress->getValue(), $matches)) {
-                throw new Tinebase_Exception_UnexpectedValue('invalid attendee provided: ' . $calAddress->getValue());
+                if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) 
+                    Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' invalid attendee provided: ' . $calAddress->getValue());
+                return null;
             }
             $email = $matches['email'];
         }
@@ -969,13 +971,19 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
                 case 'ATTACH':
                     $name = (string) $property['FILENAME'];
                     $managedId = (string) $property['MANAGED-ID'];
+                    $value = (string) $property['VALUE'];
+                    $attachment = NULL;
+                    $readFromURL = false;
+                    $url = '';
+                    
+                    if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
+                        Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' attachment found: ' . $name . ' ' . $managedId);
                     
                     if ($managedId) {
                         $attachment = $event->attachments instanceof Tinebase_Record_RecordSet ?
                             $event->attachments->filter('hash', $property['MANAGED-ID'])->getFirstRecord() :
                             NULL;
                         
-                        
                         // NOTE: we might miss a attachment here for the following reasons
                         //       1. client reuses a managed id (we are server):
                         //          We havn't observerd this yet. iCal client reuse manged id's
@@ -992,21 +1000,48 @@ class Calendar_Convert_Event_VCalendar_Abstract implements Tinebase_Convert_Inte
                         //       2. server send his managed id (we are client)
                         //          * we need to download the attachment (here?)
                         //          * we need to have a mapping externalid / internalid (where?)
+                        
                         if (! $attachment) {
-//                             $attachment = new Tinebase_Model_Tree_Node(array(
-//                                 'name'         => $name,
-//                                 'type'         => Tinebase_Model_Tree_Node::TYPE_FILE,
-//                                 'contenttype'  => (string) $property['FMTTYPE'],
-//                                 'hash'         => $managedId,
-//                             ), true);
+                            $readFromURL = true;
+                            $url = $property->getValue();
+                        } else {
+                            $attachments->addRecord($attachment);
                         }
-                        
-                        $attachments->addRecord($attachment);
+                    } elseif('URI' === $value) {
+                        /*
+                         * ATTACH;VALUE=URI:https://ical.familienservice.de/calendars/__uids__/0AA0
+ 3A3B-F7B6-459A-AB3E-4726E53637D0/dropbox/4971F93F-8657-412B-841A-A0FD913
+ 9CD61.dropbox/Canada.png
+                         */
+                        $readFromURL = true;
+                        $url = $property->getValue();
+                        $name = parse_url($url, PHP_URL_PATH);
+                        $name = pathinfo($name, PATHINFO_BASENAME);
                     }
-                    
                     // base64
                     else {
                         // @TODO: implement (check if add / update / update is needed)
+                        if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' attachment found that could not be imported due to missing managed id');
+                    }
+                    
+                    if($readFromURL) {
+                        if (preg_match('#^(https?://)(.*)$#', str_replace(array("\n","\r"), '', $url), $matches)) {
+                            // we are client and found an external hosted attachment that we need to import
+                            $user = Tinebase_Core::getUser();
+                            $userCredentialCache = Tinebase_Core::get(Tinebase_Core::USERCREDENTIALCACHE);
+                            $stream = fopen($matches[1] . $userCredentialCache->username . ':' . $userCredentialCache->password . '@' . $matches[2], 'r');
+                            $attachment = new Tinebase_Model_Tree_Node(array(
+                                'name'         => $name,
+                                'type'         => Tinebase_Model_Tree_Node::TYPE_FILE,
+                                'contenttype'  => (string) $property['FMTTYPE'],
+                                'tempFile'     => $stream,
+                                ), true);
+                            $attachments->addRecord($attachment);
+                        } else {
+                            if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' attachment found with malformed url: '.$url. ' '.$name. ' '.$managedId);
+                        }
                     }
                     break;
                     
index b47bde9..f9926d7 100644 (file)
@@ -29,18 +29,25 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
      * @return void
      */
     protected $_help = array(
+        'importCalDav' => array(
+            'description'    => 'import calendar/events from a CalDav source',
+            'params'         => array(
+                'url'        => 'CalDav source URL',
+                'caldavuserfile' => 'CalDav user file containing utf8 username;pwd',
+             )
+        ),
         'importegw14' => array(
-            'description'   => 'imports calendars/events from egw 1.4',
-            'params'        => array(
-                'host'     => 'dbhost',
-                'username' => 'username',
-                'password' => 'password',
-                'dbname'   => 'dbname'
+            'description'    => 'imports calendars/events from egw 1.4',
+            'params'         => array(
+                'host'       => 'dbhost',
+                'username'   => 'username',
+                'password'   => 'password',
+                'dbname'     => 'dbname'
             )
         ),
         'exportICS' => array(  
-            'description' => "export calendar as ics", 
-            'params' => array('container_id') 
+            'description'    => "export calendar as ics", 
+            'params'         => array('container_id') 
         ),
     );
     
@@ -131,4 +138,55 @@ class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
         $be = new Calendar_Backend_Sql();
         $be->repairDanglingDisplaycontainerEvents();
     }
+    
+    /**
+     * import calendar/events from a CalDav source
+     * 
+     * param Zend_Console_Getopt $_opts
+     */
+    public function importCalDav(Zend_Console_Getopt $_opts)
+    {
+        $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile'));
+        
+        $writer = new Zend_Log_Writer_Stream('php://output');
+        $writer->addFilter(new Zend_Log_Filter_Priority(4));
+        Tinebase_Core::getLogger()->addWriter($writer);
+        
+        $users = $this->_readCalDavUserFile($args['caldavuserfile']);
+        
+        $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
+        $client->setVerifyPeer(false);
+        
+        $client->importAllCalendarDataForUsers($users);
+    }
+    
+    /**
+     * read caldav user credentials file
+     * 
+     * - file should have the following format (CSV):
+     * USERNAME1;PASSWORD1
+     * USERNAME2;PASSWORD2
+     * 
+     * @param string $file
+     * @throws Exception
+     */
+    protected function _readCalDavUserFile($file)
+    {
+        if (!($fh = fopen($file, 'r')))
+        {
+            Tinebase_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . ' couldn\'t open file: '.$file);
+            throw new Exception('Couldn\'t open file: '.$file);
+        }
+        $users = array();
+        while ($row = fgetcsv($fh, 2048, ';'))
+        {
+            $users[$row[0]] = $row[1];
+        }
+        if (count($users) < 1)
+        {
+            Tinebase_Core::getLogger()->error(__METHOD__ . '::' . __LINE__ . ' no users found in: '.$file);
+            throw new Exception('No users found in: '.$file);
+        }
+        return $users;
+    }
 }
index a8e1aff..73c067c 100644 (file)
@@ -109,7 +109,7 @@ class Calendar_Frontend_WebDAV_Event extends Sabre\DAV\File implements Sabre\Cal
      * @param  stream|string             $vobjectData
      * @return Calendar_Frontend_WebDAV_Event
      */
-    public static function create(Tinebase_Model_Container $container, $name, $vobjectData)
+    public static function create(Tinebase_Model_Container $container, $name, $vobjectData, $onlyCurrentUserOrganizer = false)
     {
         if (is_resource($vobjectData)) {
             $vobjectData = stream_get_contents($vobjectData);
@@ -122,6 +122,13 @@ class Calendar_Frontend_WebDAV_Event extends Sabre\DAV\File implements Sabre\Cal
         list($backend, $version) = Calendar_Convert_Event_VCalendar_Factory::parseUserAgent($_SERVER['HTTP_USER_AGENT']);
         
         $event = Calendar_Convert_Event_VCalendar_Factory::factory($backend, $version)->toTine20Model($vobjectData);
+        
+        if (true === $onlyCurrentUserOrganizer) {
+            if ($event->organizer && $event->organizer != Tinebase_Core::getUser()->contact_id) {
+                return null;
+            }
+        }
+        
         $event->container_id = $container->getId();
         $id = ($pos = strpos($name, '.')) === false ? $name : substr($name, 0, $pos);
         if (strlen($id) > 40) {
diff --git a/tine20/Calendar/Import/CalDav/Client.php b/tine20/Calendar/Import/CalDav/Client.php
new file mode 100644 (file)
index 0000000..5ff171d
--- /dev/null
@@ -0,0 +1,356 @@
+<?php
+//./tine20.php --username unittest --method Calendar.importCalDav url="https://osx-testfarm-mavericks-server.hh.metaways.de:8443" caldavuserfile=caldavuserfile.csv
+
+
+/**
+ * Tine 2.0
+ * 
+ * @package     Calendar
+ * @subpackage  Import
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @todo        
+ */
+
+/**
+ * Calendar_Import_CalDAV
+ * 
+ * @package     Calendar
+ * @subpackage  Import
+ * 
+ */
+class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
+{
+    protected $calendars = array();
+    protected $calendarICSs = array();
+    protected $maxBulkRequest = 20;
+    protected $mapToDefaultContainer = 'calendar';
+    protected $decorator = null;
+    
+    const findAllCalendarsRequest =
+'<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:">
+  <d:prop>
+    <d:resourcetype />
+    <d:acl />
+    <d:displayname />
+    <x:supported-calendar-component-set xmlns:x="urn:ietf:params:xml:ns:caldav"/>
+  </d:prop>
+</d:propfind>';
+    
+    const findAllCalendarICSsRequest = 
+'<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:">
+  <d:prop>
+    <x:calendar-data xmlns:x="urn:ietf:params:xml:ns:caldav"/>
+  </d:prop>
+</d:propfind>';
+    
+    const getAllCalendarDataRequest =
+'<?xml version="1.0"?>
+<b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
+  <a:prop>
+    <b:calendar-data />
+  </a:prop>
+';
+    
+    public function __construct(array $a, $flavor)
+    {
+        parent::__construct($a);
+        
+        $flavor = 'Calendar_Import_CalDav_Decorator_'.$flavor;
+        $this->decorator = new $flavor($this);
+    }
+    
+    public function findAllCalendars()
+    {
+        if ('' == $this->calendarHomeSet && ! $this->findCalendarHomeSet())
+            return false;
+        
+        //issue with follow location in curl!?!?
+        if ($this->calendarHomeSet[strlen($this->calendarHomeSet)-1] !== '/')
+            $this->calendarHomeSet .= '/';
+        
+        $result = $this->calDavRequest('PROPFIND', $this->calendarHomeSet, $this->decorator->preparefindAllCalendarsRequest(self::findAllCalendarsRequest), 1);
+        
+        foreach ($result as $uri => $response) {
+            if (isset($response['{DAV:}resourcetype']) && isset($response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']) && 
+                    $response['{DAV:}resourcetype']->is('{urn:ietf:params:xml:ns:caldav}calendar') &&
+                    in_array('VEVENT', $response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue())) {
+                $this->calendars[$uri]['acl'] = $response['{DAV:}acl'];
+                $this->calendars[$uri]['displayname'] = $response['{DAV:}displayname'];
+                $this->decorator->processAdditionalCalendarProperties($this->calendars[$uri], $response);
+                $this->resolvePrincipals($this->calendars[$uri]['acl']->getPrivileges());
+            }
+        }
+        
+        if (count($this->calendars) > 0) {
+            return true;
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t find a calendar');
+            return false;
+        }
+    }
+    
+    public function findAllCalendarICSs()
+    {
+        if (count($this->calendars) < 1 && ! $this->findAllCalendars())
+            return false;
+        
+        foreach ($this->calendars as $calUri => $calendar) {
+            $result = $this->calDavRequest('PROPFIND', $calUri, self::findAllCalendarICSsRequest, 1);
+            foreach ($result as $ics => $value) {
+                if (strpos($ics, '.ics') !== FALSE)
+                    $this->calendarICSs[$calUri][] = $ics;
+            }
+        }
+        
+        if (count($this->calendarICSs) > 0) {
+            return true;
+        } else {
+            if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' all found calendars are empty');
+            return false;
+        }
+    }
+    
+    protected function findContainerForCalendar($calendarUri, $displayname, $defaultCalendarsName, $type, $application_id, $modelName)
+    {
+        $uuid = basename($calendarUri);
+        
+        $filter = new Tinebase_Model_ContainerFilter(array(
+            array(
+                'field' => 'uuid', 
+                'operator' => 'equals', 
+                'value' => $uuid
+            ),
+            array(
+                'field' => 'model', 
+                'operator' => 'equals', 
+                'value' => 'Calendar_Model_Event'
+            ),
+        ));
+        $existingCalendar = Tinebase_Container::getInstance()->search($filter, null, false, false, 'sync')->getFirstRecord();
+        if($existingCalendar) {
+            return $existingCalendar;
+        }
+        
+        $counter = '';
+        
+        if ($defaultCalendarsName == $displayname) {
+            $existingCalendar = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
+            if (! $existingCalendar->uuid) {
+                $existingCalendar->uuid = $uuid;
+                return $existingCalendar;
+            }
+            $existingCalendar = null;
+            $counter = 1;
+        }
+        
+        try {
+            while (true) {
+                $existingCalendar = Tinebase_Container::getInstance()->getContainerByName('Calendar', $displayname . $counter, $type, Tinebase_Core::getUser());
+                if (! $existingCalendar->uuid) {
+                    $existingCalendar->uuid = $uuid;
+                    return $existingCalendar;
+                }
+                $counter += 1;
+            }
+        } catch (Tinebase_Exception_NotFound $e) {
+            $newContainer = new Tinebase_Model_Container(array(
+                'name'              => $displayname . $counter,
+                'type'              => $type,
+                'backend'           => 'Sql',
+                'application_id'    => $application_id,
+                'model'             => $modelName,
+                'uuid'              => $uuid
+            ));
+            return Tinebase_Container::getInstance()->addContainer($newContainer);
+        }
+    }
+    
+    public function importAllCalendarData($onlyCurrentUserOrganizer = false)
+    {
+        if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs())
+            return false;
+        
+        Calendar_Controller_Event::getInstance()->sendNotifications(false);
+        Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
+        
+        $this->decorator->initCalendarImport();
+        
+        $modelName = Tinebase_Core::getApplicationInstance('Calendar')->getDefaultModel();
+        $application_id = Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId();
+        $type = Tinebase_Model_Container::TYPE_PERSONAL; //Tinebase_Model_Container::TYPE_SHARED;
+        $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
+        
+        //decide which calendar to use as default calendar
+        //if there is a remote default calendar, use that. If not, use the first we find
+        $defaultCalendarsName = '';
+        foreach ($this->calendarICSs as $calUri => $calICSs) {
+            if ($this->mapToDefaultContainer == $this->calendars[$calUri]['displayname']) {
+                $container = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
+            } elseif ($defaultsCalendarsName === '') {
+                $defaultCalendarsName = $this->calendars[$calUri]['displayname'];
+            }
+        }
+        
+        foreach ($this->calendarICSs as $calUri => $calICSs) {
+            $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['displayname'], $defaultCalendarsName,
+                    $type, $application_id, $modelName);
+            
+            $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
+            
+            $grants = $this->getCalendarGrants($calUri);
+            Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
+            
+            $start = 0;
+            $max = count($calICSs);
+            do {
+                $requestEnd = '';
+                for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
+                    $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
+                }
+                $start = $i;
+                $requestEnd .= '</b:calendar-multiget>';
+                $result = $this->calDavRequest('REPORT', $calUri, self::getAllCalendarDataRequest . $requestEnd, 1);
+                
+                foreach ($result as $key => $value) {
+                    if (isset($value['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
+                        $name = explode('/', $key);
+                        $name = end($name);
+                        try {
+                            Calendar_Frontend_WebDAV_Event::create(
+                                $container,
+                                $name,
+                                $value['{urn:ietf:params:xml:ns:caldav}calendar-data'],
+                                $onlyCurrentUserOrganizer
+                            );
+                        } catch(Tinebase_Exception_UnexpectedValue $e) {
+                            if ('no vevents found' != $e->getMessage()) {
+                                throw $e;
+                            }
+                        }
+                    }
+                }
+            } while($start < $max);
+        }
+        return true;
+    }
+    
+    public function getCalendarGrants($calUri)
+    {
+        $grants = array();
+        $user = array();
+        $type = array();
+        $privilege = array();
+        foreach ($this->calendars[$calUri]['acl']->getPrivileges() as $ace)
+        {
+            if ('{DAV:}authenticated' == $ace['principal']) {
+                $user[] = 0;
+                $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
+                $privilege[] = $ace['privilege'];
+            } elseif (isset($this->principals[$ace['principal']])) {
+                $user[] = $this->principals[$ace['principal']]->getId();
+                $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
+                $privilege[] = $ace['privilege'];
+            } elseif (isset($this->principalGroups[$ace['principal']])) {
+                foreach($this->principalGroups[$ace['principal']] as $principal) {
+                    if ('{DAV:}authenticated' == $principal) {
+                        $user[] = 0;
+                        $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
+                        $privilege[] = $ace['privilege'];
+                    } elseif (isset($this->principals[$principal])) {
+                        $user[] = $this->principals[$principal]->getId();
+                        $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
+                        $privilege[] = $ace['privilege'];
+                    } else {
+                        if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                            Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' there is an unresolved principal: ' . $principal . ' in group: ' . $ace['principal']);
+                    }
+                }
+            } else {
+                if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                    Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t resolve principal: '.$ace['principal']);
+            }
+        }
+        for ($i=0; $i<count($user); ++$i) {
+            switch ($privilege[$i]) {
+                case '{DAV:}all':
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ ] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADMIN] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_PRIVATE] = true;
+                    break;
+                case '{urn:ietf:params:xml:ns:caldav}read-free-busy':
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
+                    break;
+                case '{DAV:}read':
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
+                    break;
+                case '{DAV:}write':
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
+                    $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
+                    break;
+                case '{DAV:}read-current-user-privilege-set':
+                    continue;
+                default:
+                    if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
+                        Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' unknown privilege: ' . $privilege[$i]);
+                    continue;
+            }
+            $grants[$user[$i]]['account_id'] = $user[$i];
+            $grants[$user[$i]]['account_type'] = $type[$i];
+        }
+        if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
+            Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' found grants: ' . print_r($grants, true) . ' for calendar: ' . $calUri);
+        
+        return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
+    }
+    
+    public function importAllCalendarDataForUsers(array $users)
+    {
+        if (!$this->findCurrentUserPrincipalForUsers($users))
+            return false;
+        
+        $result = true;
+        // first only import events where the current user is also the organizer
+        foreach ($users as $username => $pwd) {
+            $this->clearCurrentUserCalendarData();
+            $this->userName = $username;
+            $this->password = $pwd;
+            if (!$this->importAllCalendarData(true)) {
+                $result = false;
+            }
+        }
+        // then import all events again
+        foreach ($users as $username => $pwd) {
+            $this->clearCurrentUserCalendarData();
+            $this->userName = $username;
+            $this->password = $pwd;
+            if (!$this->importAllCalendarData(false)) {
+                $result = false;
+            }
+        }
+        return $result;
+    }
+    
+    public function clearCurrentUserCalendarData()
+    {
+        $this->clearCurrentUserData();
+        $this->calendars = array();
+        $this->calendarICSs = array();
+    }
+}
\ No newline at end of file
diff --git a/tine20/Calendar/Import/CalDav/Decorator/Abstract.php b/tine20/Calendar/Import/CalDav/Decorator/Abstract.php
new file mode 100644 (file)
index 0000000..1100ec6
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+abstract class Calendar_Import_CalDav_Decorator_Abstract
+{
+    protected $client;
+    
+    public function __construct($client)
+    {
+        $this->client = $client;
+    }
+    
+    public function preparefindAllCalendarsRequest($request)
+    {
+        return $request;
+    }
+    
+    public function processAdditionalCalendarProperties(array &$calendar, array $response) {}
+    
+    public function initCalendarImport() {}
+    
+    public function setCalendarProperties(Tinebase_Model_Container $calendarContainer, array $calendar)
+    {
+        if (isset($calendar['color']))
+            $calendarContainer->color = $calendar['color'];
+    }
+}
\ No newline at end of file
diff --git a/tine20/Calendar/Import/CalDav/Decorator/MacOSX.php b/tine20/Calendar/Import/CalDav/Decorator/MacOSX.php
new file mode 100644 (file)
index 0000000..d192b42
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+class Calendar_Import_CalDav_Decorator_MacOSX extends Calendar_Import_CalDav_Decorator_Abstract
+{
+    public function preparefindAllCalendarsRequest($request)
+    {
+        $doc = new DOMDocument();
+        $doc->loadXML($request);
+        //$bulk = $doc->createElementNS('http://me.com/_namespace/', 'osxme:bulk-requests');
+        $color = $doc->createElementNS('http://apple.com/ns/ical/', 'osxical:calendar-color');
+        $prop = $doc->getElementsByTagNameNS('DAV:', 'prop')->item(0);
+        //$prop->appendChild($bulk);
+        $prop->appendChild($color);
+        return $doc->saveXML();
+    }
+    
+    public function processAdditionalCalendarProperties(array &$calendar, array $response)
+    {
+        /*if (isset($response['{http://apple.com/ns/ical/}calendar-color']))
+            $calendar['color'] = $response['{http://apple.com/ns/ical/}calendar-color'];*/
+    }
+    
+    public function initCalendarImport()
+    {
+        $_SERVER['HTTP_USER_AGENT'] = 'Mac_OS_X/10.9 (13A603) CalendarAgent/174';
+    }
+}
\ No newline at end of file
diff --git a/tine20/Calendar/Import/CalDav/SabreAttachProperty.php b/tine20/Calendar/Import/CalDav/SabreAttachProperty.php
new file mode 100644 (file)
index 0000000..d3ea4fa
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+class Calendar_Import_CalDav_SabreAttachProperty extends Sabre\VObject\Property\Binary
+{
+    protected $isValueBinary = true;
+    
+    public function setRawMimeDirValue($val)
+    {
+        if (($tmp = base64_encode($val)) == base64_decode($tmp))
+        {
+            $this->value = tmp;
+        } else {
+            $this->isValueBinary = false;
+            $this->value = $val;
+        }
+    }
+    
+    public function getRawMimeDirValue()
+    {
+        if ($this->isValueBinary)
+            return base64_encode($this->value);
+        else
+            return $this->value;
+    }
+}
\ No newline at end of file
diff --git a/tine20/Tinebase/Import/CalDav/Client.php b/tine20/Tinebase/Import/CalDav/Client.php
new file mode 100644 (file)
index 0000000..ccff162
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Tine 2.0
+ * 
+ * @package     Tinebase
+ * @subpackage  Import
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Paul Mehrer <p.mehrer@metaways.de>
+ * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
+ * 
+ * @todo        
+ */
+
+/**
+ * Tinebase_Import_CalDav
+ * 
+ * @package     Tinebase
+ * @subpackage  Import
+ * 
+ */
+class Tinebase_Import_CalDav_Client extends \Sabre\DAV\Client
+{
+    protected $currentUserPrincipal = '';
+    protected $calendarHomeSet = '';
+    protected $principals = array();
+    protected $principalGroups = array();
+    
+    protected $requestLogFH;
+    
+    const findCurrentUserPrincipalRequest = 
+'<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:">
+  <d:prop>
+    <d:current-user-principal />
+  </d:prop>
+</d:propfind>';
+
+    const findCalendarHomeSetRequest =
+'<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:">
+  <d:prop>
+    <x:calendar-home-set xmlns:x="urn:ietf:params:xml:ns:caldav"/>
+  </d:prop>
+</d:propfind>';
+    
+    const resolvePrincipalRequest =
+'<?xml version="1.0"?>
+<d:propfind xmlns:d="DAV:">
+  <d:prop>
+    <d:group-member-set />
+    <d:displayname />
+  </d:prop>
+</d:propfind>';
+    
+    public function __construct(array $a)
+    {
+        parent::__construct($a);
+        
+        //$this->requestLogFH = fopen('/var/log/tine20/requestLog', 'w');
+        
+        $this->propertyMap['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'] = 'Sabre\CalDAV\Property\SupportedCalendarComponentSet';
+        $this->propertyMap['{DAV:}acl'] = 'Sabre\DAVACL\Property\Acl';
+        $this->propertyMap['{DAV:}group-member-set'] = 'Tinebase_Import_CalDav_GroupMemberSet';
+    }
+    
+    public function findCurrentUserPrincipal()
+    {
+        $result = $this->calDavRequest('PROPFIND', '/principals/', self::findCurrentUserPrincipalRequest);
+        if (isset($result['{DAV:}current-user-principal']))
+        {
+            try {
+                $user = Tinebase_User::getInstance()->getUserByLoginName($this->userName);
+                Tinebase_Core::set(Tinebase_Core::USER, $user);
+                $credentialCache = Tinebase_Auth_CredentialCache::getInstance()->cacheCredentials($this->userName, $this->password);
+                Tinebase_Core::set(Tinebase_Core::USERCREDENTIALCACHE, $credentialCache);
+            } catch (Tinebase_Exception_NotFound $e) {
+                Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' can\'t find tine20 user: ' . $this->userName);
+                return false;
+            }
+            $this->currentUserPrincipal = $result['{DAV:}current-user-principal'];
+            $this->principals[$this->currentUserPrincipal] = $user;
+            return true;
+        }
+        
+        Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' couldn\'t find current users principal');
+        return false;
+    }
+    
+    public function findCurrentUserPrincipalForUsers(array $users)
+    {
+        $result = true;
+        foreach ($users as $username => $pwd) {
+            $this->userName = $username;
+            $this->password = $pwd;
+            if (!$this->findCurrentUserPrincipal()) {
+                $result = false;
+            }
+        }
+        return $result;
+    }
+    
+    public function findCalendarHomeSet()
+    {
+        if ('' == $this->currentUserPrincipal && ! $this->findCurrentUserPrincipal())
+            return false;
+        $result = $this->calDavRequest('PROPFIND', $this->currentUserPrincipal, self::findCalendarHomeSetRequest);
+        if (isset($result['{urn:ietf:params:xml:ns:caldav}calendar-home-set']))
+        {
+            $this->calendarHomeSet = $result['{urn:ietf:params:xml:ns:caldav}calendar-home-set'];
+            return true;
+        }
+        
+        Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' couldn\'t find calendar homeset');
+        return false;
+    }
+    
+    public function resolvePrincipals(array $privileges)
+    {
+        foreach ($privileges as $ace)
+        {
+            if ( $ace['principal'] == '{DAV:}authenticated' || $ace['principal'] == $this->currentUserPrincipal ||
+                 isset($this->principals[$ace['principal']]) || isset($this->principalGroups[$ace['principal']]))
+                         continue;
+            $result = $this->calDavRequest('PROPFIND', $ace['principal'], self::resolvePrincipalRequest);
+            if (isset($result['{DAV:}group-member-set'])) {
+                $this->principalGroups[$ace['principal']] = $result['{DAV:}group-member-set']->getPrincipals();
+            }
+        }
+    }
+    
+    public function clearCurrentUserData()
+    {
+        $this->currentUserPrincipal = '';
+        $this->calendarHomeSet = '';
+    }
+    
+    public function calDavRequest($method, $uri, $body, $depth = 0)
+    {try {
+        $response = $this->request($method, $uri, $body, array(
+            'Depth' => $depth,
+            'Content-Type' => 'text/xml',
+        )); } catch (Exception $e) {
+            echo $method. $uri .$body. $e->getMessage() ."\n\n";throw $e;
+        }
+        
+        $result = $this->parseMultiStatus($response['body']);
+        
+        //fputs($this->requestLogFH, $method.' '.$uri."\n".$body."\n".$depth."\n".$response['body']."\n\n\n\n\n\n\n", 10000000);
+        //echo $body."\n\n";
+        //print_r($response);
+        
+        // If depth was 0, we only return the top item
+        if ($depth===0) {
+            reset($result);
+            $result = current($result);
+            return isset($result[200])?$result[200]:array();
+        }
+        
+        $newResult = array();
+        foreach($result as $href => $statusList)
+        {
+            $newResult[$href] = isset($statusList[200])?$statusList[200]:array();
+        }
+        
+        return $newResult;
+    }
+}
\ No newline at end of file
diff --git a/tine20/Tinebase/Import/CalDav/GroupMemberSet.php b/tine20/Tinebase/Import/CalDav/GroupMemberSet.php
new file mode 100644 (file)
index 0000000..326d55e
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+class Tinebase_Import_CalDav_GroupMemberSet
+{
+    protected $principals = array();
+    
+    public function __construct(array $principals)
+    {
+        $this->principals = $principals;
+    }
+    
+    public function getPrincipals()
+    {
+        return $this->principals;
+    }
+    
+    public static function unserialize(\DOMElement $dom)
+    {
+        $principals = array();
+        $xhrefs = $dom->getElementsByTagNameNS('urn:DAV','href');
+        for($ii=0; $ii < $xhrefs->length; $ii++) {
+            $principals[] = $xhrefs->item($ii)->textContent;
+        }
+        return new self($principals);
+    }
+}
\ No newline at end of file
index e15f78f..9751ed0 100644 (file)
@@ -46,5 +46,6 @@ class Tinebase_Model_ContainerFilter extends Tinebase_Model_Filter_FilterGroup
         'query'             => array('filter' => 'Tinebase_Model_Filter_Query', 'options' => array('fields' => array('name'))),
         'owner'             => array('filter' => 'Tinebase_Model_Filter_ContainerOwner'),
         'model'             => array('filter' => 'Tinebase_Model_Filter_Text'),
+        'uuid'              => array('filter' => 'Tinebase_Model_Filter_Text'),
     );
 }
old mode 100644 (file)
new mode 100755 (executable)
index 3673dba..c32b16b
@@ -28,7 +28,7 @@ try {
         'info|i'                => 'Get usage description of method',
     
         'method=s'              => 'Method to call [required]',
-        'username=s'            => 'Username [required]',
+        'username=s'            => 'Username',
         'password=s'            => 'Password',
         'passwordfile=s'        => 'Name of file that contains password',
     ));