0011370: repair function for persistent filters (favorites) without grants
[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     public function createDemoData($_opts = NULL, $checkDependencies = TRUE)
126     {
127         // just admins can perform this action
128         if (! $this->_checkAdminRight()) {
129             return FALSE;
130         }
131         
132         $className = $this->_applicationName . '_Setup_DemoData';
133         
134         if (class_exists($className)) {
135             if ($checkDependencies) {
136                 foreach($className::getRequiredApplications() as $appName) {
137                     if (Tinebase_Application::getInstance()->isInstalled($appName)) {
138                         $cname = $appName . '_Setup_DemoData';
139                         if (class_exists($cname)) {
140                             if (! $cname::hasBeenRun()) {
141                                 $className2 = $appName . '_Frontend_Cli';
142                                 if (class_exists($className2)) {
143                                     echo 'Creating required DemoData of application "' . $appName . '"...' . PHP_EOL;
144                                     $class = new $className2();
145                                     $class->createDemoData($_opts, TRUE);
146                                 }
147                             }
148                         }
149                     }
150                 }
151             }
152             
153             $options = array('createUsers' => TRUE, 'createShared' => TRUE, 'models' => NULL, 'locale' => 'de', 'password' => '');
154             
155             if ($_opts) {
156                 $args = $this->_parseArgs($_opts, array());
157                 
158                 if ((isset($args['other']) || array_key_exists('other', $args))) {
159                     $options['createUsers']  = in_array('sharedonly', $args['other']) ? FALSE : TRUE;
160                     $options['createShared'] = in_array('noshared',   $args['other']) ? FALSE : TRUE;
161                     $options['full']         = in_array('full',       $args['other']) ? FALSE : TRUE;
162                 }
163                 
164                 // locale defaults to de
165                 if (isset($args['locale'])) {
166                     $options['locale'] = $args['locale'];
167                 }
168                 
169                 // password defaults to empty password
170                 if (isset($args['password'])) {
171                     $options['password'] = $args['password'];
172                 }
173                 
174                 if (isset($args['users'])) {
175                     $options['users'] = is_array($args['users']) ? $args['users'] : array($args['users']);
176                 }
177                 
178                 if (isset($args['models'])) {
179                     $options['models'] = is_array($args['models']) ? $args['models'] : array($args['models']);
180                 }
181             }
182             
183
184             if ($className::getInstance()->createDemoData($options)) {
185                 echo 'Demo Data was created successfully' . chr(10) . chr(10);
186             } else {
187                 echo 'No Demo Data has been created' . chr(10) . chr(10);
188             }
189         } else {
190             echo chr(10);
191             echo 'Creating Demo Data is not implemented yet for this Application!' . chr(10);
192             echo chr(10);
193         }
194
195         return true;
196     }
197     
198     /**
199      * get container for setContainerGrants
200      * 
201      * @param array $_params
202      * @return Tinebase_Record_RecordSet
203      * @throws Timetracker_Exception_UnexpectedValue
204      */
205     protected function _getContainers($_params)
206     {
207         $application = Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName);
208         $containerFilterData = array(
209             array('field' => 'application_id', 'operator' => 'equals', 'value' => $application->getId()),
210         );
211         
212         if ((isset($_params['containerId']) || array_key_exists('containerId', $_params))) {
213             $containerFilterData[] = array('field' => 'id', 'operator' => 'equals', 'value' => $_params['containerId']);
214         } else if ((isset($_params['namefilter']) || array_key_exists('namefilter', $_params))) {
215             $containerFilterData[] = array('field' => 'name', 'operator' => 'contains', 'value' => $_params['namefilter']);
216         } else {
217             throw new Timetracker_Exception_UnexpectedValue('Parameter containerId or namefilter missing!');
218         }
219         
220         $containers = Tinebase_Container::getInstance()->search(new Tinebase_Model_ContainerFilter($containerFilterData));
221         
222         return $containers;
223     }
224     
225     /**
226      * parses arguments (key1=value1 key2=value2 key3=subvalue1,subvalue2 ...)
227      * 
228      * @param Zend_Console_Getopt $_opts
229      * @param array $_requiredKeys
230      * @param string $_otherKey use this key for arguments without '='
231      * @throws Tinebase_Exception_InvalidArgument
232      * @return array
233      */
234     protected function _parseArgs(Zend_Console_Getopt $_opts, $_requiredKeys = array(), $_otherKey = 'other')
235     {
236         $args = $_opts->getRemainingArgs();
237         
238         $result = array();
239         foreach ($args as $idx => $arg) {
240             if (strpos($arg, '=') !== false) {
241                 list($key, $value) = explode('=', $arg);
242                 if (strpos($value, ',') !== false) {
243                     $value = explode(',', $value);
244                 }
245                 $value = str_replace('"', '', $value);
246                 $result[$key] = $value;
247             } else {
248                 $result[$_otherKey][] = $arg;
249             }
250         }
251         
252         if (! empty($_requiredKeys)) {
253             foreach ($_requiredKeys as $requiredKey) {
254                 if (! (isset($result[$requiredKey]) || array_key_exists($requiredKey, $result))) {
255                     throw new Tinebase_Exception_InvalidArgument('Required parameter not found: ' . $requiredKey);
256                 }
257             }
258         }
259         
260         return $result;
261     }
262     
263     /**
264      * check admin right of application
265      * 
266      * @return boolean
267      */
268     protected function _checkAdminRight()
269     {
270         // check if admin for tinebase
271         if (! Tinebase_Core::getUser()->hasRight($this->_applicationName, Tinebase_Acl_Rights::ADMIN)) {
272             echo "No permission.\n";
273             return FALSE;
274         }
275         
276         return TRUE;
277     }
278     
279     /**
280      * import records
281      *
282      * @param Zend_Console_Getopt   $_opts
283      * @return array import result
284      */
285     protected function _import($_opts)
286     {
287         $args = $this->_parseArgs($_opts, array(), 'filename');
288         
289         if ($_opts->d) {
290             $args['dryrun'] = 1;
291             if ($_opts->v) {
292                 echo "Doing dry run.\n";
293             }
294         }
295         
296         if ((isset($args['definition']) || array_key_exists('definition', $args)))  {
297             if (preg_match("/\.xml/", $args['definition'])) {
298                 $definition = Tinebase_ImportExportDefinition::getInstance()->getFromFile(
299                     $args['definition'],
300                     Tinebase_Application::getInstance()->getApplicationByName($this->_applicationName)->getId()
301                 );
302             } else {
303                 $definition = Tinebase_ImportExportDefinition::getInstance()->getByName($args['definition']);
304             }
305             // If old Admin Import plugin is given use the new one!
306             if ($definition->plugin == 'Admin_Import_Csv') {
307                 $definition->plugin = 'Admin_Import_User_Csv';
308             }
309             $importer = call_user_func($definition->plugin . '::createFromDefinition', $definition, $args);
310         } else if ((isset($args['plugin']) || array_key_exists('plugin', $args))) {
311             $importer =  new $args['plugin']($args);
312         } else {
313             echo "You need to define a plugin OR a definition at least! \n";
314             exit;
315         }
316         
317         // loop files in argv
318         $result = array();
319         foreach ((array) $args['filename'] as $filename) {
320             // read file
321             if ($_opts->v) {
322                 echo "reading file $filename ...";
323             }
324             try {
325                 $result[$filename] = $importer->importFile($filename);
326                 if ($_opts->v) {
327                     echo "done.\n";
328                 }
329             } catch (Exception $e) {
330                 if ($_opts->v) {
331                     echo "failed (". $e->getMessage() . ").\n";
332                 } else {
333                     echo $e->getMessage() . "\n";
334                 }
335                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getMessage());
336                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
337                 continue;
338             }
339             
340             echo "Imported " . $result[$filename]['totalcount'] . " records. Import failed for " . $result[$filename]['failcount'] . " records. \n";
341             if (isset($result[$filename]['duplicatecount']) && ! empty($result[$filename]['duplicatecount'])) {
342                 echo "Found " . $result[$filename]['duplicatecount'] . " duplicates.\n";
343             }
344             
345             // import (check if dry run)
346             if ($_opts->d && $_opts->v) {
347                 print_r($result[$filename]['results']->toArray());
348                 
349                 if ($result[$filename]['failcount'] > 0) {
350                     print_r($result[$filename]['exceptions']->toArray());
351                 }
352             } 
353         }
354         
355         return $result;
356     }
357
358     /**
359      * search for duplicates
360      * 
361      * @param Tinebase_Controller_Record_Interface $_controller
362      * @param  Tinebase_Model_Filter_FilterGroup
363      * @param string $_field
364      * @return array with ids / field
365      * 
366      * @todo add more options (like soundex, what do do with duplicates/delete/merge them, ...)
367      */
368     protected function _searchDuplicates(Tinebase_Controller_Record_Abstract $_controller, $_filter, $_field)
369     {
370         $pagination = new Tinebase_Model_Pagination(array(
371             'start' => 0,
372             'limit' => 100,
373         ));
374         $results = array();
375         $allRecords = array();
376         $totalCount = $_controller->searchCount($_filter);
377         echo 'Searching ' . $totalCount . " record(s) for duplicates\n";
378         while ($pagination->start < $totalCount) {
379             $records = $_controller->search($_filter, $pagination);
380             foreach ($records as $record) {
381                 if (in_array($record->{$_field}, $allRecords)) {
382                     $allRecordsFlipped = array_flip($allRecords);
383                     $duplicateId = $allRecordsFlipped[$record->{$_field}];
384                     $results[] = array('id' => $duplicateId, 'value' => $record->{$_field});
385                     $results[] = array('id' => $record->getId(), 'value' => $record->{$_field});
386                 }
387                 
388                 $allRecords[$record->getId()] = $record->{$_field};
389             }
390             $pagination->start += 100;
391         }
392         
393         return $results;
394     }
395     
396     /**
397      * add log writer for php://output
398      * 
399      * @param integer $priority
400      */
401     protected function _addOutputLogWriter($priority = 5)
402     {
403         $writer = new Zend_Log_Writer_Stream('php://output');
404         $writer->addFilter(new Zend_Log_Filter_Priority($priority));
405         Tinebase_Core::getLogger()->addWriter($writer);
406     }
407     
408     /**
409      * import from egroupware
410      *
411      * @param Zend_Console_Getopt $_opts
412      */
413     public function importegw14($_opts)
414     {
415         $args = $_opts->getRemainingArgs();
416         
417         if (count($args) < 1 || ! is_readable($args[0])) {
418             echo "can not open config file \n";
419             echo "see tine20.org/wiki/EGW_Migration_Howto for details \n\n";
420             echo "usage: ./tine20.php --method=Appname.importegw14 /path/to/config.ini  (see Tinebase/Setup/Import/Egw14/config.ini)\n\n";
421             exit(1);
422         }
423         
424         try {
425             $config = new Zend_Config_Ini($args[0]);
426             if ($config->{strtolower($this->_applicationName)}) {
427                 $config = $config->{strtolower($this->_applicationName)};
428             }
429         } catch (Zend_Config_Exception $e) {
430             fwrite(STDERR, "Error while parsing config file($args[0]) " .  $e->getMessage() . PHP_EOL);
431             exit(1);
432         }
433         
434         $writer = new Zend_Log_Writer_Stream('php://output');
435         $logger = new Zend_Log($writer);
436         
437         $filter = new Zend_Log_Filter_Priority((int) $config->loglevel);
438         $logger->addFilter($filter);
439         
440         $class_name = $this->_applicationName . '_Setup_Import_Egw14';
441         if (! class_exists($class_name)) {
442             $logger->ERR(__METHOD__ . '::' . __LINE__ . " no import for {$this->_applicationName} available");
443             exit(1);
444         }
445         
446         try {
447             $importer = new $class_name($config, $logger);
448             $importer->import();
449         } catch (Exception $e) {
450             $logger->ERR(__METHOD__ . '::' . __LINE__ . " import for {$this->_applicationName} failed ". $e->getMessage());
451         }
452     }
453 }