0b508414c6edc6c9f1ed83cb194c3303109877d4
[tine20] / tine20 / Calendar / Frontend / Cli.php
1 <?php
2 /**
3  * Tine 2.0
4  * @package     Calendar
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Cornelius Weiss <c.weiss@metaways.de>
7  * @copyright   Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
8  */
9
10 /**
11  * Cli frontend for Calendar
12  *
13  * This class handles cli requests for the Calendar
14  *
15  * @package     Calendar
16  */
17 class Calendar_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
18 {
19     /**
20      * the internal name of the application
21      * 
22      * @var string
23      */
24     protected $_applicationName = 'Calendar';
25     
26     /**
27      * help array with function names and param descriptions
28      * 
29      * @return void
30      */
31     protected $_help = array(
32         'updateCalDavData' => array(
33             'description'    => 'update calendar/events from a CalDav source using etags',
34             'params'         => array(
35                 'url'        => 'CalDav source URL',
36                 'caldavuserfile' => 'CalDav user file containing utf8 username;pwd',
37              )
38         ),
39         'importCalDavData' => array(
40             'description'    => 'import calendar/events from a CalDav source',
41             'params'         => array(
42                 'url'        => 'CalDav source URL',
43                 'caldavuserfile' => 'CalDav user file containing utf8 username;pwd',
44              )
45         ),
46         'importCalDavCalendars' => array(
47             'description'    => 'import calendars without events from a CalDav source',
48             'params'         => array(
49                 'url'        => 'CalDav source URL',
50                 'caldavuserfile' => 'CalDav user file containing utf8 username;pwd',
51              )
52         ),
53         'importegw14' => array(
54             'description'    => 'imports calendars/events from egw 1.4',
55             'params'         => array(
56                 'host'       => 'dbhost',
57                 'username'   => 'username',
58                 'password'   => 'password',
59                 'dbname'     => 'dbname'
60             )
61         ),
62         'exportICS' => array(  
63             'description'    => "export calendar as ics", 
64             'params'         => array('container_id') 
65         ),
66     );
67     
68     /**
69      * return anonymous methods
70      * 
71      * @return array
72      */
73     public static function getAnonymousMethods()
74     {
75         return array('Calendar.repairDanglingDisplaycontainerEvents');
76     }
77     
78     /**
79      * import events
80      *
81      * @param Zend_Console_Getopt $_opts
82      */
83     public function import($_opts)
84     {
85         parent::_import($_opts);
86     }
87     
88     /**
89      * exports calendars as ICS
90      *
91      * @param Zend_Console_Getopt $_opts
92      */
93     public function exportICS($_opts)
94     {
95         $opts = $_opts->getRemainingArgs();
96         $container_id = $opts[0];
97         $filter = new Calendar_Model_EventFilter(array(
98             array(
99                 'field'     => 'container_id',
100                 'operator'  => 'equals',
101                 'value'     => $container_id
102             )
103
104         ));
105         $result = Calendar_Controller_MSEventFacade::getInstance()->search($filter, null, false, false, 'get');
106         if ($result->count() == 0) {
107             throw new Tinebase_Exception('this calendar does not contain any records.');
108         }
109         $converter = Calendar_Convert_Event_VCalendar_Factory::factory("generic");
110         $result = $converter->fromTine20RecordSet($result);
111         print $result->serialize();
112     }
113     
114     /**
115      * delete duplicate events
116      * 
117      * @see 0008182: event with lots of exceptions breaks calendar sync
118      * 
119      * @todo allow user to set params
120      */
121     public function deleteDuplicateEvents()
122     {
123         $writer = new Zend_Log_Writer_Stream('php://output');
124         $writer->addFilter(new Zend_Log_Filter_Priority(6));
125         Tinebase_Core::getLogger()->addWriter($writer);
126         
127         $be = new Calendar_Backend_Sql();
128         $filter = new Calendar_Model_EventFilter(array(array(
129             'field'    => 'dtstart',
130             'operator' => 'after',
131             'value'    => Tinebase_DateTime::now(),
132         ), array(
133             'field'    => 'organizer',
134             'operator' => 'equals',
135             'value'    => 'contactid', // TODO: set correct contact_id or use container_id filter
136         )));
137         $dryrun = TRUE;
138         $be->deleteDuplicateEvents($filter, $dryrun);
139     }
140     
141     /**
142      * repair dangling attendee records (no displaycontainer_id)
143      * 
144      * @see https://forge.tine20.org/mantisbt/view.php?id=8172
145      */
146     public function repairDanglingDisplaycontainerEvents()
147     {
148         $writer = new Zend_Log_Writer_Stream('php://output');
149         $writer->addFilter(new Zend_Log_Filter_Priority(5));
150         Tinebase_Core::getLogger()->addWriter($writer);
151         
152         $be = new Calendar_Backend_Sql();
153         $be->repairDanglingDisplaycontainerEvents();
154     }
155     
156     /**
157      * import calendars from a CalDav source
158      * 
159      * param Zend_Console_Getopt $_opts
160      */
161     public function importCalDavCalendars(Zend_Console_Getopt $_opts)
162     {
163         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile'));
164         
165         $writer = new Zend_Log_Writer_Stream('php://output');
166         $writer->addFilter(new Zend_Log_Filter_Priority(4));
167         Tinebase_Core::getLogger()->addWriter($writer);
168         
169         $users = $this->_readCalDavUserFile($args['caldavuserfile']);
170         
171         $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
172         $client->setVerifyPeer(false);
173         
174         $client->importAllCalendarsForUsers($users);
175     }
176     
177     /**
178      * import calendar events from a CalDav source
179      * 
180      * param Zend_Console_Getopt $_opts
181      */
182     public function importCalDavData(Zend_Console_Getopt $_opts)
183     {
184         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile'));
185         
186         $writer = new Zend_Log_Writer_Stream('php://output');
187         $writer->addFilter(new Zend_Log_Filter_Priority(4));
188         Tinebase_Core::getLogger()->addWriter($writer);
189         
190         $users = $this->_readCalDavUserFile($args['caldavuserfile']);
191         
192         $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
193         $client->setVerifyPeer(false);
194         
195         $client->importAllCalendarDataForUsers($users);
196     }
197     
198     /**
199      * import calendars and calendar events from a CalDav source using multiple parallel processes
200      * 
201      * param Zend_Console_Getopt $_opts
202      */
203     public function importCalDavMultiProc(Zend_Console_Getopt $_opts)
204     {
205         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile', 'numProc'));
206         
207         $numProc = intval($args['numProc']);
208         if ($numProc < 1) {
209             throw new Exception('numProc: ' . $numProc . ' needs to be at least 1');
210         }
211         if ($numProc > 32) {
212             throw new Exception('numProc: ' . $numProc . ' needs to be lower than 33');
213         }
214         
215         $opts = Tinebase_Core::get('opts');
216         if (empty($opts->passwordfile)) {
217             throw new Exception('Passwordfile requried for this method');
218         }
219         
220         $writer = new Zend_Log_Writer_Stream('php://output');
221         $writer->addFilter(new Zend_Log_Filter_Priority(4));
222         Tinebase_Core::getLogger()->addWriter($writer);
223         
224         $users = $this->_readCalDavUserFile($args['caldavuserfile']);
225         
226         //first import the calendars, serial sadly
227         $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
228         $client->setVerifyPeer(false);
229         
230         $client->importAllCalendarsForUsers($users);
231         
232         //then do multiprocess part, no system resources may be used as of here, like file handles, db resources... see pcntl_fork man page!
233         for ($run = 1; $run < 3; ++$run)
234         {
235             $processes = array();
236             $line = 0;
237             foreach ($users as $user => $pwd)
238             {
239                 ++$line;
240                 if (count($processes) >= $numProc) {
241                     $pid = pcntl_wait($status);
242                     if (!isset($processes[$pid])) {
243                         exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
244                     }
245                     unset($processes[$pid]);
246                 }
247                 
248                 $pid = pcntl_fork();
249                 if ($pid == -1) {
250                      exit('could not fork' . PHP_EOL);
251                 } else if ($pid) {
252                      // we are the parent
253                      $processes[$pid] = true;
254                 } else {
255                     // we are the child
256                     exec('./tine20.php --username ' . $opts->username . ' --passwordfile ' . $opts->passwordfile . ' --method Calendar.importCalDavDataForUser url="https://ical.familienservice.de:8443" caldavuserfile=caldavuserfile.csv run=' . $run . ' line=' . $line);
257                     exit();
258                 }
259             }
260             //wait for childs to finish
261             while (count($processes) > 0) {
262                 $pid = pcntl_wait($status);
263                 if (!isset($processes[$pid])) {
264                     exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
265                 }
266                 unset($processes[$pid]);
267             }
268         }
269     }
270     
271     /**
272      * update calendar events from a CalDav source using multiple parallel processes
273      * 
274      * param Zend_Console_Getopt $_opts
275      */
276     public function updateCalDavMultiProc(Zend_Console_Getopt $_opts)
277     {
278         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile', 'numProc'));
279         
280         $numProc = intval($args['numProc']);
281         if ($numProc < 1) {
282             throw new Exception('numProc: ' . $numProc . ' needs to be at least 1');
283         }
284         if ($numProc > 32) {
285             throw new Exception('numProc: ' . $numProc . ' needs to be lower than 33');
286         }
287         
288         $opts = Tinebase_Core::get('opts');
289         if (empty($opts->passwordfile)) {
290             throw new Exception('Passwordfile requried for this method');
291         }
292         
293         $writer = new Zend_Log_Writer_Stream('php://output');
294         $writer->addFilter(new Zend_Log_Filter_Priority(4));
295         Tinebase_Core::getLogger()->addWriter($writer);
296         
297         $users = $this->_readCalDavUserFile($args['caldavuserfile']);
298         
299         //do multiprocess part, no system resources may be used as of here, like file handles, db resources... see pcntl_fork man page!
300         for ($run = 1; $run < 3; ++$run)
301         {
302             $processes = array();
303             $line = 0;
304             foreach ($users as $user => $pwd)
305             {
306                 ++$line;
307                 if (count($processes) >= $numProc) {
308                     $pid = pcntl_wait($status);
309                     if (!isset($processes[$pid])) {
310                         exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
311                     }
312                     unset($processes[$pid]);
313                 }
314                 
315                 $pid = pcntl_fork();
316                 if ($pid == -1) {
317                      exit('could not fork' . PHP_EOL);
318                 } else if ($pid) {
319                      // we are the parent
320                      $processes[$pid] = true;
321                 } else {
322                     // we are the child
323                     exec('./tine20.php --username ' . $opts->username . ' --passwordfile ' . $opts->passwordfile . ' --method Calendar.updateCalDavDataForUser url="https://ical.familienservice.de:8443" caldavuserfile=caldavuserfile.csv run=' . $run . ' line=' . $line);
324                     exit();
325                 }
326             }
327             //wait for childs to finish
328             while (count($processes) > 0) {
329                 $pid = pcntl_wait($status);
330                 if (!isset($processes[$pid])) {
331                     exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
332                 }
333                 unset($processes[$pid]);
334             }
335         }
336     }
337     
338     /**
339      * import calendar events from a CalDav source for one user
340      * 
341      * param Zend_Console_Getopt $_opts
342      */
343     public function importCalDavDataForUser(Zend_Console_Getopt $_opts)
344     {
345         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile', 'line', 'run'));
346         
347         $writer = new Zend_Log_Writer_Stream('php://output');
348         $writer->addFilter(new Zend_Log_Filter_Priority(4));
349         Tinebase_Core::getLogger()->addWriter($writer);
350         
351         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
352             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' in importCalDavDataForUser');
353         
354         $user = $this->_readCalDavUserFile($args['caldavuserfile'], $args['line']);
355         
356         $client = new Calendar_Import_CalDav_Client(array(
357                 'baseUri' => $args['url'],
358                 'userName' => $user['username'],
359                 'password' => $user['password'],
360                 ), 'MacOSX');
361         $client->setVerifyPeer(false);
362         
363         $client->importAllCalendarData($args['run']==1?true:false);
364     }
365     
366     /**
367      * update calendar/events from a CalDav source using etags for one user
368      * 
369      * param Zend_Console_Getopt $_opts
370      */
371     public function updateCalDavDataForUser(Zend_Console_Getopt $_opts)
372     {
373         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile', 'line', 'run'));
374         
375         $writer = new Zend_Log_Writer_Stream('php://output');
376         $writer->addFilter(new Zend_Log_Filter_Priority(4));
377         Tinebase_Core::getLogger()->addWriter($writer);
378         
379         $user = $this->_readCalDavUserFile($args['caldavuserfile'], $args['line']);
380         
381         $client = new Calendar_Import_CalDav_Client(array(
382                 'baseUri' => $args['url'],
383                 'userName' => $user['username'],
384                 'password' => $user['password'],
385                 ), 'MacOSX');
386         $client->setVerifyPeer(false);
387         
388         $client->updateAllCalendarData($args['run']==1?true:false);
389     }
390     
391     /**
392      * update calendar/events from a CalDav source using etags
393      * 
394      * param Zend_Console_Getopt $_opts
395      */
396     public function updateCalDavData(Zend_Console_Getopt $_opts)
397     {
398         $args = $this->_parseArgs($_opts, array('url', 'caldavuserfile'));
399         
400         $writer = new Zend_Log_Writer_Stream('php://output');
401         $writer->addFilter(new Zend_Log_Filter_Priority(4));
402         Tinebase_Core::getLogger()->addWriter($writer);
403         
404         $users = $this->_readCalDavUserFile($args['caldavuserfile']);
405         
406         $client = new Calendar_Import_CalDav_Client(array('baseUri' => $args['url']), 'MacOSX');
407         $client->setVerifyPeer(false);
408         
409         $client->updateAllCalendarDataForUsers($users);
410     }
411     
412     /**
413      * read caldav user credentials file
414      * 
415      * - file should have the following format (CSV):
416      * USERNAME1;PASSWORD1
417      * USERNAME2;PASSWORD2
418      * 
419      * @param string $file
420      * @throws Exception
421      */
422     protected function _readCalDavUserFile($file, $line = 0)
423     {
424         if (!($fh = fopen($file, 'r'))) {
425             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Couldn\'t open file: '.$file);
426             throw new Exception('Couldn\'t open file: '.$file);
427         }
428         $users = array();
429         $i = 0;
430         while ($row = fgetcsv($fh, 2048, ';'))
431         {
432             if ($line > 0 && ++$i == $line) {
433                 return array('username' => $row[0], 'password' => $row[1]);
434             }
435             $users[$row[0]] = $row[1];
436         }
437         if (count($users) < 1) {
438             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' No users found in: '.$file);
439             throw new Exception('No users found in: '.$file);
440         }
441         if ($line > 0) {
442             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Line: '.$line. ' out of bounds');
443             throw new Exception('No user found, line: '.$line. ' out of bounds');
444         }
445         return $users;
446     }
447 }