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