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