5ff171d56d5bcf5b3e6fa808306261810db800ae
[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 importAllCalendarData($onlyCurrentUserOrganizer = false)
176     {
177         if (count($this->calendarICSs) < 1 && ! $this->findAllCalendarICSs())
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->calendarICSs as $calUri => $calICSs) {
202             $container = $this->findContainerForCalendar($calUri, $this->calendars[$calUri]['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             $start = 0;
211             $max = count($calICSs);
212             do {
213                 $requestEnd = '';
214                 for ($i = $start; $i < $max && $i < ($this->maxBulkRequest+$start); ++$i) {
215                     $requestEnd .= '  <a:href>' . $calICSs[$i] . "</a:href>\n";
216                 }
217                 $start = $i;
218                 $requestEnd .= '</b:calendar-multiget>';
219                 $result = $this->calDavRequest('REPORT', $calUri, self::getAllCalendarDataRequest . $requestEnd, 1);
220                 
221                 foreach ($result as $key => $value) {
222                     if (isset($value['{urn:ietf:params:xml:ns:caldav}calendar-data'])) {
223                         $name = explode('/', $key);
224                         $name = end($name);
225                         try {
226                             Calendar_Frontend_WebDAV_Event::create(
227                                 $container,
228                                 $name,
229                                 $value['{urn:ietf:params:xml:ns:caldav}calendar-data'],
230                                 $onlyCurrentUserOrganizer
231                             );
232                         } catch(Tinebase_Exception_UnexpectedValue $e) {
233                             if ('no vevents found' != $e->getMessage()) {
234                                 throw $e;
235                             }
236                         }
237                     }
238                 }
239             } while($start < $max);
240         }
241         return true;
242     }
243     
244     public function getCalendarGrants($calUri)
245     {
246         $grants = array();
247         $user = array();
248         $type = array();
249         $privilege = array();
250         foreach ($this->calendars[$calUri]['acl']->getPrivileges() as $ace)
251         {
252             if ('{DAV:}authenticated' == $ace['principal']) {
253                 $user[] = 0;
254                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
255                 $privilege[] = $ace['privilege'];
256             } elseif (isset($this->principals[$ace['principal']])) {
257                 $user[] = $this->principals[$ace['principal']]->getId();
258                 $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
259                 $privilege[] = $ace['privilege'];
260             } elseif (isset($this->principalGroups[$ace['principal']])) {
261                 foreach($this->principalGroups[$ace['principal']] as $principal) {
262                     if ('{DAV:}authenticated' == $principal) {
263                         $user[] = 0;
264                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_ANYONE;
265                         $privilege[] = $ace['privilege'];
266                     } elseif (isset($this->principals[$principal])) {
267                         $user[] = $this->principals[$principal]->getId();
268                         $type[] = Tinebase_Acl_Rights::ACCOUNT_TYPE_USER;
269                         $privilege[] = $ace['privilege'];
270                     } else {
271                         if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
272                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' there is an unresolved principal: ' . $principal . ' in group: ' . $ace['principal']);
273                     }
274                 }
275             } else {
276                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
277                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' couldn\'t resolve principal: '.$ace['principal']);
278             }
279         }
280         for ($i=0; $i<count($user); ++$i) {
281             switch ($privilege[$i]) {
282                 case '{DAV:}all':
283                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ ] = true;
284                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
285                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
286                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
287                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
288                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
289                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADMIN] = true;
290                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
291                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_PRIVATE] = true;
292                     break;
293                 case '{urn:ietf:params:xml:ns:caldav}read-free-busy':
294                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
295                     break;
296                 case '{DAV:}read':
297                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_READ] = true;
298                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EXPORT] = true;
299                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_SYNC] = true;
300                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_FREEBUSY] = true;
301                     break;
302                 case '{DAV:}write':
303                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_ADD] = true;
304                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_EDIT] = true;
305                     $grants[$user[$i]][Tinebase_Model_Grants::GRANT_DELETE] = true;
306                     break;
307                 case '{DAV:}read-current-user-privilege-set':
308                     continue;
309                 default:
310                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN))
311                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' unknown privilege: ' . $privilege[$i]);
312                     continue;
313             }
314             $grants[$user[$i]]['account_id'] = $user[$i];
315             $grants[$user[$i]]['account_type'] = $type[$i];
316         }
317         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE))
318             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' found grants: ' . print_r($grants, true) . ' for calendar: ' . $calUri);
319         
320         return new Tinebase_Record_RecordSet('Tinebase_Model_Grants', $grants, TRUE);
321     }
322     
323     public function importAllCalendarDataForUsers(array $users)
324     {
325         if (!$this->findCurrentUserPrincipalForUsers($users))
326             return false;
327         
328         $result = true;
329         // first only import events where the current user is also the organizer
330         foreach ($users as $username => $pwd) {
331             $this->clearCurrentUserCalendarData();
332             $this->userName = $username;
333             $this->password = $pwd;
334             if (!$this->importAllCalendarData(true)) {
335                 $result = false;
336             }
337         }
338         // then import all events again
339         foreach ($users as $username => $pwd) {
340             $this->clearCurrentUserCalendarData();
341             $this->userName = $username;
342             $this->password = $pwd;
343             if (!$this->importAllCalendarData(false)) {
344                 $result = false;
345             }
346         }
347         return $result;
348     }
349     
350     public function clearCurrentUserCalendarData()
351     {
352         $this->clearCurrentUserData();
353         $this->calendars = array();
354         $this->calendarICSs = array();
355     }
356 }