0011960: print with 300 dpi by default
[tine20] / tine20 / Tinebase / Frontend / Cli.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) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
9  */
10
11 /**
12  * cli server
13  *
14  * This class handles all requests from cli scripts
15  *
16  * @package     Tinebase
17  * @subpackage  Frontend
18  */
19 class Tinebase_Frontend_Cli extends Tinebase_Frontend_Cli_Abstract
20 {
21     /**
22      * the internal name of the application
23      *
24      * @var string
25      */
26     protected $_applicationName = 'Tinebase';
27
28     /**
29      * needed by demo data fns
30      *
31      * @var array
32      */
33     protected $_applicationsToWorkOn = array();
34
35     /**
36     * @param Zend_Console_Getopt $_opts
37     * @return boolean success
38     */
39     public function rebuildPaths($opts)
40     {
41         if (! $this->_checkAdminRight()) {
42             return -1;
43         }
44
45         $applications = Tinebase_Application::getInstance()->getApplications();
46         foreach($applications as $application) {
47             try {
48                 $app = Tinebase_Core::getApplicationInstance($application, '', true);
49             } catch (Tinebase_Exception_NotFound $tenf) {
50                 continue;
51             }
52
53             if (! $app instanceof Tinebase_Controller_Abstract) {
54                 continue;
55             }
56
57             $pathModels = $app->getModelsUsingPaths();
58             if (!is_array($pathModels)) {
59                 $pathModels = array();
60             }
61             foreach($pathModels as $pathModel) {
62                 $controller = Tinebase_Core::getApplicationInstance($pathModel, '', true);
63
64                 $_filter = $pathModel . 'Filter';
65                 $_filter = new $_filter();
66
67                 $iterator = new Tinebase_Record_Iterator(array(
68                     'iteratable' => $this,
69                     'controller' => $controller,
70                     'filter' => $_filter,
71                     'options' => array('getRelations' => true),
72                     'function' => 'rebuildPathsIteration',
73                 ));
74                 $result = $iterator->iterate($controller);
75
76                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
77                     if (false === $result) {
78                         $result['totalcount'] = 0;
79                     }
80                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Build paths for ' . $result['totalcount'] . ' records of ' . $pathModel);
81                 }
82             }
83         }
84     }
85
86     /**
87      * rebuild paths for multiple records in an iteration
88      * @see Tinebase_Record_Iterator / self::rebuildPaths()
89      *
90      * @param Tinebase_Record_RecordSet $records
91      * @param Tinebase_Controller_Abstract $controller
92      */
93     public function rebuildPathsIteration(Tinebase_Record_RecordSet $records, Tinebase_Controller_Record_Abstract $controller)
94     {
95         foreach ($records as $record) {
96             try {
97                 $controller->buildPath($record, true);
98             } catch (Exception $e) {
99                 Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' record path building failed: '
100                     . $e->getMessage() . PHP_EOL
101                     . $e->getTraceAsString() . PHP_EOL
102                     . $record->toArray());
103             }
104         }
105     }
106
107     /**
108      * clean timemachine_modlog for records that have been pruned (not deleted!)
109      */
110     public function cleanModlog()
111     {
112         $deleted = Tinebase_Timemachine_ModificationLog::getInstance()->clean();
113
114         echo "\ndeleted $deleted modlogs records\n";
115     }
116
117     /**
118      * clean relations, set relation to deleted if at least one of the ends has been set to deleted or pruned
119      */
120     public function cleanRelations()
121     {
122         $relations = Tinebase_Relations::getInstance();
123         $filter = new Tinebase_Model_Filter_FilterGroup();
124         $pagination = new Tinebase_Model_Pagination();
125         $pagination->limit = 10000;
126         $pagination->sort = 'id';
127
128         $totalCount = 0;
129         $date = Tinebase_DateTime::now()->subYear(1);
130
131         while ( ($recordSet = $relations->search($filter, $pagination)) && $recordSet->count() > 0 ) {
132             $filter = new Tinebase_Model_Filter_FilterGroup();
133             $pagination->start += $pagination->limit;
134             $models = array();
135
136             foreach($recordSet as $relation) {
137                 $models[$relation->own_model][$relation->own_id][] = $relation->id;
138                 $models[$relation->related_model][$relation->related_id][] = $relation->id;
139             }
140             foreach ($models as $model => &$ids) {
141                 $doAll = false;
142
143                 try {
144                     $app = Tinebase_Core::getApplicationInstance($model, '', true);
145                 } catch (Tinebase_Exception_NotFound $tenf) {
146                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
147                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' no application found for it');
148                     $doAll = true;
149                 }
150                 if (!$doAll) {
151                     if ($app instanceof Tinebase_Container)
152                     {
153                         $backend = $app;
154                     } else {
155                         if (!$app instanceof Tinebase_Controller_Record_Abstract) {
156                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
157                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' controller: ' . get_class($app) . ' not an instance of Tinebase_Controller_Record_Abstract');
158                             continue;
159                         }
160
161                         $backend = $app->getBackend();
162                     }
163                     if (!$backend instanceof Tinebase_Backend_Interface) {
164                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
165                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' backend: ' . get_class($backend) . ' not an instance of Tinebase_Backend_Interface');
166                         continue;
167                     }
168                     $record = new $model(null, true);
169
170                     $modelFilter = $model . 'Filter';
171                     $idFilter = new $modelFilter(array(), '', array('ignoreAcl' => true));
172                     $idFilter->addFilter(new Tinebase_Model_Filter_Id(array(
173                         'field' => $record->getIdProperty(), 'operator' => 'in', 'value' => array_keys($ids)
174                     )));
175
176
177                     $existingIds = $backend->search($idFilter, null, true);
178
179                     if (!is_array($existingIds)) {
180                         throw new Exception('search for model: ' . $model . ' returned not an array!');
181                     }
182                     foreach ($existingIds as $id) {
183                         unset($ids[$id]);
184                     }
185                 }
186
187                 if ( count($ids) > 0 ) {
188                     $toDelete = array();
189                     foreach ($ids as $idArrays) {
190                         foreach ($idArrays as $id) {
191                             $toDelete[$id] = true;
192                         }
193                     }
194
195                     $toDelete = array_keys($toDelete);
196
197                     foreach($toDelete as $id) {
198                         if ( $recordSet->getById($id)->creation_time && $recordSet->getById($id)->creation_time->isLater($date) ) {
199                             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' relation is about to get deleted that is younger than 1 year: ' . print_r($recordSet->getById($id)->toArray(false), true));
200                         }
201                     }
202
203                     $relations->delete($toDelete);
204                     $totalCount += count($toDelete);
205                 }
206             }
207         }
208
209         $message = 'Deleted ' . $totalCount . ' relations in total';
210         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
211             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
212         echo $message . "\n";
213     }
214
215     /**
216      * authentication
217      *
218      * @param string $_username
219      * @param string $_password
220      */
221     public function authenticate($_username, $_password)
222     {
223         $authResult = Tinebase_Auth::getInstance()->authenticate($_username, $_password);
224         
225         if ($authResult->isValid()) {
226             $accountsController = Tinebase_User::getInstance();
227             try {
228                 $account = $accountsController->getFullUserByLoginName($authResult->getIdentity());
229             } catch (Tinebase_Exception_NotFound $e) {
230                 echo 'account ' . $authResult->getIdentity() . ' not found in account storage'."\n";
231                 exit();
232             }
233             
234             Tinebase_Core::set('currentAccount', $account);
235
236             $ipAddress = '127.0.0.1';
237             $account->setLoginTime($ipAddress);
238
239             Tinebase_AccessLog::getInstance()->create(new Tinebase_Model_AccessLog(array(
240                 'sessionid'     => 'cli call',
241                 'login_name'    => $authResult->getIdentity(),
242                 'ip'            => $ipAddress,
243                 'li'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
244                 'lo'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
245                 'result'        => $authResult->getCode(),
246                 'account_id'    => Tinebase_Core::getUser()->getId(),
247                 'clienttype'    => 'TineCli',
248             )));
249             
250         } else {
251             echo "Wrong username and/or password.\n";
252             exit();
253         }
254     }
255     
256     /**
257      * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
258      *
259      * @param Zend_Console_Getopt $_opts
260      * @return boolean success
261      */
262     public function handle($_opts)
263     {
264         list($application, $method) = explode('.', $_opts->method);
265         $class = $application . '_Frontend_Cli';
266         
267         if (@class_exists($class)) {
268             $object = new $class;
269             if ($_opts->info) {
270                 $result = $object->getHelp();
271             } else if (method_exists($object, $method)) {
272                 $result = call_user_func(array($object, $method), $_opts);
273             } else {
274                 $result = FALSE;
275                 echo "Method $method not found.\n";
276             }
277         } else {
278             echo "Class $class does not exist.\n";
279             $result = FALSE;
280         }
281         
282         return $result;
283     }
284
285     /**
286      * trigger async events (for example via cronjob)
287      *
288      * @param Zend_Console_Getopt $_opts
289      * @return boolean success
290      */
291     public function triggerAsyncEvents($_opts)
292     {
293         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
294             . ' Triggering async events from CLI.');
295
296         $freeLock = $this->_aquireMultiServerLock(__CLASS__ . '::' . __FUNCTION__);
297         if (! $freeLock) {
298             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
299                 .' Job already running.');
300             return false;
301         }
302         
303         $userController = Tinebase_User::getInstance();
304         
305         // deactivate user plugins (like postfix/dovecot email backends) for async job user
306         $userController->unregisterAllPlugins();
307         
308         try {
309             $cronuser = $userController->getFullUserByLoginName($_opts->username);
310         } catch (Tinebase_Exception_NotFound $tenf) {
311             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
312         }
313         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
314         
315         $scheduler = Tinebase_Core::getScheduler();
316         $responses = $scheduler->run();
317         
318         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' ' . print_r(array_keys($responses), TRUE));
319         
320         $responseString = ($responses) ? implode(',', array_keys($responses)) : 'NULL';
321         echo "Tine 2.0 scheduler run (" . $responseString . ") complete.\n";
322         
323         return true;
324     }
325     
326     /**
327      * try to get user for cronjob from config
328      * 
329      * @return Tinebase_Model_FullUser
330      */
331     protected function _getCronuserFromConfigOrCreateOnTheFly()
332     {
333         try {
334             $cronuserId = Tinebase_Config::getInstance()->get(Tinebase_Config::CRONUSERID);
335             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Setting user with id ' . $cronuserId . ' as cronuser.');
336             $cronuser = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $cronuserId, 'Tinebase_Model_FullUser');
337         } catch (Tinebase_Exception_NotFound $tenf) {
338             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $tenf->getMessage());
339             
340             $cronuser = $this->_createCronuser();
341             Tinebase_Config::getInstance()->set(Tinebase_Config::CRONUSERID, $cronuser->getId());
342         }
343         
344         return $cronuser;
345     }
346     
347     /**
348      * create new cronuser
349      * 
350      * @return Tinebase_Model_FullUser
351      */
352     protected function _createCronuser()
353     {
354         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' Creating new cronuser.');
355         
356         $adminGroup = Tinebase_Group::getInstance()->getDefaultAdminGroup();
357         $cronuser = new Tinebase_Model_FullUser(array(
358             'accountLoginName'      => 'cronuser',
359             'accountStatus'         => Tinebase_Model_User::ACCOUNT_STATUS_DISABLED,
360             'visibility'            => Tinebase_Model_FullUser::VISIBILITY_HIDDEN,
361             'accountPrimaryGroup'   => $adminGroup->getId(),
362             'accountLastName'       => 'cronuser',
363             'accountDisplayName'    => 'cronuser',
364             'accountExpires'        => NULL,
365         ));
366         $cronuser = Tinebase_User::getInstance()->addUser($cronuser);
367         Tinebase_Group::getInstance()->addGroupMember($cronuser->accountPrimaryGroup, $cronuser->getId());
368         
369         return $cronuser;
370     }
371     
372     /**
373      * process given queue job
374      *  --message json encoded task
375      *
376      * @TODO rework user management, jobs should be executed as the right user in future
377      * 
378      * @param Zend_Console_Getopt $_opts
379      * @return boolean success
380      */
381     public function executeQueueJob($_opts)
382     {
383         try {
384             $cronuser = Tinebase_User::getInstance()->getFullUserByLoginName($_opts->username);
385         } catch (Tinebase_Exception_NotFound $tenf) {
386             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
387         }
388         
389         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
390         
391         $args = $_opts->getRemainingArgs();
392         $message = preg_replace('/^message=/', '', $args[0]);
393         
394         if (! $message) {
395             throw new Tinebase_Exception_InvalidArgument('mandatory parameter "message" is missing');
396         }
397         
398         Tinebase_ActionQueue::getInstance()->executeAction($message);
399         
400         return TRUE;
401     }
402     
403     /**
404      * clear table as defined in arguments
405      * can clear the following tables:
406      * - credential_cache
407      * - access_log
408      * - async_job
409      * - temp_files
410      * 
411      * if param date is given (date=2010-09-17), all records before this date are deleted (if the table has a date field)
412      * 
413      * @param $_opts
414      * @return boolean success
415      */
416     public function clearTable(Zend_Console_Getopt $_opts)
417     {
418         if (! $this->_checkAdminRight()) {
419             return FALSE;
420         }
421         
422         $args = $this->_parseArgs($_opts, array('tables'), 'tables');
423         $dateString = (isset($args['date']) || array_key_exists('date', $args)) ? $args['date'] : NULL;
424
425         $db = Tinebase_Core::getDb();
426         foreach ($args['tables'] as $table) {
427             switch ($table) {
428                 case 'access_log':
429                     $date = ($dateString) ? new Tinebase_DateTime($dateString) : NULL;
430                     Tinebase_AccessLog::getInstance()->clearTable($date);
431                     break;
432                 case 'async_job':
433                     $where = ($dateString) ? array(
434                         $db->quoteInto($db->quoteIdentifier('end_time') . ' < ?', $dateString)
435                     ) : array();
436                     $where[] = $db->quoteInto($db->quoteIdentifier('status') . ' < ?', 'success');
437                     
438                     echo "\nRemoving all successful async_job entries " . ($dateString ? "before $dateString " : "") . "...";
439                     $deleteCount = $db->delete(SQL_TABLE_PREFIX . $table, $where);
440                     echo "\nRemoved $deleteCount records.";
441                     break;
442                 case 'credential_cache':
443                     Tinebase_Auth_CredentialCache::getInstance()->clearCacheTable($dateString);
444                     break;
445                 case 'temp_files':
446                     Tinebase_TempFile::getInstance()->clearTableAndTempdir($dateString);
447                     break;
448                 default:
449                     echo 'Table ' . $table . " not supported or argument missing.\n";
450             }
451             echo "\nCleared table $table.";
452         }
453         echo "\n\n";
454         
455         return TRUE;
456     }
457     
458     /**
459      * purge deleted records
460      * 
461      * if param date is given (for example: date=2010-09-17), all records before this date are deleted (if the table has a date field)
462      * if table names are given, purge only records from this tables
463      * 
464      * @param $_opts
465      * @return boolean success
466      *
467      * TODO move purge logic to applications, purge Tinebase tables at the end
468      */
469     public function purgeDeletedRecords(Zend_Console_Getopt $_opts)
470     {
471         if (! $this->_checkAdminRight()) {
472             return FALSE;
473         }
474
475         $args = $this->_parseArgs($_opts, array(), 'tables');
476         $doEverything = false;
477
478         if (! (isset($args['tables']) || array_key_exists('tables', $args)) || empty($args['tables'])) {
479             echo "No tables given.\nPurging records from all tables!\n";
480             $args['tables'] = $this->_getAllApplicationTables();
481             $doEverything = true;
482         }
483         
484         $db = Tinebase_Core::getDb();
485         
486         if ((isset($args['date']) || array_key_exists('date', $args))) {
487             echo "\nRemoving all deleted entries before {$args['date']} ...";
488             $where = array(
489                 $db->quoteInto($db->quoteIdentifier('deleted_time') . ' < ?', $args['date'])
490             );
491         } else {
492             echo "\nRemoving all deleted entries ...";
493             $where = array();
494         }
495         $where[] = $db->quoteInto($db->quoteIdentifier('is_deleted') . ' = ?', 1);
496
497         $orderedTables = $this->_orderTables($args['tables']);
498         $this->_purgeTables($orderedTables, $where);
499
500         if ($doEverything) {
501             echo "\nCleaning relations...";
502             $this->cleanRelations();
503
504             echo "\nCleaning modlog...";
505             $this->cleanModlog();
506         }
507
508         echo "\n\n";
509         
510         return TRUE;
511     }
512     
513     /**
514      * get all app tables
515      * 
516      * @return array
517      */
518     protected function _getAllApplicationTables()
519     {
520         $result = array();
521         
522         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
523         foreach ($enabledApplications as $application) {
524             $result = array_merge($result, Tinebase_Application::getInstance()->getApplicationTables($application));
525         }
526         
527         return $result;
528     }
529
530     /**
531      * order tables for purging deleted records in a defined order
532      *
533      * @param array $tables
534      * @return array
535      *
536      * TODO could be improved by using usort
537      */
538     protected function _orderTables($tables)
539     {
540         // tags should be deleted first
541         // containers should be deleted last
542
543         $orderedTables = array();
544         $lastTables = array();
545         foreach($tables as $table) {
546             switch ($table) {
547                 case 'container':
548                     $lastTables[] = $table;
549                     break;
550                 case 'tags':
551                     array_unshift($orderedTables, $table);
552                     break;
553                 default:
554                     $orderedTables[] = $table;
555             }
556         }
557         $orderedTables = array_merge($orderedTables, $lastTables);
558
559         return $orderedTables;
560     }
561
562     /**
563      * purge tables
564      *
565      * @param $orderedTables
566      * @param $where
567      */
568     protected function _purgeTables($orderedTables, $where)
569     {
570         foreach ($orderedTables as $table) {
571             try {
572                 $schema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . $table);
573             } catch (Zend_Db_Statement_Exception $zdse) {
574                 echo "\nCould not get schema (" . $zdse->getMessage() . "). Skipping table $table";
575                 continue;
576             }
577             if (!(isset($schema['is_deleted']) || array_key_exists('is_deleted', $schema)) || !(isset($schema['deleted_time']) || array_key_exists('deleted_time', $schema))) {
578                 continue;
579             }
580
581             $deleteCount = 0;
582             try {
583                 $deleteCount = Tinebase_Core::getDb()->delete(SQL_TABLE_PREFIX . $table, $where);
584             } catch (Zend_Db_Statement_Exception $zdse) {
585                 echo "\nFailed to purge deleted records for table $table. " . $zdse->getMessage();
586             }
587             if ($deleteCount > 0) {
588                 echo "\nCleared table $table (deleted $deleteCount records).";
589             }
590             // TODO this should only be echoed with --verbose or written to the logs
591             else {
592                 echo "\nNothing to purge from $table";
593             }
594         }
595     }
596
597     /**
598      * add new customfield config
599      * 
600      * needs args like this:
601      * application="Addressbook" name="datefield" label="Date" model="Addressbook_Model_Contact" type="datefield"
602      * @see Tinebase_Model_CustomField_Config for full list 
603      * 
604      * @param $_opts
605      * @return boolean success
606      */
607     public function addCustomfield(Zend_Console_Getopt $_opts)
608     {
609         if (! $this->_checkAdminRight()) {
610             return FALSE;
611         }
612         
613         // parse args
614         $args = $_opts->getRemainingArgs();
615         $data = array();
616         foreach ($args as $idx => $arg) {
617             list($key, $value) = explode('=', $arg);
618             if ($key == 'application') {
619                 $key = 'application_id';
620                 $value = Tinebase_Application::getInstance()->getApplicationByName($value)->getId();
621             }
622             $data[$key] = $value;
623         }
624         
625         $customfieldConfig = new Tinebase_Model_CustomField_Config($data);
626         $cf = Tinebase_CustomField::getInstance()->addCustomField($customfieldConfig);
627
628         echo "\nCreated customfield: ";
629         print_r($cf->toArray());
630         echo "\n";
631         
632         return TRUE;
633     }
634     
635     /**
636      * nagios monitoring for tine 2.0 database connection
637      * 
638      * @return integer
639      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
640      */
641     public function monitoringCheckDB()
642     {
643         $message = 'DB CONNECTION FAIL';
644         try {
645             if (! Setup_Core::isRegistered(Setup_Core::CONFIG)) {
646                 Setup_Core::setupConfig();
647             }
648             if (! Setup_Core::isRegistered(Setup_Core::LOGGER)) {
649                 Setup_Core::setupLogger();
650             }
651             $time_start = microtime(true);
652             $dbcheck = Setup_Core::setupDatabaseConnection();
653             $time = (microtime(true) - $time_start) * 1000;
654         } catch (Exception $e) {
655             $message .= ': ' . $e->getMessage();
656             $dbcheck = FALSE;
657         }
658         
659         if ($dbcheck) {
660             echo "DB CONNECTION OK | connecttime={$time}ms;;;;\n";
661             return 0;
662         } 
663         
664         echo $message . "\n";
665         return 2;
666     }
667     
668     /**
669      * nagios monitoring for tine 2.0 config file
670      * 
671      * @return integer
672      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
673      */
674     public function monitoringCheckConfig()
675     {
676         $message = 'CONFIG FAIL';
677         $configcheck = FALSE;
678         
679         $configfile = Setup_Core::getConfigFilePath();
680         if ($configfile) {
681             $configfile = escapeshellcmd($configfile);
682             if (preg_match('/^win/i', PHP_OS)) {
683                 exec("php -l $configfile 2> NUL", $error, $code);
684             } else {
685                 exec("php -l $configfile 2> /dev/null", $error, $code);
686             }
687             if ($code == 0) {
688                 $configcheck = TRUE;
689             } else {
690                 $message .= ': CONFIG FILE SYNTAX ERROR';
691             }
692         } else {
693             $message .= ': CONFIG FILE MISSING';
694         }
695         
696         if ($configcheck) {
697             echo "CONFIG FILE OK\n";
698             return 0;
699         } else {
700             echo $message . "\n";
701             return 2;
702         }
703     }
704     
705     /**
706     * nagios monitoring for tine 2.0 async cronjob run
707     *
708     * @return integer
709     * 
710     * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
711     * @see 0008038: monitoringCheckCron -> check if cron did run in the last hour
712     */
713     public function monitoringCheckCron()
714     {
715         $message = 'CRON FAIL';
716
717         try {
718             $lastJob = Tinebase_AsyncJob::getInstance()->getLastJob('Tinebase_Event_Async_Minutely');
719             
720             if ($lastJob === NULL) {
721                 $message .= ': NO LAST JOB FOUND';
722                 $result = 1;
723             } else {
724                 if ($lastJob->end_time instanceof Tinebase_DateTime) {
725                     $duration = $lastJob->end_time->getTimestamp() - $lastJob->start_time->getTimestamp();
726                     $valueString = ' | duration=' . $duration . 's;;;;';
727                     $valueString .= ' end=' . $lastJob->end_time->getIso() . ';;;;';
728                 } else {
729                     $valueString = '';
730                 }
731                 
732                 if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_RUNNING && Tinebase_DateTime::now()->isLater($lastJob->end_time)) {
733                     $message .= ': LAST JOB TOOK TOO LONG';
734                     $result = 1;
735                 } else if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_FAILURE) {
736                     $message .= ': LAST JOB FAILED';
737                     $result = 1;
738                 } else if (Tinebase_DateTime::now()->isLater($lastJob->start_time->addHour(1))) {
739                     $message .= ': NO JOB IN THE LAST HOUR';
740                     $result = 1;
741                 } else {
742                     $message = 'CRON OK';
743                     $result = 0;
744                 }
745                 $message .= $valueString;
746             }
747         } catch (Exception $e) {
748             $message .= ': ' . $e->getMessage();
749             $result = 2;
750         }
751         
752         echo $message . "\n";
753         return $result;
754     }
755     
756     /**
757      * nagios monitoring for tine 2.0 logins during the last 5 mins
758      * 
759      * @return number
760      * 
761      * @todo allow to configure timeslot
762      */
763     public function monitoringLoginNumber()
764     {
765         $message = 'LOGINS';
766         $result  = 0;
767         
768         try {
769             $filter = new Tinebase_Model_AccessLogFilter(array(
770                 array('field' => 'li', 'operator' => 'after', 'value' => Tinebase_DateTime::now()->subMinute(5))
771             ));
772             $accesslogs = Tinebase_AccessLog::getInstance()->search($filter, NULL, FALSE, TRUE);
773             $valueString = ' | count=' . count($accesslogs) . ';;;;';
774             $message .= ' OK' . $valueString;
775         } catch (Exception $e) {
776             $message .= ' FAIL: ' . $e->getMessage();
777             $result = 2;
778         }
779         
780         echo $message . "\n";
781         return $result;
782     }
783
784     /**
785      * nagios monitoring for tine 2.0 active users
786      *
787      * @return number
788      *
789      * @todo allow to configure timeslot / currently the active users of the last month are returned
790      */
791     public function monitoringActiveUsers()
792     {
793         $message = 'ACTIVE USERS';
794         $result  = 0;
795
796         try {
797             $userCount = Tinebase_User::getInstance()->getActiveUserCount();
798             $valueString = ' | count=' . $userCount . ';;;;';
799             $message .= ' OK' . $valueString;
800         } catch (Exception $e) {
801             $message .= ' FAIL: ' . $e->getMessage();
802             $result = 2;
803         }
804
805         echo $message . "\n";
806         return $result;
807     }
808
809     /**
810      * undo changes to records defined by certain criteria (user, date, fields, ...)
811      * 
812      * example: $ php tine20.php --username pschuele --method Tinebase.undo -d 
813      *   -- record_type=Addressbook_Model_Contact modification_time=2013-05-08 modification_account=3263
814      * 
815      * @param Zend_Console_Getopt $opts
816      */
817     public function undo(Zend_Console_Getopt $opts)
818     {
819         if (! $this->_checkAdminRight()) {
820             return FALSE;
821         }
822         
823         $data = $this->_parseArgs($opts, array('modification_time'));
824         
825         // build filter from params
826         $filterData = array();
827         $allowedFilters = array(
828             'record_type',
829             'modification_time',
830             'modification_account',
831             'record_id',
832             'modified_attribute'
833         );
834         foreach ($data as $key => $value) {
835             if (in_array($key, $allowedFilters)) {
836                 $operator = ($key === 'modification_time') ? 'within' : 'equals';
837                 $filterData[] = array('field' => $key, 'operator' => $operator, 'value' => $value);
838             }
839         }
840         $filter = new Tinebase_Model_ModificationLogFilter($filterData);
841         
842         $dryrun = $opts->d;
843         $overwrite = (isset($data['overwrite']) && $data['overwrite']) ? TRUE : FALSE;
844         $result = Tinebase_Timemachine_ModificationLog::getInstance()->undo($filter, $overwrite, $dryrun);
845         
846         if (! $dryrun) {
847             echo 'Reverted ' . $result['totalcount'] . " change(s)\n";
848         } else {
849             echo "Dry run\n";
850             echo 'Would revert ' . $result['totalcount'] . " change(s):\n";
851             foreach ($result['undoneModlogs'] as $modlog) {
852                 echo 'id ' . $modlog->record_id . ' [' . $modlog->modified_attribute . ']: ' . $modlog->new_value . ' -> ' . $modlog->old_value . "\n";
853             }
854         }
855         echo 'Failcount: ' . $result['failcount'] . "\n";
856         return 0;
857     }
858     
859     /**
860      * creates demo data for all applications
861      * accepts same arguments as Tinebase_Frontend_Cli_Abstract::createDemoData
862      * and the additional argument "skipAdmin" to force no user/group/role creation
863      * 
864      * @param Zend_Console_Getopt $_opts
865      */
866     public function createAllDemoData($_opts)
867     {
868         if (! $this->_checkAdminRight()) {
869             return FALSE;
870         }
871         
872         // fetch all applications and check if required are installed, otherwise remove app from array
873         $applications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
874         foreach($applications as $appName) {
875             echo 'Searching for DemoData in application "' . $appName . '"...' . PHP_EOL;
876             $className = $appName.'_Setup_DemoData';
877             if (class_exists($className)) {
878                 echo 'DemoData in application "' . $appName . '" found!' . PHP_EOL;
879                 $required = $className::getRequiredApplications();
880                 foreach($required as $requiredApplication) {
881                     if (! Tinebase_Helper::in_array_case($applications, $requiredApplication)) {
882                         echo 'Creating DemoData for Application ' . $appName . ' is impossible, because application "' . $requiredApplication . '" is not installed.' . PHP_EOL;
883                         continue 2;
884                     }
885                 }
886                 $this->_applicationsToWorkOn[$appName] = array('appName' => $appName, 'required' => $required);
887             } else {
888                 echo 'DemoData in application "' . $appName . '" not found.' . PHP_EOL . PHP_EOL;
889             }
890         }
891         unset($applications);
892         
893         foreach($this->_applicationsToWorkOn as $app => $cfg) {
894             $this->_createDemoDataRecursive($app, $cfg, $_opts);
895         }
896
897         return 0;
898     }
899     
900     /**
901      * creates demo data and calls itself if there are required apps
902      * 
903      * @param string $app
904      * @param array $cfg
905      * @param Zend_Console_Getopt $opts
906      */
907     protected function _createDemoDataRecursive($app, $cfg, $opts)
908     {
909         if (isset($cfg['required']) && is_array($cfg['required'])) {
910             foreach($cfg['required'] as $requiredApp) {
911                 $this->_createDemoDataRecursive($requiredApp, $this->_applicationsToWorkOn[$requiredApp], $opts);
912             }
913         }
914         
915         $className = $app . '_Frontend_Cli';
916         
917         $classNameDD = $app . '_Setup_DemoData';
918         
919         if (class_exists($className)) {
920             if (! $classNameDD::hasBeenRun()) {
921                 echo 'Creating DemoData in application "' . $app . '"...' . PHP_EOL;
922                 $class = new $className();
923                 $class->createDemoData($opts, FALSE);
924             } else {
925                 echo 'DemoData for ' . $app . ' has been run already, skipping...' . PHP_EOL;
926             }
927         } else {
928             echo 'Could not found ' . $className . ', so DemoData for application "' . $app . '" could not be created!';
929         }
930     }
931     
932     /**
933      * clears deleted files from filesystem + database
934      * @return boolean
935      */
936     public function clearDeletedFiles()
937     {
938         if (! $this->_checkAdminRight()) {
939             return FALSE;
940         }
941         
942         $this->_addOutputLogWriter();
943         
944         Tinebase_FileSystem::getInstance()->clearDeletedFiles();
945
946         return 0;
947     }
948     
949     /**
950      * repair a table
951      * 
952      * @param Zend_Console_Getopt $opts
953      * 
954      * @todo add more tables
955      */
956     public function repairTable($opts)
957     {
958         if (! $this->_checkAdminRight()) {
959             return FALSE;
960         }
961         
962         $this->_addOutputLogWriter();
963         
964         $data = $this->_parseArgs($opts, array('table'));
965         
966         switch ($data['table']) {
967             case 'importexport_definition':
968                 Tinebase_ImportExportDefinition::getInstance()->repairTable();
969                 $result = 0;
970                 break;
971             default:
972                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
973                     . ' No repair script found for ' . $data['table']);
974                 $result = 1;
975         }
976         
977         exit($result);
978     }
979     
980     /**
981      * repairs container names
982      * 
983      * @param Zend_Console_Getopt $opts
984      */
985     public function repairContainerName($opts)
986     {
987         if (! $this->_checkAdminRight()) {
988             return FALSE;
989         }
990         $dryrun = $opts->d;
991         
992         $this->_addOutputLogWriter();
993         $args = $this->_parseArgs($opts);
994         
995         $containersWithBadNames = Tinebase_Container::getInstance()->getContainersWithBadNames();
996         
997         $locale = Tinebase_Translation::getLocale((isset($args['locale']) ?$args['locale'] : 'auto'));
998
999         if ($dryrun) {
1000             print_r($containersWithBadNames->toArray());
1001             echo "Using Locale " . $locale . "\n";
1002         }
1003         
1004         $appContainerNames = array(
1005             'Calendar' => 'calendar',
1006             'Tasks'    => 'tasks',
1007             'Addressbook'    => 'addressbook',
1008         );
1009         
1010         foreach ($containersWithBadNames as $container) {
1011             if (empty($container->owner_id)) {
1012                 if ($dryrun) {
1013                     echo "Don't rename shared container " . $container->id . "\n";
1014                 }
1015                 continue;
1016             }
1017             $app = Tinebase_Application::getInstance()->getApplicationById($container->application_id);
1018             $appContainerName = isset($appContainerNames[$app->name]) ? $appContainerNames[$app->name] : "container";
1019             $translation = Tinebase_Translation::getTranslation($app->name, $locale);
1020             $account = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $container->owner_id);
1021             $newName = $newBaseName = sprintf($translation->_("%s's personal " . $appContainerName), $account->accountFullName);
1022             
1023             $count = 1;
1024             do {
1025                 try {
1026                     Tinebase_Container::getInstance()->getContainerByName($app->name, $newName, Tinebase_Model_Container::TYPE_PERSONAL, $container->owner_id);
1027                     $found = true;
1028                     $newName = $newBaseName . ' ' . ++$count;
1029                 } catch (Tinebase_Exception_NotFound $tenf) {
1030                     $found = false;
1031                 }
1032                 
1033             } while ($found);
1034             if ($dryrun) {
1035                 echo "Rename container id " . $container->id . ' to ' . $newName . "\n";
1036             } else {
1037                 
1038                 $container->name = $newName;
1039                 Tinebase_Container::getInstance()->update($container);
1040             }
1041         }
1042         
1043         $result = 0;
1044         exit($result);
1045     }
1046     
1047     /**
1048      * transfer relations
1049      * 
1050      * @param Zend_Console_Getopt $opts
1051      */
1052     public function transferRelations($opts)
1053     {
1054         if (! $this->_checkAdminRight()) {
1055             return FALSE;
1056         }
1057         
1058         $this->_addOutputLogWriter();
1059         
1060         try {
1061             $args = $this->_parseArgs($opts, array('oldId', 'newId', 'model'));
1062         } catch (Tinebase_Exception_InvalidArgument $e) {
1063             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1064                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Parameters "oldId", "newId" and "model" are required!');
1065             }
1066             exit(1);
1067         }
1068         
1069         $skippedEntries = Tinebase_Relations::getInstance()->transferRelations($args['oldId'], $args['newId'], $args['model']);
1070
1071         if (! empty($skippedEntries) && Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1072             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . count($skippedEntries) . ' entries has been skipped:');
1073         }
1074         
1075         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1076             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' The operation has been terminated successfully.');
1077         }
1078
1079         return 0;
1080     }
1081
1082     /**
1083      * repair function for persistent filters (favorites) without grants: this adds default grants for those filters.
1084      *
1085      * @return int
1086      */
1087     public function setDefaultGrantsOfPersistentFilters()
1088     {
1089         if (! $this->_checkAdminRight()) {
1090             return -1;
1091         }
1092
1093         $this->_addOutputLogWriter(6);
1094
1095         // get all persistent filters without grants
1096         // TODO this could be enhanced by allowing to set default grants for other filters, too
1097         Tinebase_PersistentFilter::getInstance()->doContainerACLChecks(false);
1098         $filters = Tinebase_PersistentFilter::getInstance()->search(new Tinebase_Model_PersistentFilterFilter(array(),'', array('ignoreAcl' => true)));
1099         $filtersWithoutGrants = 0;
1100
1101         foreach ($filters as $filter) {
1102             if (count($filter->grants) == 0) {
1103                 // update to set default grants
1104                 $filter = Tinebase_PersistentFilter::getInstance()->update($filter);
1105                 $filtersWithoutGrants++;
1106
1107                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1108                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1109                         . ' Updated filter: ' . print_r($filter->toArray(), true));
1110                 }
1111             }
1112         }
1113
1114         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1115             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1116                 . ' Set default grants for ' . $filtersWithoutGrants . ' filters'
1117                 . ' (checked ' . count($filters) . ' in total).');
1118         }
1119
1120         return 0;
1121     }
1122 }