4dba9922af7d86383c09c5ae36904c49df9db227
[tine20] / tine20 / Tinebase / Frontend / Cli / Abstract.php
1 <?php
2 /**
3  * Tine 2.0
4  * @package     Tinebase
5  * @subpackage  Frontend
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @author      Philipp Schüle <p.schuele@metaways.de>
8  * @copyright   Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9  * 
10  */
11
12 /**
13  * abstract cli server
14  *
15  * This class handles cli requests
16  *
17  * @package     Tinebase
18  * @subpackage  Frontend
19  */
20 class Tinebase_Frontend_Cli_Abstract
21 {
22     /**
23      * the internal name of the application
24      *
25      * @var string
26      */
27     protected $_applicationName = 'Tinebase';
28     
29     /**
30      * help array with function names and param descriptions
31      */
32     protected $_help = array();
33     
34     /**
35      * echos usage information
36      *
37      */
38     public function getHelp()
39     {
40         foreach ($this->_help as $functionHelp) {
41             echo $functionHelp['description']."\n";
42             echo "parameters:\n";
43             foreach ($functionHelp['params'] as $param => $description) {
44                 echo "$param \t $description \n";
45             }
46         }
47     }
48     
49     /**
50      * update or create import/export definition
51      * 
52      * @param Zend_Console_Getopt $_opts
53      * @return boolean
54      */
55     public function updateImportExportDefinition(Zend_Console_Getopt $_opts)
56     {
57         $defs = $_opts->getRemainingArgs();
58         if (empty($defs)) {
59             echo "No definition given.\n";
60             return FALSE;
61         }
62         
63         if (! $this->_checkAdminRight()) {
64             return FALSE;
65         }
66         
67         $application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
68         
69         foreach ($defs as $definitionFilename) {
70             Tinebase_ImportExportDefinition::getInstance()->updateOrCreateFromFilename($definitionFilename, $application);
71             echo "Imported " . $definitionFilename . " successfully.\n";
72         }
73         
74         return TRUE;
75     }
76
77     /**
78      * set container grants
79      * 
80      * example usages: 
81      * (1) $ php tine20.php --method=Calendar.setContainerGrants containerId=3339 accountId=15 accountType=group grants=readGrant
82      * (2) $ php tine20.php --method=Timetracker.setContainerGrants namefilter="timeaccount name" accountId=15,30 accountType=group grants=book_own,manage_billable overwrite=1
83      * 
84      * @param Zend_Console_Getopt $_opts
85      * @return boolean
86      */
87     public function setContainerGrants(Zend_Console_Getopt $_opts)
88     {
89         if (! $this->_checkAdminRight()) {
90             return FALSE;
91         }
92         
93         $data = $this->_parseArgs($_opts, array('accountId', 'grants'));
94         
95         $containers = $this->_getContainers($data);
96         if (count($containers) == 0) {
97             echo "No matching containers found.\n";
98         } else {
99             Admin_Controller_Container::getInstance()->setGrantsForContainers(
100                 $containers, 
101                 $data['grants'],
102                 $data['accountId'], 
103                 ((isset($data['accountType']) || array_key_exists('accountType', $data))) ? $data['accountType'] : Tinebase_Acl_Rights::ACCOUNT_TYPE_USER,
104                 ((isset($data['overwrite']) || array_key_exists('overwrite', $data)) && $data['overwrite'] == '1')
105             );
106             
107             echo "Updated " . count($containers) . " container(s).\n";
108         }
109         
110         return TRUE;
111     }
112     
113     /**
114      * create demo data
115      * 
116      * example usages: 
117      * (1) $ php tine20.php --method=Calendar.createDemoData --username=admin --password=xyz locale=de users=pwulf,rwright // Creates demo events for pwulf and rwright with the locale de, just de and en is supported at the moment
118      * (2) $ php tine20.php --method=Calendar.createDemoData --username=admin --password=xyz models=Calendar,Event sharedonly // Creates shared calendars and events with the default locale en                
119      * (3) $ php tine20.php --method=Calendar.createDemoData --username=admin --password=xyz // Creates all demo calendars and events for all users
120      * (4) $ php tine20.php --method=Calendar.createDemoData --username=admin --password=xyz full // Creates much more demo data than (3)
121      * 
122      * @param Zend_Console_Getopt $_opts
123      * @param boolean $checkDependencies
124      */
125     
126     public function createDemoData($_opts = NULL, $checkDependencies = TRUE)
127     {
128         // just admins can perform this action
129         if (! $this->_checkAdminRight()) {
130             return FALSE;
131         }
132         
133         $className = $this->_applicationName . '_Setup_DemoData';
134         
135         if (class_exists($className)) {
136             if ($checkDependencies) {
137                 foreach($className::getRequiredApplications() as $appName) {
138                     if (Tinebase_Application::getInstance()->isInstalled($appName)) {
139                         $cname = $appName . '_Setup_DemoData';
140                         if (class_exists($cname)) {
141                             if (! $cname::hasBeenRun()) {
142                                 $className2 = $appName . '_Frontend_Cli';
143                                 if (class_exists($className2)) {
144                                     echo 'Creating required DemoData of application "' . $appName . '"...' . PHP_EOL;
145                                     $class = new $className2();
146                                     $class->createDemoData($_opts, TRUE);
147                                 }
148                             }
149                         }
150                     }
151                 }
152             }
153             
154             $options = array('createUsers' => TRUE, 'createShared' => TRUE, 'models' => NULL, 'locale' => 'de', 'password' => '');
155             
156             if ($_opts) {
157                 $args = $this->_parseArgs($_opts, array());
158                 
159                 if ((isset($args['other']) || array_key_exists('other', $args))) {
160                     $options['createUsers']  = in_array('sharedonly', $args['other']) ? FALSE : TRUE;
161                     $options['createShared'] = in_array('noshared',   $args['other']) ? FALSE : TRUE;
162                     $options['full']         = in_array('full',       $args['other']) ? FALSE : TRUE;
163                 }
164                 
165                 // locale defaults to de
166                 if (isset($args['locale'])) {
167                     $options['locale'] = $args['locale'];
168                 }
169                 
170                 // password defaults to empty password
171                 if (isset($args['password'])) {
172                     $options['password'] = $args['password'];
173                 }
174                 
175                 if (isset($args['users'])) {
176                     $options['users'] = is_array($args['users']) ? $args['users'] : array($args['users']);
177                 }
178                 
179                 if (isset($args['models'])) {
180                     $options['models'] = is_array($args['models']) ? $args['models'] : array($args['models']);
181                 }
182             }
183             
184
185             if ($className::getInstance()->createDemoData($options)) {
186                 echo 'Demo Data was created successfully' . chr(10) . chr(10);
187             } else {
188                 echo 'No Demo Data has been created' . chr(10) . chr(10);
189             }
190         } else {
191             echo chr(10);
192             echo 'Creating Demo Data is not implemented yet for this Application!' . chr(10);
193             echo chr(10);
194         }
195     }
196     
197     /**
198      * get container for setContainerGrants
199      * 
200      * @param array $_params
201      * @return Tinebase_Record_RecordSet
202      * @throws Timetracker_Exception_UnexpectedValue
203      */
204     protected function _getContainers($_params)
205     {
206         $application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
207         $containerFilterData = array(
208             array('field' => 'application_id', 'operator' => 'equals', 'value' => $application->getId()),
209         );
210         
211         if ((isset($_params['containerId']) || array_key_exists('containerId', $_params))) {
212             $containerFilterData[] = array('field' => 'id', 'operator' => 'equals', 'value' => $_params['containerId']);
213         } else if ((isset($_params['namefilter']) || array_key_exists('namefilter', $_params))) {
214             $containerFilterData[] = array('field' => 'name', 'operator' => 'contains', 'value' => $_params['namefilter']);
215         } else {
216             throw new Timetracker_Exception_UnexpectedValue('Parameter containerId or namefilter missing!');
217         }
218         
219         $containers = Tinebase_Container::getInstance()->search(new Tinebase_Model_ContainerFilter($containerFilterData));
220         
221         return $containers;
222     }
223     
224     /**
225      * parses arguments (key1=value1 key2=value2 key3=subvalue1,subvalue2 ...)
226      * 
227      * @param Zend_Console_Getopt $_opts
228      * @param array $_requiredKeys
229      * @param string $_otherKey use this key for arguments without '='
230      * @throws Tinebase_Exception_InvalidArgument
231      * @return array
232      */
233     protected function _parseArgs(Zend_Console_Getopt $_opts, $_requiredKeys = array(), $_otherKey = 'other')
234     {
235         $args = $_opts->getRemainingArgs();
236         
237         $result = array();
238         foreach ($args as $idx => $arg) {
239             if (strpos($arg, '=') !== false) {
240                 list($key, $value) = explode('=', $arg);
241                 if (strpos($value, ',') !== false) {
242                     $value = explode(',', $value);
243                 }
244                 $value = str_replace('"', '', $value);
245                 $result[$key] = $value;
246             } else {
247                 $result[$_otherKey][] = $arg;
248             }
249         }
250         
251         if (! empty($_requiredKeys)) {
252             foreach ($_requiredKeys as $requiredKey) {
253                 if (! (isset($result[$requiredKey]) || array_key_exists($requiredKey, $result))) {
254                     throw new Tinebase_Exception_InvalidArgument('Required parameter not found: ' . $requiredKey);
255                 }
256             }
257         }
258         
259         return $result;
260     }
261     
262     /**
263      * check admin right of application
264      * 
265      * @return boolean
266      */
267     protected function _checkAdminRight()
268     {
269         // check if admin for tinebase
270         if (! Tinebase_Core::getUser()->hasRight($this->_applicationName, Tinebase_Acl_Rights::ADMIN)) {
271             echo "No permission.\n";
272             return FALSE;
273         }
274         
275         return TRUE;
276     }
277     
278     /**
279      * import records
280      *
281      * @param Zend_Console_Getopt   $_opts
282      * @return array import result
283      */
284     protected function _import($_opts)
285     {
286         $args = $this->_parseArgs($_opts, array(), 'filename');
287         
288         if ($_opts->d) {
289             $args['dryrun'] = 1;
290             if ($_opts->v) {
291                 echo "Doing dry run.\n";
292             }
293         }
294         
295         if ((isset($args['definition']) || array_key_exists('definition', $args)))  {
296             if (preg_match("/\.xml/", $args['definition'])) {
297                 $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
298                     $args['definition'],
299                     Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId()
300                 );
301             } else {
302                 $definition = Tinebase_ImportExportDefinition::getInstance()->getByName($args['definition']);
303             }
304             // If old Admin Import plugin is given use the new one!
305             if ($definition->plugin == 'Admin_Import_Csv') {
306                 $definition->plugin = 'Admin_Import_User_Csv';
307             }
308             $importer = call_user_func($definition->plugin . '::createFromDefinition', $definition, $args);
309         } else if ((isset($args['plugin']) || array_key_exists('plugin', $args))) {
310             $importer =  new $args['plugin']($args);
311         } else {
312             echo "You need to define a plugin OR a definition at least! \n";
313             exit;
314         }
315         
316         // loop files in argv
317         $result = array();
318         foreach ((array) $args['filename'] as $filename) {
319             // read file
320             if ($_opts->v) {
321                 echo "reading file $filename ...";
322             }
323             try {
324                 $result[$filename] = $importer->importFile($filename);
325                 if ($_opts->v) {
326                     echo "done.\n";
327                 }
328             } catch (Exception $e) {
329                 if ($_opts->v) {
330                     echo "failed (". $e->getMessage() . ").\n";
331                 } else {
332                     echo $e->getMessage() . "\n";
333                 }
334                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
335                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
336                 continue;
337             }
338             
339             echo "Imported " . $result[$filename]['totalcount'] . " records. Import failed for " . $result[$filename]['failcount'] . " records. \n";
340             if (isset($result[$filename]['duplicatecount']) && ! empty($result[$filename]['duplicatecount'])) {
341                 echo "Found " . $result[$filename]['duplicatecount'] . " duplicates.\n";
342             }
343             
344             // import (check if dry run)
345             if ($_opts->d && $_opts->v) {
346                 print_r($result[$filename]['results']->toArray());
347                 
348                 if ($result[$filename]['failcount'] > 0) {
349                     print_r($result[$filename]['exceptions']->toArray());
350                 }
351             } 
352         }
353         
354         return $result;
355     }
356
357     /**
358      * search for duplicates
359      * 
360      * @param Tinebase_Controller_Record_Interface $_controller
361      * @param  Tinebase_Model_Filter_FilterGroup
362      * @param string $_field
363      * @return array with ids / field
364      * 
365      * @todo add more options (like soundex, what do do with duplicates/delete/merge them, ...)
366      */
367     protected function _searchDuplicates(Tinebase_Controller_Record_Abstract $_controller, $_filter, $_field)
368     {
369         $pagination = new Tinebase_Model_Pagination(array(
370             'start' => 0,
371             'limit' => 100,
372         ));
373         $results = array();
374         $allRecords = array();
375         $totalCount = $_controller->searchCount($_filter);
376         echo 'Searching ' . $totalCount . " record(s) for duplicates\n";
377         while ($pagination->start < $totalCount) {
378             $records = $_controller->search($_filter, $pagination);
379             foreach ($records as $record) {
380                 if (in_array($record->{$_field}, $allRecords)) {
381                     $allRecordsFlipped = array_flip($allRecords);
382                     $duplicateId = $allRecordsFlipped[$record->{$_field}];
383                     $results[] = array('id' => $duplicateId, 'value' => $record->{$_field});
384                     $results[] = array('id' => $record->getId(), 'value' => $record->{$_field});
385                 }
386                 
387                 $allRecords[$record->getId()] = $record->{$_field};
388             }
389             $pagination->start += 100;
390         }
391         
392         return $results;
393     }
394     
395     /**
396      * add log writer for php://output
397      * 
398      * @param integer $priority
399      */
400     protected function _addOutputLogWriter($priority = 5)
401     {
402         $writer = new Zend_Log_Writer_Stream('php://output');
403         $writer->addFilter(new Zend_Log_Filter_Priority($priority));
404         Tinebase_Core::getLogger()->addWriter($writer);
405     }
406     
407     /**
408      * import from egroupware
409      *
410      * @param Zend_Console_Getopt $_opts
411      */
412     public function importegw14($_opts)
413     {
414         $args = $_opts->getRemainingArgs();
415         
416         if (count($args) < 1 || ! is_readable($args[0])) {
417             echo "can not open config file \n";
418             echo "see tine20.org/wiki/EGW_Migration_Howto for details \n\n";
419             echo "usage: ./tine20.php --method=Appname.importegw14 /path/to/config.ini  (see Tinebase/Setup/Import/Egw14/config.ini)\n\n";
420             exit(1);
421         }
422         
423         try {
424             $config = new Zend_Config_Ini($args[0]);
425             if ($config->{strtolower($this->_applicationName)}) {
426                 $config = $config->{strtolower($this->_applicationName)};
427             }
428         } catch (Zend_Config_Exception $e) {
429             fwrite(STDERR, "Error while parsing config file($args[0]) " .  $e->getMessage() . PHP_EOL);
430             exit(1);
431         }
432         
433         $writer = new Zend_Log_Writer_Stream('php://output');
434         $logger = new Zend_Log($writer);
435         
436         $filter = new Zend_Log_Filter_Priority((int) $config->loglevel);
437         $logger->addFilter($filter);
438         
439         $class_name = $this->_applicationName . '_Setup_Import_Egw14';
440         if (! class_exists($class_name)) {
441             $logger->ERR(__METHOD__ . '::' . __LINE__ . " no import for {$this->_applicationName} available");
442             continue;
443         }
444         
445         try {
446             $importer = new $class_name($config, $logger);
447             $importer->import();
448         } catch (Exception $e) {
449             $logger->ERR(__METHOD__ . '::' . __LINE__ . " import for {$this->_applicationName} failed ". $e->getMessage());
450         }
451     }
452 }