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