79ecd312434424f15f591b7a74c15d000020ff35
[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 /**
6  * Tine 2.0
7  * 
8  * @package     Calendar
9  * @subpackage  Import
10  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
11  * @author      Paul Mehrer <p.mehrer@metaways.de>
12  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
13  * 
14  * @todo        
15  */
16
17 /**
18  * Calendar_Import_CalDAV
19  * 
20  * @package     Calendar
21  * @subpackage  Import
22  * 
23  */
24 class Calendar_Import_CalDav_Client extends Tinebase_Import_CalDav_Client
25 {
26     protected $calendars = array();
27     protected $calendarICSs = array();
28     protected $maxBulkRequest = 20;
29     protected $mapToDefaultContainer = 'calendar';
30     protected $decorator = null;
31     
32     const findAllCalendarsRequest =
33 '<?xml version="1.0"?>
34 <d:propfind xmlns:d="DAV:">
35   <d:prop>
36     <d:resourcetype />
37     <d:acl />
38     <d:displayname />
39     <x:supported-calendar-component-set xmlns:x="urn:ietf:params:xml:ns:caldav"/>
40   </d:prop>
41 </d:propfind>';
42     
43     const findAllCalendarICSsRequest = 
44 '<?xml version="1.0"?>
45 <d:propfind xmlns:d="DAV:">
46   <d:prop>
47     <x:calendar-data xmlns:x="urn:ietf:params:xml:ns:caldav"/>
48   </d:prop>
49 </d:propfind>';
50     
51     const getAllCalendarDataRequest =
52 '<?xml version="1.0"?>
53 <b:calendar-multiget xmlns:a="DAV:" xmlns:b="urn:ietf:params:xml:ns:caldav">
54   <a:prop>
55     <b:calendar-data />
56   </a:prop>
57 ';
58     
59     public function __construct(array $a, $flavor)
60     {
61         parent::__construct($a);
62         
63         $flavor = 'Calendar_Import_CalDav_Decorator_'.$flavor;
64         $this->decorator = new $flavor($this);
65     }
66     
67     public function findAllCalendars()
68     {
69         if ('' == $this->calendarHomeSet && ! $this->findCalendarHomeSet())
70             return false;
71         
72         //issue with follow location in curl!?!?
73         if ($this->calendarHomeSet[strlen($this->calendarHomeSet)-1] !== '/')
74             $this->calendarHomeSet .= '/';
75         
76         $result = $this->calDavRequest('PROPFIND', $this->calendarHomeSet, $this->decorator->preparefindAllCalendarsRequest(self::findAllCalendarsRequest), 1);
77         
78         foreach ($result as $uri => $response) {
79             if (isset($response['{DAV:}resourcetype']) && isset($response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']) && 
80                     $response['{DAV:}resourcetype']->is('{urn:ietf:params:xml:ns:caldav}calendar') &&
81                     in_array('VEVENT', $response['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']->getValue())) {
82                 $this->calendars[$uri]['acl'] = $response['{DAV:}acl'];
83                 $this->calendars[$uri]['displayname'] = $response['{DAV:}displayname'];
84                 $this->decorator->processAdditionalCalendarProperties($this->calendars[$uri], $response);
85                 $this->resolvePrincipals($this->calendars[$uri]['acl']->getPrivileges());
86             }
87         }
88         
89         if (count($this->calendars) > 0) {
90             return true;
91         } else {
92             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
93                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t find a calendar');
94             return false;
95         }
96     }
97     
98     public function findAllCalendarICSs()
99     {
100         if (count($this->calendars) < 1 && ! $this->findAllCalendars())
101             return false;
102         
103         foreach ($this->calendars as $calUri => $calendar) {
104             $result = $this->calDavRequest('PROPFIND', $calUri, self::findAllCalendarICSsRequest, 1);
105             foreach ($result as $ics => $value) {
106                 if (strpos($ics, '.ics') !== FALSE)
107                     $this->calendarICSs[$calUri][] = $ics;
108             }
109         }
110         
111         if (count($this->calendarICSs) > 0) {
112             return true;
113         } else {
114             if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
115                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' all found calendars are empty');
116             return false;
117         }
118     }
119     
120     protected function findContainerForCalendar($calendarUri, $displayname, $defaultCalendarsName, $type, $application_id, $modelName)
121     {
122         $uuid = basename($calendarUri);
123         
124         $filter = new Tinebase_Model_ContainerFilter(array(
125             array(
126                 'field' => 'uuid', 
127                 'operator' => 'equals', 
128                 'value' => $uuid
129             ),
130             array(
131                 'field' => 'model', 
132                 'operator' => 'equals', 
133                 'value' => 'Calendar_Model_Event'
134             ),
135         ));
136         $existingCalendar = Tinebase_Container::getInstance()->search($filter, null, false, false, 'sync')->getFirstRecord();
137         if($existingCalendar) {
138             return $existingCalendar;
139         }
140         
141         $counter = '';
142         
143         if ($defaultCalendarsName == $displayname) {
144             $existingCalendar = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
145             if (! $existingCalendar->uuid) {
146                 $existingCalendar->uuid = $uuid;
147                 return $existingCalendar;
148             }
149             $existingCalendar = null;
150             $counter = 1;
151         }
152         
153         try {
154             while (true) {
155                 $existingCalendar = Tinebase_Container::getInstance()->getContainerByName('Calendar', $displayname . $counter, $type, Tinebase_Core::getUser());
156                 if (! $existingCalendar->uuid) {
157                     $existingCalendar->uuid = $uuid;
158                     return $existingCalendar;
159                 }
160                 $counter += 1;
161             }
162         } catch (Tinebase_Exception_NotFound $e) {
163             $newContainer = new Tinebase_Model_Container(array(
164                 'name'              => $displayname . $counter,
165                 'type'              => $type,
166                 'backend'           => 'Sql',
167                 'application_id'    => $application_id,
168                 'model'             => $modelName,
169                 'uuid'              => $uuid
170             ));
171             return Tinebase_Container::getInstance()->addContainer($newContainer);
172         }
173     }
174     
175     public function importAllCalendars()
176     {
177         if (count($this->calendars) < 1 && ! $this->findAllCalendars())
178             return false;
179         
180         Calendar_Controller_Event::getInstance()->sendNotifications(false);
181         Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
182         
183         $this->decorator->initCalendarImport();
184         
185         $modelName = Tinebase_Core::getApplicationInstance('Calendar')->getDefaultModel();
186         $application_id = Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId();
187         $type = Tinebase_Model_Container::TYPE_PERSONAL; //Tinebase_Model_Container::TYPE_SHARED;
188         $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
189         
190         //decide which calendar to use as default calendar
191         //if there is a remote default calendar, use that. If not, use the first we find
192         $defaultCalendarsName = '';
193         foreach ($this->calendarICSs as $calUri => $calICSs) {
194             if ($this->mapToDefaultContainer == $this->calendars[$calUri]['displayname']) {
195                 $container = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
196             } elseif ($defaultsCalendarsName === '') {
197                 $defaultCalendarsName = $this->calendars[$calUri]['displayname'];
198             }
199         }
200         
201         foreach ($this->calendars as $calUri => $cal) {
202             $container = $this->findContainerForCalendar($calUri, $cal['displayname'], $defaultCalendarsName,
203                     $type, $application_id, $modelName);
204             
205             $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
206             
207             $grants = $this->getCalendarGrants($calUri);
208             Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
209         }
210     }
211     
212     public function importAllCalendarData($onlyCurrentUserOrganizer = false)
213     {
214         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs())
215             return false;
216         
217         Calendar_Controller_Event::getInstance()->sendNotifications(false);
218         Sabre\VObject\Component\VCalendar::$propertyMap['ATTACH'] = '\\Calendar_Import_CalDav_SabreAttachProperty';
219         
220         $this->decorator->initCalendarImport();
221         
222         $modelName = Tinebase_Core::getApplicationInstance('Calendar')->getDefaultModel();
223         $application_id = Tinebase_Application::getInstance()->getApplicationByName('Calendar')->getId();
224         $type = Tinebase_Model_Container::TYPE_PERSONAL; //Tinebase_Model_Container::TYPE_SHARED;
225         $defaultContainer = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
226         
227         //decide which calendar to use as default calendar
228         //if there is a remote default calendar, use that. If not, use the first we find
229         $defaultCalendarsName = '';
230         foreach ($this->calendarICSs as $calUri => $calICSs) {
231             if ($this->mapToDefaultContainer == $this->calendars[$calUri]['displayname']) {
232                 $container = Tinebase_Container::getInstance()->getDefaultContainer('Calendar_Model_Event');
233             } elseif ($defaultsCalendarsName === '') {
234                 $defaultCalendarsName = $this->calendars[$calUri]['displayname'];
235             }
236         }
237         
238         foreach ($this->calendarICSs as $calUri => $calICSs) {
239             $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['displayname'], $defaultCalendarsName,
240                     $type, $application_id, $modelName);
241             
242             $this->decorator->setCalendarProperties($container, $this->calendars[$calUri]);
243             
244             // we shouldnt do the grants here as the caldav user file may not contain all users, so setting the grants wont work properly!
245             // use importAllCalendars to have the grants set
246             //$grants = $this->getCalendarGrants($calUri);
247             //Tinebase_Container::getInstance()->setGrants($container->getId(), $grants, TRUE, FALSE);
248             
249             $start = 0;
250             $max = count($calICSs);
251             do {
252                 $requestEnd = '';
253                 for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
254                     $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
255                 }
256                 $start = $i;
257                 $requestEnd .= '</b:calendar-multiget>';
258                 $result = $this->calDavRequest('REPORT', $calUri, self::getAllCalendarDataRequest . $requestEnd, 1);
259                 
260                 foreach ($result as $key => $value) {
261                     if (isset($value['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
262                         $data = $value['{urn:ietf:params:xml:ns:caldav}calendar-data'];
263                         $name = explode('/', $key);
264                         $name = end($name);
265                         try {
266                             Calendar_Frontend_WebDAV_Event::create(
267                                 $container,
268                                 $name,
269                                 $data,
270                                 $onlyCurrentUserOrganizer
271                             );
272                         } catch (Exception $e) {
273                             // don't warn on VTODOs
274                             if (strpos($data, 'BEGIN:VTODO') !== false) {
275                                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
276                                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Skipping VTODO');
277                             } else {
278                                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
279                                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Could not create event from data: '
280                                             . $data);
281                                 Tinebase_Exception::log($e);
282                             }
283                         }
284                     }
285                 }
286             } while ($start < $max);
287         }
288         return true;
289     }
290     
291     public function getCalendarGrants($calUri)
292     {
293         $grants = array();
294         $user = array();
295         $type = array();
296         $privilege = array();
297         foreach ($this->calendars[$calUri]['acl']->getPrivileges() as $ace)
298         {
299             if ('{DAV:}authenticated' == $ace['principal']) {
300                 $user[] = 0;
301                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
302                 $privilege[] = $ace['privilege'];
303             } elseif (isset($this->principals[$ace['principal']])) {
304                 $user[] = $this->principals[$ace['principal']]->getId();
305                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
306                 $privilege[] = $ace['privilege'];
307             } elseif (isset($this->principalGroups[$ace['principal']])) {
308                 foreach($this->principalGroups[$ace['principal']] as $principal) {
309                     if ('{DAV:}authenticated' == $principal) {
310                         $user[] = 0;
311                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
312                         $privilege[] = $ace['privilege'];
313                     } elseif (isset($this->principals[$principal])) {
314                         $user[] = $this->principals[$principal]->getId();
315                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
316                         $privilege[] = $ace['privilege'];
317                     } else {
318                         if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
319                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' there is an unresolved principal: ' . $principal . ' in group: ' . $ace['principal']);
320                     }
321                 }
322             } else {
323                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
324                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t resolve principal: '.$ace['principal']);
325             }
326         }
327         for ($i=0; $i<count($user); ++$i) {
328             switch ($privilege[$i]) {
329                 case '{DAV:}all':
330                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ ] = true;
331                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
332                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
333                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
334                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
335                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
336                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADMIN] = true;
337                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
338                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_PRIVATE] = true;
339                     break;
340                 case '{urn:ietf:params:xml:ns:caldav}read-free-busy':
341                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
342                     break;
343                 case '{DAV:}read':
344                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ] = true;
345                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
346                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
347                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
348                     break;
349                 case '{DAV:}write':
350                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
351                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
352                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
353                     break;
354                 case '{DAV:}read-current-user-privilege-set':
355                     continue;
356                 default:
357                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
358                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' unknown privilege: ' . $privilege[$i]);
359                     continue;
360             }
361             $grants[$user[$i]]['account_id'] = $user[$i];
362             $grants[$user[$i]]['account_type'] = $type[$i];
363         }
364         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
365             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' found grants: ' . print_r($grants, true) . ' for calendar: ' . $calUri);
366         
367         return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
368     }
369     
370     public function importAllCalendarDataForUsers(array $users)
371     {
372         if (!$this->findCurrentUserPrincipalForUsers($users))
373             return false;
374         
375         $result = true;
376         // first only import events where the current user is also the organizer
377         foreach ($users as $username => $pwd) {
378             $this->clearCurrentUserCalendarData();
379             $this->userName = $username;
380             $this->password = $pwd;
381             if (!$this->importAllCalendarData(true)) {
382                 $result = false;
383             }
384         }
385         // then import all events again
386         foreach ($users as $username => $pwd) {
387             $this->clearCurrentUserCalendarData();
388             $this->userName = $username;
389             $this->password = $pwd;
390             if (!$this->importAllCalendarData(false)) {
391                 $result = false;
392             }
393         }
394         return $result;
395     }
396     
397     public function importAllCalendarsForUsers(array $users)
398     {
399         if (!$this->findCurrentUserPrincipalForUsers($users))
400             return false;
401         
402         $result = true;
403         foreach ($users as $username => $pwd) {
404             $this->clearCurrentUserCalendarData();
405             $this->userName = $username;
406             $this->password = $pwd;
407             if (!$this->importAllCalendars()) {
408                 $result = false;
409             }
410         }
411         return $result;
412     }
413     
414     public function clearCurrentUserCalendarData()
415     {
416         $this->clearCurrentUserData();
417         $this->calendars = array();
418         $this->calendarICSs = array();
419     }
420 }