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