catch errors when trying to get "other app etags"
[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         try {
483             $filter = ($this->appName === 'Calendar') 
484                 ? new Tasks_Model_TaskFilter(array($containerFilterArray))
485                 : new Calendar_Model_EventFilter(array($containerFilterArray));
486             $ids = $otherBackend->search($filter, null, true);
487         } catch (Exception $e) {
488             Tinebase_Exception::log($e);
489             return array();
490         }
491         return $ids;
492     }
493     
494     protected function _fetchServerEtags($calUri, $calICSs)
495     {
496         $start = 0;
497         $max = count($calICSs);
498         
499         $etags = array();
500         do {
501             $requestEnd = '';
502             for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
503                 $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
504             }
505             $start = $i;
506             $requestEnd .= '</b:calendar-multiget>';
507             $result = $this->calDavRequest('REPORT', $calUri, self::getEventETagsRequest . $requestEnd, 1);
508         
509             foreach ($result as $key => $value) {
510                 if (isset($value['{DAV:}getetag'])) {
511                     $name = explode('/', $key);
512                     $name = end($name);
513                     $id = $this->_getEventIdFromName($name);
514                     $etags[$key] = array( 'id' => $id, 'etag' => $value['{DAV:}getetag']);
515                 }
516             }
517         } while($start < $max);
518
519         return $etags;
520     }
521     
522     protected function _getEventIdFromName($name)
523     {
524         $id = ($pos = strpos($name, '.')) === false ? $name : substr($name, 0, $pos);
525         if (strlen($id) > 40) {
526             $id = sha1($id);
527         }
528         return $id;
529     }
530     
531     public function importAllCalendarData($onlyCurrentUserOrganizer = false, $update = false)
532     {
533         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs()) {
534             return false;
535         }
536         
537         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
538             . ' Importing all calendar data for user ' . $this->userName . ' with ics uris: ' . print_r(array_keys($this->calendarICSs), true));
539         
540         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->sendNotifications(false);
541         Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->useNotes(false);
542         Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
543         
544         $this->decorator->initCalendarImport();
545         
546         $application_id = Tinebase_Application::getInstance()->getApplicationByName($this->appName)->getId();
547         $type = Tinebase_Model_Container::TYPE_PERSONAL;
548         $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer($this->modelName);
549         
550         $defaultCalendarsName = $this->_getDefaultCalendarsName();
551         
552         foreach ($this->calendarICSs as $calUri => $calICSs) {
553             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
554                 . ' Processing calendar ' . print_r($this->calendars[$calUri], true));
555             
556             $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['displayname'], $defaultCalendarsName,
557                     $type, $application_id);
558             
559             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . ' ' . __LINE__
560                     . ' User container: ' . print_r($container->toArray(), true));
561             
562             $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
563             
564             // we shouldnt do the grants here as the caldav user file may not contain all users, so setting the grants wont work properly!
565             // use importAllCalendars to have the grants set
566             //$grants = $this->getCalendarGrants($calUri);
567             //Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
568
569             $start = 0;
570             $max = count($calICSs);
571             do {
572                 $etags = array();
573                 $requestEnd = '';
574                 for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
575                     if (in_array($calICSs[$i], $this->_icsBlacklist)) {
576                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
577                                . ' Ignoring blacklisted ics: ' . $calICSs[$i]);
578                         continue;
579                     }
580                     $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
581                 }
582                 $start = $i;
583                 $requestEnd .= '</b:calendar-multiget>';
584                 $result = $this->calDavRequest('REPORT', $calUri, self::getAllCalendarDataRequest . $requestEnd, 1);
585                 
586                 foreach ($result as $key => $value) {
587                     if (! isset($value['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
588                         continue;
589                     }
590                     
591                     $data = $value['{urn:ietf:params:xml:ns:caldav}calendar-data'];
592                     
593                     if (strpos($data, 'BEGIN:' . $this->skipComonent) !== false) {
594                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
595                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Skipping ' . $this->skipComonent);
596                         continue;
597                     }
598                     
599                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
600                             . ' Processing caldav record: ' . $key);
601                     
602                     $name = explode('/', $key);
603                     $name = end($name);
604                     $id = $this->_getEventIdFromName($name);
605                     try {
606                         if ($update && in_array($id, $this->existingRecordIds[$calUri])) {
607                             $webdavFrontend = new $this->webdavFrontend($container, $id);
608                             // @todo move this to separate fn
609                             if ($onlyCurrentUserOrganizer && $this->modelName === 'Calendar_Model_Event') {
610                                 // assert current user is organizer
611                                 if ($webdavFrontend->getRecord()->organizer && $webdavFrontend->getRecord()->organizer == Tinebase_Core::getUser()->contact_id) {
612                                     $webdavFrontend->put($data);
613                                 } else {
614                                     continue;
615                                 }
616                             } else {
617                                 $webdavFrontend->put($data);
618                             }
619                             
620                         } else {
621                             $webdavFrontend = call_user_func_array(array($this->webdavFrontend, 'create'), array(
622                                 $container,
623                                 $name,
624                                 $data,
625                                 $onlyCurrentUserOrganizer
626                             ));
627                         }
628                         
629                         if ($webdavFrontend) {
630                             $etags[$webdavFrontend->getRecord()->getId()] = $value['{DAV:}getetag'];
631                         }
632                     } catch (Exception $e) {
633                         if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
634                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not create event from data: ' . $data);
635                         Tinebase_Exception::log($e, /* $suppressTrace = */ false);
636                     }
637                 }
638                 
639                 $this->_setEtags($etags);
640             } while($start < $max);
641         }
642         return true;
643     }
644     
645     protected function _setEtags($etags)
646     {
647         $recordBackend = Tinebase_Core::getApplicationInstance($this->appName, $this->modelName)->getBackend();
648         $recordBackend->setETags($etags);
649     }
650     
651     /**
652      * get Tine 2.0 group for given principal (by display name)
653      * - result is cached for 1 week
654      * 
655      * @param string $principal
656      * @return null|Tinebase_Model_Group
657      */
658     protected function _getGroupForPrincipal($principal)
659     {
660         $cacheId = convertCacheId('_getGroupForPrincipal' . $principal);
661         if (Tinebase_Core::getCache()->test($cacheId)) {
662             $group = Tinebase_Core::getCache()->load($cacheId);
663             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
664                     . ' Loading principal group from cache: ' . $group->name);
665             return $group;
666         }
667         
668         $group = null;
669         
670         $result = $this->calDavRequest('PROPFIND', $principal, self::resolvePrincipalRequest);
671         if (count($result['{DAV:}group-member-set']->getPrincipals()) > 0 && isset($result['{DAV:}displayname'])) {
672             $groupDescription = $result['{DAV:}displayname'];
673             try {
674                 $group = Tinebase_Group::getInstance()->getGroupByPropertyFromSqlBackend('description',$groupDescription);
675                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . ' ' . __LINE__
676                         . ' Found matching group ' . $group->name . ' (' . $group->description .') for principal ' . $principal);
677                 Tinebase_Core::getCache()->save($group, $cacheId, array(), /* 1 week */ 24*3600*7);
678             } catch (Tinebase_Exception_Record_NotDefined $ternd) {
679                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . ' ' . __LINE__
680                         . ' Group not found: ' . $groupDescription . ' ' . print_r($result, true));
681             }
682         }
683         
684         return $group;
685     }
686     
687     /**
688      * get grants for cal uri
689      * 
690      * @param string $calUri
691      * @return Tinebase_Record_RecordSet
692      */
693     public function getCalendarGrants($calUri)
694     {
695         $grants = array();
696         $user = array();
697         $type = array();
698         $privilege = array();
699         foreach ($this->calendars[$calUri]['acl']->getPrivileges() as $ace)
700         {
701             if ('{DAV:}authenticated' == $ace['principal']) {
702                 $user[] = 0;
703                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
704                 $privilege[] = $ace['privilege'];
705             } elseif (isset($this->principals[$ace['principal']])) {
706                 $user[] = $this->principals[$ace['principal']]->getId();
707                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
708                 $privilege[] = $ace['privilege'];
709             } elseif (isset($this->principalGroups[$ace['principal']])) {
710                 foreach($this->principalGroups[$ace['principal']] as $principal) {
711                     if ('{DAV:}authenticated' == $principal) {
712                         $user[] = 0;
713                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
714                         $privilege[] = $ace['privilege'];
715                     } elseif (isset($this->principals[$principal])) {
716                         $user[] = $this->principals[$principal]->getId();
717                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
718                         $privilege[] = $ace['privilege'];
719                     } else {
720                         $group = $this->_getGroupForPrincipal($principal);
721                         if ($group) {
722                             $user[] = $group->getId();
723                             $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_GROUP;
724                             $privilege[] = $ace['privilege'];
725                         } else {
726                             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
727                                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
728                                     . ' There is an unresolved principal: ' . $principal . ' in group: ' . $ace['principal']);
729                         }
730                     }
731                 }
732             } else {
733                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
734                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Couldn\'t resolve principal: '.$ace['principal']);
735             }
736         }
737         for ($i=0; $i<count($user); ++$i) {
738             switch ($privilege[$i]) {
739                 case '{DAV:}all':
740                     $grants[$user[$i]] = $this->_getAllGrants();
741                     break;
742                 case '{urn:ietf:params:xml:ns:caldav}read-free-busy':
743                     $grants[$user[$i]] = $this->_getFreeBusyGrants();
744                     break;
745                 case '{DAV:}read':
746                     $grants[$user[$i]] = $this->_getReadGrants();
747                     break;
748                 case '{DAV:}write':
749                     $grants[$user[$i]] = $this->_getWriteGrants();
750                     break;
751                 case '{DAV:}read-current-user-privilege-set':
752                     continue;
753                 default:
754                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
755                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' unknown privilege: ' . $privilege[$i]);
756                     continue;
757             }
758             $grants[$user[$i]]['account_id'] = $user[$i];
759             $grants[$user[$i]]['account_type'] = $type[$i];
760         }
761         
762         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
763             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' found ' . count($grants) . ' grants for calendar: ' . $calUri);
764         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
765             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' grants: ' . print_r($grants, true));
766         
767         return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
768     }
769     
770     protected function _getAllGrants()
771     {
772         return array(
773             Tinebase_Model_Grants::GRANT_READ => true,
774             Tinebase_Model_Grants::GRANT_ADD=> true,
775             Tinebase_Model_Grants::GRANT_EDIT=> true,
776             Tinebase_Model_Grants::GRANT_DELETE=> true,
777             Tinebase_Model_Grants::GRANT_EXPORT=> true,
778             Tinebase_Model_Grants::GRANT_SYNC=> true,
779             Tinebase_Model_Grants::GRANT_ADMIN=> true,
780             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
781             Tinebase_Model_Grants::GRANT_PRIVATE=> true,
782         );
783     }
784
785     protected function _getFreeBusyGrants()
786     {
787         return array(
788             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
789         );
790     }
791
792     protected function _getReadGrants()
793     {
794         return array(
795             Tinebase_Model_Grants::GRANT_READ=> true,
796             Tinebase_Model_Grants::GRANT_EXPORT=> true,
797             Tinebase_Model_Grants::GRANT_SYNC=> true,
798             Tinebase_Model_Grants::GRANT_FREEBUSY=> true,
799         );
800     }
801
802     protected function _getWriteGrants()
803     {
804         return array(
805             Tinebase_Model_Grants::GRANT_READ=> true,
806             Tinebase_Model_Grants::GRANT_ADD=> true,
807             Tinebase_Model_Grants::GRANT_EDIT=> true,
808             Tinebase_Model_Grants::GRANT_SYNC=> true,
809             Tinebase_Model_Grants::GRANT_DELETE=> true,
810         );
811     }
812     
813     public function updateAllCalendarDataForUsers(array $users)
814     {
815         $result = true;
816         // first only update/import events where the current user is also the organizer
817         foreach ($users as $username => $pwd) {
818             $this->clearCurrentUserCalendarData();
819             $this->userName = $username;
820             $this->password = $pwd;
821             if (!$this->updateAllCalendarData(true)) {
822                 $result = false;
823             }
824         }
825         // then update all events again
826         foreach ($users as $username => $pwd) {
827             $this->clearCurrentUserCalendarData();
828             $this->userName = $username;
829             $this->password = $pwd;
830             if (!$this->updateAllCalendarData(false)) {
831                 $result = false;
832             }
833         }
834         return $result;
835     }
836     
837     public function importAllCalendarDataForUsers(array $users)
838     {
839         $result = true;
840         // first only import events where the current user is also the organizer
841         foreach ($users as $username => $pwd) {
842             $this->clearCurrentUserCalendarData();
843             $this->userName = $username;
844             $this->password = $pwd;
845             if (!$this->importAllCalendarData(true)) {
846                 $result = false;
847             }
848         }
849         // then import all events again
850         foreach ($users as $username => $pwd) {
851             $this->clearCurrentUserCalendarData();
852             $this->userName = $username;
853             $this->password = $pwd;
854             if (!$this->importAllCalendarData(false)) {
855                 $result = false;
856             }
857         }
858         return $result;
859     }
860     
861     public function importAllCalendarsForUsers(array $users)
862     {
863         if (!$this->findCurrentUserPrincipalForUsers($users)) {
864             return false;
865         }
866         
867         $result = true;
868         foreach ($users as $username => $pwd) {
869             $this->clearCurrentUserCalendarData();
870             $this->userName = $username;
871             $this->password = $pwd;
872             if (!$this->importAllCalendars()) {
873                 $result = false;
874             }
875         }
876         return $result;
877     }
878     
879     public function clearCurrentUserCalendarData()
880     {
881         $this->clearCurrentUserData();
882         $this->calendars = array();
883         $this->calendarICSs = array();
884     }
885 }