adds some minor formatting and debugging improvements
[tine20] / tine20 / Calendar / Import / CalDav / Client.php
1 <?php
2 //./tine20.php --username unittest --method Calendar.importCalDav url="https://osx-testfarm-mavericks-server.hh.metaways.de:8443" caldavuserfile=caldavuserfile.csv
3
4 /**
5  * Tine 2.0
6  * 
7  * @package     Calendar
8  * @subpackage  Import
9  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
10  * @author      Paul Mehrer <p.mehrer@metaways.de>
11  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
12  */
13
14 /**
15  * Calendar_Import_CalDAV
16  * 
17  * @package     Calendar
18  * @subpackage  Import
19  */
20 class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
21 {
22     protected $calendars = array();
23     protected $calendarICSs = array();
24     protected $existingRecordIds = array();
25     protected $maxBulkRequest = 20;
26     protected $mapToDefaultContainer = 'calendar';
27     protected $decorator = null;
28     
29     protected $component = 'VEVENT';
30     protected $skipComonent = 'VTODO';
31     protected $modelName = 'Calendar_Model_Event';
32     protected $appName = 'Calendar';
33     protected $webdavFrontend = 'Calendar_Frontend_WebDAV_Event';
34     
35     const findAllCalendarsRequest =
36 '<?xml version="1.0"?>
37 <d:propfind xmlns:d="DAV:">
38   <d:prop>
39     <d:resourcetype />
40     <d:acl />
41     <d:displayname />
42     <x:supported-calendar-component-set xmlns:x="urn:ietf:params:xml:ns:caldav"/>
43   </d:prop>
44 </d:propfind>';
45     
46     const findAllCalendarICSsRequest = 
47 '<?xml version="1.0"?>
48 <d:propfind xmlns:d="DAV:">
49   <d:prop>
50     <x:calendar-data xmlns:x="urn:ietf:params:xml:ns:caldav"/>
51   </d:prop>
52 </d:propfind>';
53     
54     const getAllCalendarDataRequest =
55 '<?xml version="1.0"?>
56 <b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
57   <a:prop>
58     <b:calendar-data />
59     <a:getetag />
60   </a:prop>
61 ';
62     
63     const getEventETagsRequest =
64 '<?xml version="1.0"?>
65 <b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
66   <a:prop>
67     <a:getetag />
68   </a:prop>
69 ';
70     
71     public function __construct(array $a, $flavor)
72     {
73         parent::__construct($a);
74         
75         $flavor = 'Calendar_Import_CalDav_Decorator_' . $flavor;
76         $this->decorator = new $flavor($this);
77     }
78     
79     public function findAllCalendars()
80     {
81         if ('' == $this->calendarHomeSet && ! $this->findCalendarHomeSet())
82             return false;
83         
84         //issue with follow location in curl!?!?
85         if ($this->calendarHomeSet[strlen($this->calendarHomeSet)-1] !== '/')
86             $this->calendarHomeSet .= '/';
87         
88         try {
89             $result = $this->calDavRequest('PROPFIND', $this->calendarHomeSet, $this->decorator->preparefindAllCalendarsRequest(self::findAllCalendarsRequest), 1);
90         } catch (Tinebase_Exception $te) {
91             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
92                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' request failed');
93             Tinebase_Exception::log($te);
94             return false;
95         }
96         
97         foreach ($result as $uri => $response) {
98             if (isset($response['{DAV:}resourcetype']) &&
99                     isset($response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']) && 
100                     $response['{DAV:}resourcetype']->is('{urn:ietf:params:xml:ns:caldav}calendar') &&
101                     in_array($this->component, $response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue())
102             ) {
103                 $this->calendars[$uri]['acl'] = $response['{DAV:}acl'];
104                 $this->calendars[$uri]['displayname'] = $response['{DAV:}displayname'];
105                 $this->decorator->processAdditionalCalendarProperties($this->calendars[$uri], $response);
106                 $this->resolvePrincipals($this->calendars[$uri]['acl']->getPrivileges());
107             }
108         }
109         
110         if (count($this->calendars) > 0) {
111             return true;
112         } else {
113             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
114                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t find a calendar');
115             return false;
116         }
117     }
118     
119     public function findAllCalendarICSs()
120     {
121         if (count($this->calendars) < 1 && ! $this->findAllCalendars())
122             return false;
123         
124         foreach ($this->calendars as $calUri => $calendar) {
125             $result = $this->calDavRequest('PROPFIND', $calUri, self::findAllCalendarICSsRequest, 1);
126             foreach ($result as $ics => $value) {
127                 if (strpos($ics, '.ics') !== FALSE)
128                     $this->calendarICSs[$calUri][] = $ics;
129             }
130         }
131         
132         if (count($this->calendarICSs) > 0) {
133             return true;
134         } else {
135             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
136                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' all found calendars are empty');
137             return false;
138         }
139     }
140     
141     /**
142      * findContainerForCalendar
143      * 
144      * @param unknown $calendarUri
145      * @param unknown $displayname
146      * @param unknown $defaultCalendarsName
147      * @param unknown $type
148      * @param string $application_id
149      */
150     protected function findContainerForCalendar($calendarUri, 
151             $displayname, $defaultCalendarsName, $type = Tinebase_Model_Container::TYPE_PERSONAL,
152             $application_id = null)
153     {
154         if (! $application_id) {
155             $application_id = Tinebase_Application::getInstance()->getApplicationByName($this->appName)->getId();
156         }
157         
158         // sha1() the whole calendar uri as it is very hard to separate a uuid string from the uri otherwise
159         $uuid = sha1($calendarUri);
160         
161         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
162                 . ' $calendarUri = ' . $calendarUri . ' / $displayname = ' . $displayname 
163                 . ' / $defaultCalendarsName = ' . $defaultCalendarsName . ' / $uuid = ' . $uuid);
164         
165         $filter = new Tinebase_Model_ContainerFilter(array(
166             array(
167                 'field' => 'uuid', 
168                 'operator' => 'equals', 
169                 'value' => $uuid
170             ),
171             array(
172                 'field' => 'model', 
173                 'operator' => 'equals', 
174                 'value' => $this->modelName
175             ),
176         ));
177         $existingCalendar = Tinebase_Container::getInstance()->search($filter)->getFirstRecord();
178         if ($existingCalendar) {
179             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
180                 . ' Found existing container ' . $existingCalendar->name . ' (id: ' . $existingCalendar->getId() . ')');
181             return $existingCalendar;
182         }
183         
184         $counter = '';
185         
186         if ($defaultCalendarsName == $displayname) {
187             $existingCalendar = Tinebase_Container::getInstance()->getDefaultContainer($this->modelName);
188             if (! $existingCalendar->uuid) {
189                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
190                     . ' Found existing calendar with the same name');
191                 $existingCalendar->uuid = $uuid;
192                 return $existingCalendar;
193             }
194             $existingCalendar = null;
195             $counter = 1;
196         }
197         
198         try {
199             while (true) {
200                 $existingCalendar = Tinebase_Container::getInstance()->getContainerByName($this->appName, $displayname . $counter, $type, Tinebase_Core::getUser());
201                 
202                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
203                     . ' Got calendar: ' . $existingCalendar->name . ' (id: ' . $existingCalendar->getId() . ')');
204                 
205                 if (! $existingCalendar->uuid) {
206                     $existingCalendar->uuid = $uuid;
207                     return $existingCalendar;
208                 }
209                 $counter += 1;
210             }
211         } catch (Tinebase_Exception_NotFound $e) {
212             $newContainer = new Tinebase_Model_Container(array(
213                 'name'              => $displayname . $counter,
214                 'type'              => $type,
215                 'backend'           => 'Sql',
216                 'application_id'    => $application_id,
217                 'model'             => $this->modelName,
218                 'uuid'              => $uuid
219             ));
220             
221             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
222                     . ' Adding container: ' . $newContainer->name . ' for model ' . $this->modelName);
223             
224             return Tinebase_Container::getInstance()->addContainer($newContainer);
225         }
226     }
227     
228     public function importAllCalendars()
229     {
230         if (count($this->calendars) < 1 && ! $this->findAllCalendars()) {
231             return false;
232         }
233         
234         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__ 
235             . ' Importing all calendars for user ' . $this->userName);
236         
237         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->sendNotifications(false);
238         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->useNotes(false);
239         Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
240         
241         $this->decorator->initCalendarImport();
242         
243         $defaultCalendarsName = $this->_getDefaultCalendarsName();
244         
245         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
246             . ' Calendar uris to import: ' . print_r(array_keys($this->calendars), true));
247         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__
248             . ' Calendars to import: ' . print_r($this->calendars, true));
249         
250         foreach ($this->calendars as $calUri => $cal) {
251             $container = $this->findContainerForCalendar($calUri, $cal['displayname'], $defaultCalendarsName);
252             
253             $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
254             
255             $grants = $this->getCalendarGrants($calUri);
256             Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
257         }
258     }
259     
260     /**
261      * decide which calendar to use as default calendar
262      * if there is a remote default calendar, use that. If not, use the first we find
263      * 
264      * @return string
265      */
266     protected function _getDefaultCalendarsName() 
267     {
268         $defaultCalendarsName = '';
269         foreach ($this->calendarICSs as $calUri => $calICSs) {
270             if ($this->mapToDefaultContainer == $this->calendars[$calUri]['displayname']) {
271                 return $this->calendars[$calUri]['displayname'];
272             } elseif ($defaultCalendarsName === '') {
273                 $defaultCalendarsName = $this->calendars[$calUri]['displayname'];
274             }
275         }
276         return $defaultCalendarsName;
277     }
278     
279     /**
280      * 
281      * @param string $onlyCurrentUserOrganizer
282      * @return boolean
283      */
284     public function updateAllCalendarData($onlyCurrentUserOrganizer = false)
285     {
286         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs()) {
287             if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
288                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' no calendars found for: ' . $this->userName);
289             return false;
290         }
291         
292         $newICSs = array();
293         $newEventCount = 0;
294         $updateEventCount = 0;
295         $deleteEventCount = 0;
296         
297         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
298                 . ' Looking for updates in ' . count($this->calendarICSs). ' calendars ...');
299         
300         // @todo move to a member variable
301         $recordBackend = Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->getBackend();
302         
303         foreach ($this->calendarICSs as $calUri => $calICSs) {
304             $updateResult = $this->updateCalendar($calUri, $calICSs, $recordBackend);
305             if (count($updateResult['ics']) > 0) {
306                 $newICSs[$calUri] = $updateResult['ics'];
307             }
308             
309             if (! empty($updateResult['todelete'])) {
310                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' '
311                         . ' Deleting ' . count($updateResult['todelete']) . ' ' . $this->modelName . ' in calendar '  . $calUri);
312                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
313                         . ' ' . print_r($updateResult['todelete'], true));
314                 
315                 $recordBackend->delete($updateResult['todelete']);
316             }
317             
318             $newEventCount += $updateResult['toadd'];
319             $updateEventCount += $updateResult['toupdate'];
320             $deleteEventCount += count($updateResult['todelete']);
321         }
322         
323         if (($count = count($newICSs)) > 0) {
324             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' 
325                     . $count . ' calendar(s) changed for: ' . $this->userName
326                     . ' (' . $newEventCount . '/' . $updateEventCount . '/' . $deleteEventCount . ' records add/update/delete): '
327                     . print_r(array_keys($newICSs), true));
328             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' 
329                     . 'Events changed: ' . print_r($newICSs, true));
330             
331             $this->calendarICSs = $newICSs;
332             $this->importAllCalendarData($onlyCurrentUserOrganizer, /* $update = */ true);
333         } else {
334             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
335                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' no changes found for: ' . $this->userName);
336         }
337     }
338     
339     protected function updateCalendar($calUri, $calICSs, $recordBackend)
340     {
341         $updateResult = array(
342             'ics'       => array(),
343             'toupdate'  => 0,
344             'toadd'     => 0,
345             'todelete'  => array(), // of record ids
346         );
347         
348         $serverEtags = $this->_fetchServerEtags($calUri, $calICSs);
349         
350         // get current tine20 id/etags of records
351         $defaultCalendarsName = $this->_getDefaultCalendarsName();
352         $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['displayname'], $defaultCalendarsName);
353         $containerEtags = $recordBackend->getEtagsForContainerId($container->getId());
354         
355         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' '
356                 . ' Got ' . count($serverEtags) . ' server etags for container ' . $container->name);
357         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
358                 . ' server etags: ' . print_r($serverEtags, true));
359         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' '
360                 . ' tine20 etags: ' . print_r($containerEtags, true));
361         
362         // handle add/updates
363         foreach ($serverEtags as $ics => $data) {
364             if (isset($containerEtags[$data['id']])) {
365                 $tine20Etag = $containerEtags[$data['id']];
366         
367                 // remove from $containerEtags list to be able to tell deletes
368                 unset($containerEtags[$data['id']]);
369         
370                 if ($tine20Etag == $data['etag']) {
371                     continue; // same
372                 } else if (empty($tine20Etag)) {
373                     // event has been added in tine -> don't overwrite/delete
374                     continue;
375                 } else {
376                     $eventExists = true; // different
377                 }
378             } else {
379                 try {
380                     // might be a delegated event from another container/organizer
381                     $recordBackend->checkETag($data['id'], $data['etag']);
382                     continue; // ignore update here
383                 } catch (Tinebase_Exception_NotFound $tenf) {
384                     $eventExists = false; // new record;
385                 }
386             }
387         
388             if (! isset($this->existingRecordIds[$calUri])) {
389                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' '
390                         . ' Found changed event(s) for container ' . $container->name);
391         
392                 $this->existingRecordIds[$calUri] = array();
393             }
394             $updateResult['ics'][] = $ics;
395             if ($eventExists) {
396                 $this->existingRecordIds[$calUri][] = $data['id'];
397                 $updateResult['toupdate']++;
398             } else {
399                 $updateResult['toadd']++;
400             }
401         }
402         
403         // handle deletes
404         foreach ($containerEtags as $id => $etag) {
405             if (! empty($etag)) {
406                 // record has been deleted on server
407                 $updateResult['todelete'][] = $id;
408             } else {
409                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' '
410                         . ' Record has been added in tine: ' . $id);
411             }
412         }
413         
414         return $updateResult;
415     }
416     
417     protected function _fetchServerEtags($calUri, $calICSs)
418     {
419         $start = 0;
420         $max = count($calICSs);
421         
422         $etags = array();
423         do {
424             $requestEnd = '';
425             for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
426                 $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
427             }
428             $start = $i;
429             $requestEnd .= '</b:calendar-multiget>';
430             $result = $this->calDavRequest('REPORT', $calUri, self::getEventETagsRequest . $requestEnd, 1);
431         
432             foreach ($result as $key => $value) {
433                 if (isset($value['{DAV:}getetag'])) {
434                     $name = explode('/', $key);
435                     $name = end($name);
436                     $id = $this->_getEventIdFromName($name);
437                     $etags[$key] = array( 'id' => $id, 'etag' => $value['{DAV:}getetag']);
438                 }
439             }
440         } while($start < $max);
441
442         return $etags;
443     }
444     
445     protected function _getEventIdFromName($name)
446     {
447         $id = ($pos = strpos($name, '.')) === false ? $name : substr($name, 0, $pos);
448         if (strlen($id) > 40) {
449             $id = sha1($id);
450         }
451         return $id;
452     }
453     
454     public function importAllCalendarData($onlyCurrentUserOrganizer = false, $update = false)
455     {
456         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs()) {
457             return false;
458         }
459         
460         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
461             . ' Importing all calendar data for user ' . $this->userName . ' with ics uris: ' . print_r(array_keys($this->calendarICSs), true));
462         
463         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->sendNotifications(false);
464         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->useNotes(false);
465         Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
466         
467         $this->decorator->initCalendarImport();
468         
469         $application_id = Tinebase_Application::getInstance()->getApplicationByName($this->appName)->getId();
470         $type = Tinebase_Model_Container::TYPE_PERSONAL;
471         $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer($this->modelName);
472         
473         $defaultCalendarsName = $this->_getDefaultCalendarsName();
474         
475         foreach ($this->calendarICSs as $calUri => $calICSs) {
476             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
477                 . ' Processing calendar ' . print_r($this->calendars[$calUri], true));
478             
479             $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['displayname'], $defaultCalendarsName,
480                     $type, $application_id);
481             
482             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__
483                     . ' User container: ' . print_r($container->toArray(), true));
484             
485             $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
486             
487             // we shouldnt do the grants here as the caldav user file may not contain all users, so setting the grants wont work properly!
488             // use importAllCalendars to have the grants set
489             //$grants = $this->getCalendarGrants($calUri);
490             //Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
491             
492             $start = 0;
493             $max = count($calICSs);
494             do {
495                 $etags = array();
496                 $requestEnd = '';
497                 for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
498                     $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
499                 }
500                 $start = $i;
501                 $requestEnd .= '</b:calendar-multiget>';
502                 $result = $this->calDavRequest('REPORT', $calUri, self::getAllCalendarDataRequest . $requestEnd, 1);
503                 
504                 foreach ($result as $key => $value) {
505                     if (! isset($value['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
506                         continue;
507                     }
508                     
509                     $data = $value['{urn:ietf:params:xml:ns:caldav}calendar-data'];
510                     
511                     if (strpos($data, 'BEGIN:' . $this->skipComonent) !== false) {
512                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
513                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Skipping ' . $this->skipComonent);
514                         continue;
515                     }
516                     
517                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
518                             . ' Processing caldav record: ' . $key);
519                     
520                     $name = explode('/', $key);
521                     $name = end($name);
522                     $id = $this->_getEventIdFromName($name);
523                     try {
524                         if ($update && in_array($id, $this->existingRecordIds[$calUri])) {
525                             $webdavFrontend = new $this->webdavFrontend($container, $id);
526                             // @todo move this to separate fn
527                             if ($onlyCurrentUserOrganizer && $this->modelName === 'Calendar_Model_Event') {
528                                 // assert current user is organizer
529                                 if ($webdavFrontend->getRecord()->organizer && $webdavFrontend->getRecord()->organizer == Tinebase_Core::getUser()->contact_id) {
530                                     $webdavFrontend->put($data);
531                                 } else {
532                                     continue;
533                                 }
534                             } else {
535                                 $webdavFrontend->put($data);
536                             }
537                             
538                         } else {
539                             $webdavFrontend = call_user_func_array(array($this->webdavFrontend, 'create'), array(
540                                 $container,
541                                 $name,
542                                 $data,
543                                 $onlyCurrentUserOrganizer
544                             ));
545                         }
546                         
547                         if ($webdavFrontend) {
548                             $etags[$webdavFrontend->getRecord()->getId()] = $value['{DAV:}getetag'];
549                         }
550                     } catch (Exception $e) {
551                         if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
552                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not create event from data: ' . $data);
553                         Tinebase_Exception::log($e);
554                     }
555                 }
556                 
557                 $this->_setEtags($etags);
558             } while($start < $max);
559         }
560         return true;
561     }
562     
563     protected function _setEtags($etags)
564     {
565         $recordBackend = Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->getBackend();
566         $recordBackend->setETags($etags);
567     }
568     
569     /**
570      * get Tine 2.0 group for given principal (by display name)
571      * - result is cached for 1 week
572      * 
573      * @param string $principal
574      * @return null|Tinebase_Model_Group
575      */
576     protected function _getGroupForPrincipal($principal)
577     {
578         $cacheId = convertCacheId('_getGroupForPrincipal' . $principal);
579         if (Tinebase_Core::getCache()->test($cacheId)) {
580             $group = Tinebase_Core::getCache()->load($cacheId);
581             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
582                     . ' Loading principal group from cache: ' . $group->name);
583             return $group;
584         }
585         
586         $group = null;
587         
588         $result = $this->calDavRequest('PROPFIND', $principal, self::resolvePrincipalRequest);
589         if (count($result['{DAV:}group-member-set']->getPrincipals()) > 0 && isset($result['{DAV:}displayname'])) {
590             $groupDescription = $result['{DAV:}displayname'];
591             try {
592                 $group = Tinebase_Group::getInstance()->getGroupByPropertyFromSqlBackend('description',$groupDescription);
593                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
594                         . ' Found matching group ' . $group->name . ' (' . $group->description .') for principal ' . $principal);
595                 Tinebase_Core::getCache()->save($group, $cacheId, array(), /* 1 week */ 24*3600*7);
596             } catch (Tinebase_Exception_Record_NotDefined $ternd) {
597                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
598                         . ' Group not found: ' . $groupDescription . ' ' . print_r($result, true));
599             }
600         }
601         
602         return $group;
603     }
604     
605     /**
606      * get grants for cal uri
607      * 
608      * @param string $calUri
609      * @return Tinebase_Record_RecordSet
610      */
611     public function getCalendarGrants($calUri)
612     {
613         $grants = array();
614         $user = array();
615         $type = array();
616         $privilege = array();
617         foreach ($this->calendars[$calUri]['acl']->getPrivileges() as $ace)
618         {
619             if ('{DAV:}authenticated' == $ace['principal']) {
620                 $user[] = 0;
621                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
622                 $privilege[] = $ace['privilege'];
623             } elseif (isset($this->principals[$ace['principal']])) {
624                 $user[] = $this->principals[$ace['principal']]->getId();
625                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
626                 $privilege[] = $ace['privilege'];
627             } elseif (isset($this->principalGroups[$ace['principal']])) {
628                 foreach($this->principalGroups[$ace['principal']] as $principal) {
629                     if ('{DAV:}authenticated' == $principal) {
630                         $user[] = 0;
631                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
632                         $privilege[] = $ace['privilege'];
633                     } elseif (isset($this->principals[$principal])) {
634                         $user[] = $this->principals[$principal]->getId();
635                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
636                         $privilege[] = $ace['privilege'];
637                     } else {
638                         $group = $this->_getGroupForPrincipal($principal);
639                         if ($group) {
640                             $user[] = $group->getId();
641                             $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP;
642                             $privilege[] = $ace['privilege'];
643                         } else {
644                             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
645                                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
646                                     . ' There is an unresolved principal: ' . $principal . ' in group: ' . $ace['principal']);
647                         }
648                     }
649                 }
650             } else {
651                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
652                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Couldn\'t resolve principal: '.$ace['principal']);
653             }
654         }
655         for ($i=0; $i<count($user); ++$i) {
656             switch ($privilege[$i]) {
657                 case '{DAV:}all':
658                     $grants[$user[$i]] = $this->_getAllGrants();
659                     break;
660                 case '{urn:ietf:params:xml:ns:caldav}read-free-busy':
661                     $grants[$user[$i]] = $this->_getFreeBusyGrants();
662                     break;
663                 case '{DAV:}read':
664                     $grants[$user[$i]] = $this->_getReadGrants();
665                     break;
666                 case '{DAV:}write':
667                     $grants[$user[$i]] = $this->_getWriteGrants();
668                     break;
669                 case '{DAV:}read-current-user-privilege-set':
670                     continue;
671                 default:
672                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
673                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' unknown privilege: ' . $privilege[$i]);
674                     continue;
675             }
676             $grants[$user[$i]]['account_id'] = $user[$i];
677             $grants[$user[$i]]['account_type'] = $type[$i];
678         }
679         
680         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
681             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' found ' . count($grants) . ' grants for calendar: ' . $calUri);
682         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
683             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' grants: ' . print_r($grants, true));
684         
685         return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
686     }
687     
688     protected function _getAllGrants()
689     {
690         return array(
691             Tinebase_Model_Grants::GRANT_READ => true,
692             Tinebase_Model_Grants::GRANT_ADD=> true,
693             Tinebase_Model_Grants::GRANT_EDIT=> true,
694             Tinebase_Model_Grants::GRANT_DELETE=> true,
695             Tinebase_Model_Grants::GRANT_EXPORT=> true,
696             Tinebase_Model_Grants::GRANT_SYNC=> true,
697             Tinebase_Model_Grants::GRANT_ADMIN=> true,
698             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
699             Tinebase_Model_Grants::GRANT_PRIVATE=> true,
700         );
701     }
702
703     protected function _getFreeBusyGrants()
704     {
705         return array(
706             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
707         );
708     }
709
710     protected function _getReadGrants()
711     {
712         return array(
713             Tinebase_Model_Grants::GRANT_READ=> true,
714             Tinebase_Model_Grants::GRANT_EXPORT=> true,
715             Tinebase_Model_Grants::GRANT_SYNC=> true,
716             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
717         );
718     }
719
720     protected function _getWriteGrants()
721     {
722         return array(
723             Tinebase_Model_Grants::GRANT_READ=> true,
724             Tinebase_Model_Grants::GRANT_ADD=> true,
725             Tinebase_Model_Grants::GRANT_EDIT=> true,
726             Tinebase_Model_Grants::GRANT_DELETE=> true,
727         );
728     }
729     
730     public function updateAllCalendarDataForUsers(array $users)
731     {
732         $result = true;
733         // first only update/import events where the current user is also the organizer
734         foreach ($users as $username => $pwd) {
735             $this->clearCurrentUserCalendarData();
736             $this->userName = $username;
737             $this->password = $pwd;
738             if (!$this->updateAllCalendarData(true)) {
739                 $result = false;
740             }
741         }
742         // then update all events again
743         foreach ($users as $username => $pwd) {
744             $this->clearCurrentUserCalendarData();
745             $this->userName = $username;
746             $this->password = $pwd;
747             if (!$this->updateAllCalendarData(false)) {
748                 $result = false;
749             }
750         }
751         return $result;
752     }
753     
754     public function importAllCalendarDataForUsers(array $users)
755     {
756         $result = true;
757         // first only import events where the current user is also the organizer
758         foreach ($users as $username => $pwd) {
759             $this->clearCurrentUserCalendarData();
760             $this->userName = $username;
761             $this->password = $pwd;
762             if (!$this->importAllCalendarData(true)) {
763                 $result = false;
764             }
765         }
766         // then import all events again
767         foreach ($users as $username => $pwd) {
768             $this->clearCurrentUserCalendarData();
769             $this->userName = $username;
770             $this->password = $pwd;
771             if (!$this->importAllCalendarData(false)) {
772                 $result = false;
773             }
774         }
775         return $result;
776     }
777     
778     public function importAllCalendarsForUsers(array $users)
779     {
780         if (!$this->findCurrentUserPrincipalForUsers($users)) {
781             return false;
782         }
783         
784         $result = true;
785         foreach ($users as $username => $pwd) {
786             $this->clearCurrentUserCalendarData();
787             $this->userName = $username;
788             $this->password = $pwd;
789             if (!$this->importAllCalendars()) {
790                 $result = false;
791             }
792         }
793         return $result;
794     }
795     
796     public function clearCurrentUserCalendarData()
797     {
798         $this->clearCurrentUserData();
799         $this->calendars = array();
800         $this->calendarICSs = array();
801     }
802 }