add optional download attachment url property to ics
[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      * forces containers that support sync token to resync via WebDAV sync tokens
109      *
110      * this will cause 2 BadRequest responses to sync token requests
111      * the first one as soon as the client notices that something changed and sends a sync token request
112      * eventually the client receives a false sync token (as we increased content sequence, but we dont have a content history entry)
113      * eventually not (if something really changed in the calendar in the meantime)
114      *
115      * in case the client got a fake sync token, the clients next sync token request (once something really changed) will fail again
116      * after something really changed valid sync tokens will be handed out again
117      *
118      * @param Zend_Console_Getopt $_opts
119      */
120     public function forceSyncTokenResync($_opts)
121     {
122         $args = $this->_parseArgs($_opts, array());
123
124         if (isset($args['containerIds'])) {
125             $resultStr = '';
126
127             if (!is_array($args['containerIds'])) {
128                 $args['containerIds'] = array($args['containerIds']);
129             }
130
131             $db = Tinebase_Core::getDb();
132
133             $container = Tinebase_Container::getInstance();
134             $contentBackend = $container->getContentBackend();
135             foreach($args['containerIds'] as $id) {
136                 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
137
138                 $containerData = $container->get($id);
139                 $recordsBackend = Tinebase_Core::getApplicationInstance($containerData->model)->getBackend();
140                 $recordsBackend->increaseSeqsForContainerId($id);
141
142                 $container->increaseContentSequence($id);
143                 $resultStr .= ($resultStr!==''?', ':'') . $id . '(' . $contentBackend->deleteByProperty($id, 'container_id') . ')';
144
145                 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
146             }
147
148             echo "\nDeleted containers(num content history records): " . $resultStr . "\n";
149         }
150     }
151
152     /**
153      * clean timemachine_modlog for records that have been pruned (not deleted!)
154      */
155     public function cleanModlog()
156     {
157         if (! $this->_checkAdminRight()) {
158             return FALSE;
159         }
160
161         $deleted = Tinebase_Timemachine_ModificationLog::getInstance()->clean();
162
163         echo "\ndeleted $deleted modlogs records\n";
164     }
165
166     /**
167      * clean relations, set relation to deleted if at least one of the ends has been set to deleted or pruned
168      */
169     public function cleanRelations()
170     {
171         if (! $this->_checkAdminRight()) {
172             return FALSE;
173         }
174
175         $relations = Tinebase_Relations::getInstance();
176         $filter = new Tinebase_Model_Filter_FilterGroup();
177         $pagination = new Tinebase_Model_Pagination();
178         $pagination->limit = 10000;
179         $pagination->sort = 'id';
180
181         $totalCount = 0;
182         $date = Tinebase_DateTime::now()->subYear(1);
183
184         while ( ($recordSet = $relations->search($filter, $pagination)) && $recordSet->count() > 0 ) {
185             $filter = new Tinebase_Model_Filter_FilterGroup();
186             $pagination->start += $pagination->limit;
187             $models = array();
188
189             foreach($recordSet as $relation) {
190                 $models[$relation->own_model][$relation->own_id][] = $relation->id;
191                 $models[$relation->related_model][$relation->related_id][] = $relation->id;
192             }
193             foreach ($models as $model => &$ids) {
194                 $doAll = false;
195
196                 try {
197                     $app = Tinebase_Core::getApplicationInstance($model, '', true);
198                 } catch (Tinebase_Exception_NotFound $tenf) {
199                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
200                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' no application found for it');
201                     $doAll = true;
202                 }
203                 if (!$doAll) {
204                     if ($app instanceof Tinebase_Container)
205                     {
206                         $backend = $app;
207                     } else {
208                         if (!$app instanceof Tinebase_Controller_Record_Abstract) {
209                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
210                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' controller: ' . get_class($app) . ' not an instance of Tinebase_Controller_Record_Abstract');
211                             continue;
212                         }
213
214                         $backend = $app->getBackend();
215                     }
216                     if (!$backend instanceof Tinebase_Backend_Interface) {
217                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
218                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' backend: ' . get_class($backend) . ' not an instance of Tinebase_Backend_Interface');
219                         continue;
220                     }
221                     $record = new $model(null, true);
222
223                     $modelFilter = $model . 'Filter';
224                     $idFilter = new $modelFilter(array(), '', array('ignoreAcl' => true));
225                     $idFilter->addFilter(new Tinebase_Model_Filter_Id(array(
226                         'field' => $record->getIdProperty(), 'operator' => 'in', 'value' => array_keys($ids)
227                     )));
228
229
230                     $existingIds = $backend->search($idFilter, null, true);
231
232                     if (!is_array($existingIds)) {
233                         throw new Exception('search for model: ' . $model . ' returned not an array!');
234                     }
235                     foreach ($existingIds as $id) {
236                         unset($ids[$id]);
237                     }
238                 }
239
240                 if ( count($ids) > 0 ) {
241                     $toDelete = array();
242                     foreach ($ids as $idArrays) {
243                         foreach ($idArrays as $id) {
244                             $toDelete[$id] = true;
245                         }
246                     }
247
248                     $toDelete = array_keys($toDelete);
249
250                     foreach($toDelete as $id) {
251                         if ( $recordSet->getById($id)->creation_time && $recordSet->getById($id)->creation_time->isLater($date) ) {
252                             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));
253                         }
254                     }
255
256                     $relations->delete($toDelete);
257                     $totalCount += count($toDelete);
258                 }
259             }
260         }
261
262         $message = 'Deleted ' . $totalCount . ' relations in total';
263         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
264             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
265         echo $message . "\n";
266     }
267
268     /**
269      * authentication
270      *
271      * @param string $_username
272      * @param string $_password
273      */
274     public function authenticate($_username, $_password)
275     {
276         $authResult = Tinebase_Auth::getInstance()->authenticate($_username, $_password);
277         
278         if ($authResult->isValid()) {
279             $accountsController = Tinebase_User::getInstance();
280             try {
281                 $account = $accountsController->getFullUserByLoginName($authResult->getIdentity());
282             } catch (Tinebase_Exception_NotFound $e) {
283                 echo 'account ' . $authResult->getIdentity() . ' not found in account storage'."\n";
284                 exit();
285             }
286             
287             Tinebase_Core::set('currentAccount', $account);
288
289             $ipAddress = '127.0.0.1';
290             $account->setLoginTime($ipAddress);
291
292             Tinebase_AccessLog::getInstance()->create(new Tinebase_Model_AccessLog(array(
293                 'sessionid'     => 'cli call',
294                 'login_name'    => $authResult->getIdentity(),
295                 'ip'            => $ipAddress,
296                 'li'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
297                 'lo'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
298                 'result'        => $authResult->getCode(),
299                 'account_id'    => Tinebase_Core::getUser()->getId(),
300                 'clienttype'    => 'TineCli',
301             )));
302             
303         } else {
304             echo "Wrong username and/or password.\n";
305             exit();
306         }
307     }
308     
309     /**
310      * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
311      *
312      * @param Zend_Console_Getopt $_opts
313      * @return boolean success
314      */
315     public function handle($_opts)
316     {
317         list($application, $method) = explode('.', $_opts->method);
318         $class = $application . '_Frontend_Cli';
319         
320         if (@class_exists($class)) {
321             $object = new $class;
322             if ($_opts->info) {
323                 $result = $object->getHelp();
324             } else if (method_exists($object, $method)) {
325                 $result = call_user_func(array($object, $method), $_opts);
326             } else {
327                 $result = FALSE;
328                 echo "Method $method not found.\n";
329             }
330         } else {
331             echo "Class $class does not exist.\n";
332             $result = FALSE;
333         }
334         
335         return $result;
336     }
337
338     /**
339      * trigger async events (for example via cronjob)
340      *
341      * @param Zend_Console_Getopt $_opts
342      * @return boolean success
343      */
344     public function triggerAsyncEvents($_opts)
345     {
346         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
347             . ' Triggering async events from CLI.');
348
349         $freeLock = $this->_aquireMultiServerLock(__CLASS__ . '::' . __FUNCTION__);
350         if (! $freeLock) {
351             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
352                 .' Job already running.');
353             return false;
354         }
355         
356         $userController = Tinebase_User::getInstance();
357
358         // deactivate user plugins (like postfix/dovecot email backends) for async job user
359         $userController->unregisterAllPlugins();
360
361         try {
362             $cronuser = $userController->getFullUserByLoginName($_opts->username);
363         } catch (Tinebase_Exception_NotFound $tenf) {
364             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
365         }
366         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
367         
368         $scheduler = Tinebase_Core::getScheduler();
369         $responses = $scheduler->run();
370         
371         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' ' . print_r(array_keys($responses), TRUE));
372         
373         $responseString = ($responses) ? implode(',', array_keys($responses)) : 'NULL';
374         echo "Tine 2.0 scheduler run (" . $responseString . ") complete.\n";
375         
376         return true;
377     }
378
379     /**
380      * process given queue job
381      *  --message json encoded task
382      *
383      * @TODO rework user management, jobs should be executed as the right user in future
384      * 
385      * @param Zend_Console_Getopt $_opts
386      * @return boolean success
387      */
388     public function executeQueueJob($_opts)
389     {
390         try {
391             $cronuser = Tinebase_User::getInstance()->getFullUserByLoginName($_opts->username);
392         } catch (Tinebase_Exception_NotFound $tenf) {
393             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
394         }
395         
396         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
397         
398         $args = $_opts->getRemainingArgs();
399         $message = preg_replace('/^message=/', '', $args[0]);
400         
401         if (! $message) {
402             throw new Tinebase_Exception_InvalidArgument('mandatory parameter "message" is missing');
403         }
404         
405         Tinebase_ActionQueue::getInstance()->executeAction($message);
406         
407         return TRUE;
408     }
409     
410     /**
411      * clear table as defined in arguments
412      * can clear the following tables:
413      * - credential_cache
414      * - access_log
415      * - async_job
416      * - temp_files
417      * 
418      * if param date is given (date=2010-09-17), all records before this date are deleted (if the table has a date field)
419      * 
420      * @param $_opts
421      * @return boolean success
422      */
423     public function clearTable(Zend_Console_Getopt $_opts)
424     {
425         if (! $this->_checkAdminRight()) {
426             return FALSE;
427         }
428         
429         $args = $this->_parseArgs($_opts, array('tables'), 'tables');
430         $dateString = (isset($args['date']) || array_key_exists('date', $args)) ? $args['date'] : NULL;
431
432         $db = Tinebase_Core::getDb();
433         foreach ($args['tables'] as $table) {
434             switch ($table) {
435                 case 'access_log':
436                     $date = ($dateString) ? new Tinebase_DateTime($dateString) : NULL;
437                     Tinebase_AccessLog::getInstance()->clearTable($date);
438                     break;
439                 case 'async_job':
440                     $where = ($dateString) ? array(
441                         $db->quoteInto($db->quoteIdentifier('end_time') . ' < ?', $dateString)
442                     ) : array();
443                     $where[] = $db->quoteInto($db->quoteIdentifier('status') . ' < ?', 'success');
444                     
445                     echo "\nRemoving all successful async_job entries " . ($dateString ? "before $dateString " : "") . "...";
446                     $deleteCount = $db->delete(SQL_TABLE_PREFIX . $table, $where);
447                     echo "\nRemoved $deleteCount records.";
448                     break;
449                 case 'credential_cache':
450                     Tinebase_Auth_CredentialCache::getInstance()->clearCacheTable($dateString);
451                     break;
452                 case 'temp_files':
453                     Tinebase_TempFile::getInstance()->clearTableAndTempdir($dateString);
454                     break;
455                 default:
456                     echo 'Table ' . $table . " not supported or argument missing.\n";
457             }
458             echo "\nCleared table $table.";
459         }
460         echo "\n\n";
461         
462         return TRUE;
463     }
464     
465     /**
466      * purge deleted records
467      * 
468      * 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)
469      * if table names are given, purge only records from this tables
470      * 
471      * @param $_opts
472      * @return boolean success
473      *
474      * TODO move purge logic to applications, purge Tinebase tables at the end
475      */
476     public function purgeDeletedRecords(Zend_Console_Getopt $_opts)
477     {
478         if (! $this->_checkAdminRight()) {
479             return FALSE;
480         }
481
482         $args = $this->_parseArgs($_opts, array(), 'tables');
483         $doEverything = false;
484
485         if (! (isset($args['tables']) || array_key_exists('tables', $args)) || empty($args['tables'])) {
486             echo "No tables given.\nPurging records from all tables!\n";
487             $args['tables'] = $this->_getAllApplicationTables();
488             $doEverything = true;
489         }
490         
491         $db = Tinebase_Core::getDb();
492         
493         if ((isset($args['date']) || array_key_exists('date', $args))) {
494             echo "\nRemoving all deleted entries before {$args['date']} ...";
495             $where = array(
496                 $db->quoteInto($db->quoteIdentifier('deleted_time') . ' < ?', $args['date'])
497             );
498         } else {
499             echo "\nRemoving all deleted entries ...";
500             $where = array();
501         }
502         $where[] = $db->quoteInto($db->quoteIdentifier('is_deleted') . ' = ?', 1);
503
504         $orderedTables = $this->_orderTables($args['tables']);
505         $this->_purgeTables($orderedTables, $where);
506
507         if ($doEverything) {
508             echo "\nCleaning relations...";
509             $this->cleanRelations();
510
511             echo "\nCleaning modlog...";
512             $this->cleanModlog();
513
514             echo "\nCleaning customfields...";
515             $this->cleanCustomfields();
516
517             echo "\nCleaning notes...";
518             $this->cleanNotes();
519         }
520
521         echo "\n\n";
522         
523         return TRUE;
524     }
525
526     /**
527      * cleanNotes: removes notes of records that have been deleted
528      */
529     public function cleanNotes()
530     {
531         if (! $this->_checkAdminRight()) {
532             return FALSE;
533         }
534
535         $notesController = Tinebase_Notes::getInstance();
536         $notes = $notesController->getAllNotes();
537         $controllers = array();
538         $models = array();
539         $deleteIds = array();
540
541         /** @var Tinebase_Model_Note $note */
542         foreach ($notes as $note) {
543             if (!isset($controllers[$note->record_model])) {
544                 if (strpos($note->record_model, 'Tinebase') === 0) {
545                     continue;
546                 }
547                 try {
548                     $controllers[$note->record_model] = Tinebase_Core::getApplicationInstance($note->record_model);
549                 } catch(Tinebase_Exception_AccessDenied $e) {
550                     // TODO log
551                     continue;
552                 } catch(Tinebase_Exception_NotFound $tenf) {
553                     $deleteIds[] = $note->getId();
554                     continue;
555                 }
556                 $oldACLCheckValue = $controllers[$note->record_model]->doContainerACLChecks(false);
557                 $models[$note->record_model] = array(
558                     0 => new $note->record_model(),
559                     1 => ($note->record_model !== 'Filemanager_Model_Node' ? class_exists($note->record_model . 'Filter') : false),
560                     2 => $note->record_model . 'Filter',
561                     3 => $oldACLCheckValue
562                 );
563             }
564             $controller = $controllers[$note->record_model];
565             $model = $models[$note->record_model];
566
567             if ($model[1]) {
568                 $filter = new $model[2](array(
569                     array('field' => $model[0]->getIdProperty(), 'operator' => 'equals', 'value' => $note->record_id)
570                 ));
571                 if ($model[0]->has('is_deleted')) {
572                     $filter->addFilter(new Tinebase_Model_Filter_Int(array('field' => 'is_deleted', 'operator' => 'notnull', 'value' => NULL)));
573                 }
574                 $result = $controller->searchCount($filter);
575
576                 if (is_bool($result) || (is_string($result) && $result === ((string)intval($result)))) {
577                     $result = (int)$result;
578                 }
579
580                 if (!is_int($result)) {
581                     if (is_array($result) && isset($result['totalcount'])) {
582                         $result = (int)$result['totalcount'];
583                     } elseif(is_array($result) && isset($result['count'])) {
584                         $result = (int)$result['count'];
585                     } else {
586                         // todo log
587                         // dummy line, remove!
588                         $result = 1;
589                     }
590                 }
591
592                 if ($result === 0) {
593                     $deleteIds[] = $note->getId();
594                 }
595             } else {
596                 try {
597                     $controller->get($note->record_id, null, false, true);
598                 } catch(Tinebase_Exception_NotFound $tenf) {
599                     $deleteIds[] = $note->getId();
600                 }
601             }
602         }
603
604         if (count($deleteIds) > 0) {
605             $notesController->purgeNotes($deleteIds);
606         }
607
608         foreach($controllers as $model => $controller) {
609             $controller->doContainerACLChecks($models[$model][3]);
610         }
611
612         echo "\ndeleted " . count($deleteIds) . " notes\n";
613     }
614
615     /**
616      * cleanCustomfields
617      */
618     public function cleanCustomfields()
619     {
620         if (! $this->_checkAdminRight()) {
621             return FALSE;
622         }
623
624         $customFieldController = Tinebase_CustomField::getInstance();
625         $customFieldConfigs = $customFieldController->searchConfig();
626         $deleteCount = 0;
627
628         /** @var Tinebase_Model_CustomField_Config $customFieldConfig */
629         foreach($customFieldConfigs as $customFieldConfig) {
630             $deleteAll = false;
631             try {
632                 $controller = Tinebase_Core::getApplicationInstance($customFieldConfig->model);
633
634                 $oldACLCheckValue = $controller->doContainerACLChecks(false);
635                 if ($customFieldConfig->model !== 'Filemanager_Model_Node') {
636                     $filterClass = $customFieldConfig->model . 'Filter';
637                 } else {
638                     $filterClass = 'ClassThatDoesNotExist';
639                 }
640             } catch(Tinebase_Exception_AccessDenied $e) {
641                 // TODO log
642                 continue;
643             } catch(Tinebase_Exception_NotFound $tenf) {
644                 $deleteAll = true;
645             }
646
647
648
649             $filter = new Tinebase_Model_CustomField_ValueFilter(array(
650                 array('field' => 'customfield_id', 'operator' => 'equals', 'value' => $customFieldConfig->id)
651             ));
652             $customFieldValues = $customFieldController->search($filter);
653             $deleteIds = array();
654
655             if (true === $deleteAll) {
656                 $deleteIds = $customFieldValues->getId();
657             } elseif (class_exists($filterClass)) {
658                 $model = new $customFieldConfig->model();
659                 /** @var Tinebase_Model_CustomField_Value $customFieldValue */
660                 foreach ($customFieldValues as $customFieldValue) {
661                     $filter = new $filterClass(array(
662                         array('field' => $model->getIdProperty(), 'operator' => 'equals', 'value' => $customFieldValue->record_id)
663                     ));
664                     if ($model->has('is_deleted')) {
665                         $filter->addFilter(new Tinebase_Model_Filter_Int(array('field' => 'is_deleted', 'operator' => 'notnull', 'value' => NULL)));
666                     }
667
668                     $result = $controller->searchCount($filter);
669
670                     if (is_bool($result) || (is_string($result) && $result === ((string)intval($result)))) {
671                         $result = (int)$result;
672                     }
673
674                     if (!is_int($result)) {
675                         if (is_array($result) && isset($result['totalcount'])) {
676                             $result = (int)$result['totalcount'];
677                         } elseif(is_array($result) && isset($result['count'])) {
678                             $result = (int)$result['count'];
679                         } else {
680                             // todo log
681                             // dummy line, remove!
682                             $result = 1;
683                         }
684                     }
685
686                     if ($result === 0) {
687                         $deleteIds[] = $customFieldValue->getId();
688                     }
689                 }
690             } else {
691                 /** @var Tinebase_Model_CustomField_Value $customFieldValue */
692                 foreach ($customFieldValues as $customFieldValue) {
693                     try {
694                         $controller->get($customFieldValue->record_id, null, false, true);
695                     } catch(Tinebase_Exception_NotFound $tenf) {
696                         $deleteIds[] = $customFieldValue->getId();
697                     }
698                 }
699             }
700
701             if (count($deleteIds) > 0) {
702                 $customFieldController->deleteCustomFieldValue($deleteIds);
703                 $deleteCount += count($deleteIds);
704             }
705
706             if (true !== $deleteAll) {
707                 $controller->doContainerACLChecks($oldACLCheckValue);
708             }
709         }
710
711         echo "\ndeleted " . $deleteCount . " customfield values\n";
712     }
713     
714     /**
715      * get all app tables
716      * 
717      * @return array
718      */
719     protected function _getAllApplicationTables()
720     {
721         $result = array();
722         
723         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
724         foreach ($enabledApplications as $application) {
725             $result = array_merge($result, Tinebase_Application::getInstance()->getApplicationTables($application));
726         }
727         
728         return $result;
729     }
730
731     /**
732      * order tables for purging deleted records in a defined order
733      *
734      * @param array $tables
735      * @return array
736      *
737      * TODO could be improved by using usort
738      */
739     protected function _orderTables($tables)
740     {
741         // tags should be deleted first
742         // containers should be deleted last
743
744         $orderedTables = array();
745         $lastTables = array();
746         foreach($tables as $table) {
747             switch ($table) {
748                 case 'container':
749                     $lastTables[] = $table;
750                     break;
751                 case 'tags':
752                     array_unshift($orderedTables, $table);
753                     break;
754                 default:
755                     $orderedTables[] = $table;
756             }
757         }
758         $orderedTables = array_merge($orderedTables, $lastTables);
759
760         return $orderedTables;
761     }
762
763     /**
764      * purge tables
765      *
766      * @param $orderedTables
767      * @param $where
768      */
769     protected function _purgeTables($orderedTables, $where)
770     {
771         foreach ($orderedTables as $table) {
772             try {
773                 $schema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . $table);
774             } catch (Zend_Db_Statement_Exception $zdse) {
775                 echo "\nCould not get schema (" . $zdse->getMessage() . "). Skipping table $table";
776                 continue;
777             }
778             if (!(isset($schema['is_deleted']) || array_key_exists('is_deleted', $schema)) || !(isset($schema['deleted_time']) || array_key_exists('deleted_time', $schema))) {
779                 continue;
780             }
781
782             $deleteCount = 0;
783             try {
784                 $deleteCount = Tinebase_Core::getDb()->delete(SQL_TABLE_PREFIX . $table, $where);
785             } catch (Zend_Db_Statement_Exception $zdse) {
786                 echo "\nFailed to purge deleted records for table $table. " . $zdse->getMessage();
787             }
788             if ($deleteCount > 0) {
789                 echo "\nCleared table $table (deleted $deleteCount records).";
790             }
791             // TODO this should only be echoed with --verbose or written to the logs
792             else {
793                 echo "\nNothing to purge from $table";
794             }
795         }
796     }
797
798     /**
799      * add new customfield config
800      *
801      * needs args like this:
802      * application="Addressbook" name="datefield" label="Date" model="Addressbook_Model_Contact" type="datefield"
803      * @see Tinebase_Model_CustomField_Config for full list
804      *
805      * @param $_opts
806      * @return boolean success
807      */
808     public function addCustomfield(Zend_Console_Getopt $_opts)
809     {
810         if (! $this->_checkAdminRight()) {
811             return FALSE;
812         }
813         
814         // parse args
815         $args = $_opts->getRemainingArgs();
816         $data = array();
817         foreach ($args as $idx => $arg) {
818             list($key, $value) = explode('=', $arg);
819             if ($key == 'application') {
820                 $key = 'application_id';
821                 $value = Tinebase_Application::getInstance()->getApplicationByName($value)->getId();
822             }
823             $data[$key] = $value;
824         }
825         
826         $customfieldConfig = new Tinebase_Model_CustomField_Config($data);
827         $cf = Tinebase_CustomField::getInstance()->addCustomField($customfieldConfig);
828
829         echo "\nCreated customfield: ";
830         print_r($cf->toArray());
831         echo "\n";
832         
833         return TRUE;
834     }
835     
836     /**
837      * nagios monitoring for tine 2.0 database connection
838      * 
839      * @return integer
840      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
841      */
842     public function monitoringCheckDB()
843     {
844         $message = 'DB CONNECTION FAIL';
845         try {
846             if (! Setup_Core::isRegistered(Setup_Core::CONFIG)) {
847                 Setup_Core::setupConfig();
848             }
849             if (! Setup_Core::isRegistered(Setup_Core::LOGGER)) {
850                 Setup_Core::setupLogger();
851             }
852             $time_start = microtime(true);
853             $dbcheck = Setup_Core::setupDatabaseConnection();
854             $time = (microtime(true) - $time_start) * 1000;
855         } catch (Exception $e) {
856             $message .= ': ' . $e->getMessage();
857             $dbcheck = FALSE;
858         }
859         
860         if ($dbcheck) {
861             echo "DB CONNECTION OK | connecttime={$time}ms;;;;\n";
862             return 0;
863         } 
864         
865         echo $message . "\n";
866         return 2;
867     }
868     
869     /**
870      * nagios monitoring for tine 2.0 config file
871      * 
872      * @return integer
873      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
874      */
875     public function monitoringCheckConfig()
876     {
877         $message = 'CONFIG FAIL';
878         $configcheck = FALSE;
879         
880         $configfile = Setup_Core::getConfigFilePath();
881         if ($configfile) {
882             $configfile = escapeshellcmd($configfile);
883             if (preg_match('/^win/i', PHP_OS)) {
884                 exec("php -l $configfile 2> NUL", $error, $code);
885             } else {
886                 exec("php -l $configfile 2> /dev/null", $error, $code);
887             }
888             if ($code == 0) {
889                 $configcheck = TRUE;
890             } else {
891                 $message .= ': CONFIG FILE SYNTAX ERROR';
892             }
893         } else {
894             $message .= ': CONFIG FILE MISSING';
895         }
896         
897         if ($configcheck) {
898             echo "CONFIG FILE OK\n";
899             return 0;
900         } else {
901             echo $message . "\n";
902             return 2;
903         }
904     }
905     
906     /**
907     * nagios monitoring for tine 2.0 async cronjob run
908     *
909     * @return integer
910     * 
911     * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
912     * @see 0008038: monitoringCheckCron -> check if cron did run in the last hour
913     */
914     public function monitoringCheckCron()
915     {
916         $message = 'CRON FAIL';
917
918         try {
919             $lastJob = Tinebase_AsyncJob::getInstance()->getLastJob('Tinebase_Event_Async_Minutely');
920             
921             if ($lastJob === NULL) {
922                 $message .= ': NO LAST JOB FOUND';
923                 $result = 1;
924             } else {
925                 if ($lastJob->end_time instanceof Tinebase_DateTime) {
926                     $duration = $lastJob->end_time->getTimestamp() - $lastJob->start_time->getTimestamp();
927                     $valueString = ' | duration=' . $duration . 's;;;;';
928                     $valueString .= ' end=' . $lastJob->end_time->getIso() . ';;;;';
929                 } else {
930                     $valueString = '';
931                 }
932                 
933                 if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_RUNNING && Tinebase_DateTime::now()->isLater($lastJob->end_time)) {
934                     $message .= ': LAST JOB TOOK TOO LONG';
935                     $result = 1;
936                 } else if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_FAILURE) {
937                     $message .= ': LAST JOB FAILED';
938                     $result = 1;
939                 } else if (Tinebase_DateTime::now()->isLater($lastJob->start_time->addHour(1))) {
940                     $message .= ': NO JOB IN THE LAST HOUR';
941                     $result = 1;
942                 } else {
943                     $message = 'CRON OK';
944                     $result = 0;
945                 }
946                 $message .= $valueString;
947             }
948         } catch (Exception $e) {
949             $message .= ': ' . $e->getMessage();
950             $result = 2;
951         }
952         
953         echo $message . "\n";
954         return $result;
955     }
956     
957     /**
958      * nagios monitoring for tine 2.0 logins during the last 5 mins
959      * 
960      * @return number
961      * 
962      * @todo allow to configure timeslot
963      */
964     public function monitoringLoginNumber()
965     {
966         $message = 'LOGINS';
967         $result  = 0;
968         
969         try {
970             $filter = new Tinebase_Model_AccessLogFilter(array(
971                 array('field' => 'li', 'operator' => 'after', 'value' => Tinebase_DateTime::now()->subMinute(5))
972             ));
973             $accesslogs = Tinebase_AccessLog::getInstance()->search($filter, NULL, FALSE, TRUE);
974             $valueString = ' | count=' . count($accesslogs) . ';;;;';
975             $message .= ' OK' . $valueString;
976         } catch (Exception $e) {
977             $message .= ' FAIL: ' . $e->getMessage();
978             $result = 2;
979         }
980         
981         echo $message . "\n";
982         return $result;
983     }
984
985     /**
986      * nagios monitoring for tine 2.0 active users
987      *
988      * @return number
989      *
990      * @todo allow to configure timeslot / currently the active users of the last month are returned
991      */
992     public function monitoringActiveUsers()
993     {
994         $message = 'ACTIVE USERS';
995         $result  = 0;
996
997         try {
998             $userCount = Tinebase_User::getInstance()->getActiveUserCount();
999             $valueString = ' | count=' . $userCount . ';;;;';
1000             $message .= ' OK' . $valueString;
1001         } catch (Exception $e) {
1002             $message .= ' FAIL: ' . $e->getMessage();
1003             $result = 2;
1004         }
1005
1006         echo $message . "\n";
1007         return $result;
1008     }
1009
1010     /**
1011      * undo changes to records defined by certain criteria (user, date, fields, ...)
1012      * 
1013      * example: $ php tine20.php --username pschuele --method Tinebase.undo -d 
1014      *   -- record_type=Addressbook_Model_Contact modification_time=2013-05-08 modification_account=3263
1015      * 
1016      * @param Zend_Console_Getopt $opts
1017      */
1018     public function undo(Zend_Console_Getopt $opts)
1019     {
1020         if (! $this->_checkAdminRight()) {
1021             return FALSE;
1022         }
1023         
1024         $data = $this->_parseArgs($opts, array('modification_time'));
1025         
1026         // build filter from params
1027         $filterData = array();
1028         $allowedFilters = array(
1029             'record_type',
1030             'modification_time',
1031             'modification_account',
1032             'record_id',
1033             'modified_attribute'
1034         );
1035         foreach ($data as $key => $value) {
1036             if (in_array($key, $allowedFilters)) {
1037                 $operator = ($key === 'modification_time') ? 'within' : 'equals';
1038                 $filterData[] = array('field' => $key, 'operator' => $operator, 'value' => $value);
1039             }
1040         }
1041         $filter = new Tinebase_Model_ModificationLogFilter($filterData);
1042         
1043         $dryrun = $opts->d;
1044         $overwrite = (isset($data['overwrite']) && $data['overwrite']) ? TRUE : FALSE;
1045         $result = Tinebase_Timemachine_ModificationLog::getInstance()->undo($filter, $overwrite, $dryrun);
1046         
1047         if (! $dryrun) {
1048             echo 'Reverted ' . $result['totalcount'] . " change(s)\n";
1049         } else {
1050             echo "Dry run\n";
1051             echo 'Would revert ' . $result['totalcount'] . " change(s):\n";
1052             foreach ($result['undoneModlogs'] as $modlog) {
1053                 echo 'id ' . $modlog->record_id . ' [' . $modlog->modified_attribute . ']: ' . $modlog->new_value . ' -> ' . $modlog->old_value . "\n";
1054             }
1055         }
1056         echo 'Failcount: ' . $result['failcount'] . "\n";
1057         return 0;
1058     }
1059     
1060     /**
1061      * creates demo data for all applications
1062      * accepts same arguments as Tinebase_Frontend_Cli_Abstract::createDemoData
1063      * and the additional argument "skipAdmin" to force no user/group/role creation
1064      * 
1065      * @param Zend_Console_Getopt $_opts
1066      */
1067     public function createAllDemoData($_opts)
1068     {
1069         if (! $this->_checkAdminRight()) {
1070             return FALSE;
1071         }
1072         
1073         // fetch all applications and check if required are installed, otherwise remove app from array
1074         $applications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
1075         foreach($applications as $appName) {
1076             echo 'Searching for DemoData in application "' . $appName . '"...' . PHP_EOL;
1077             $className = $appName.'_Setup_DemoData';
1078             if (class_exists($className)) {
1079                 echo 'DemoData in application "' . $appName . '" found!' . PHP_EOL;
1080                 $required = $className::getRequiredApplications();
1081                 foreach($required as $requiredApplication) {
1082                     if (! Tinebase_Helper::in_array_case($applications, $requiredApplication)) {
1083                         echo 'Creating DemoData for Application ' . $appName . ' is impossible, because application "' . $requiredApplication . '" is not installed.' . PHP_EOL;
1084                         continue 2;
1085                     }
1086                 }
1087                 $this->_applicationsToWorkOn[$appName] = array('appName' => $appName, 'required' => $required);
1088             } else {
1089                 echo 'DemoData in application "' . $appName . '" not found.' . PHP_EOL . PHP_EOL;
1090             }
1091         }
1092         unset($applications);
1093         
1094         foreach($this->_applicationsToWorkOn as $app => $cfg) {
1095             $this->_createDemoDataRecursive($app, $cfg, $_opts);
1096         }
1097
1098         return 0;
1099     }
1100     
1101     /**
1102      * creates demo data and calls itself if there are required apps
1103      * 
1104      * @param string $app
1105      * @param array $cfg
1106      * @param Zend_Console_Getopt $opts
1107      */
1108     protected function _createDemoDataRecursive($app, $cfg, $opts)
1109     {
1110         if (isset($cfg['required']) && is_array($cfg['required'])) {
1111             foreach($cfg['required'] as $requiredApp) {
1112                 $this->_createDemoDataRecursive($requiredApp, $this->_applicationsToWorkOn[$requiredApp], $opts);
1113             }
1114         }
1115         
1116         $className = $app . '_Frontend_Cli';
1117         
1118         $classNameDD = $app . '_Setup_DemoData';
1119         
1120         if (class_exists($className)) {
1121             if (! $classNameDD::hasBeenRun()) {
1122                 echo 'Creating DemoData in application "' . $app . '"...' . PHP_EOL;
1123                 $class = new $className();
1124                 $class->createDemoData($opts, FALSE);
1125             } else {
1126                 echo 'DemoData for ' . $app . ' has been run already, skipping...' . PHP_EOL;
1127             }
1128         } else {
1129             echo 'Could not found ' . $className . ', so DemoData for application "' . $app . '" could not be created!';
1130         }
1131     }
1132     
1133     /**
1134      * clears deleted files from filesystem + database
1135      * @return boolean
1136      */
1137     public function clearDeletedFiles()
1138     {
1139         if (! $this->_checkAdminRight()) {
1140             return FALSE;
1141         }
1142         
1143         $this->_addOutputLogWriter();
1144         
1145         Tinebase_FileSystem::getInstance()->clearDeletedFiles();
1146
1147         return 0;
1148     }
1149
1150     /**
1151      * repair a table
1152      * 
1153      * @param Zend_Console_Getopt $opts
1154      * 
1155      * @todo add more tables
1156      */
1157     public function repairTable($opts)
1158     {
1159         if (! $this->_checkAdminRight()) {
1160             return FALSE;
1161         }
1162         
1163         $this->_addOutputLogWriter();
1164         
1165         $data = $this->_parseArgs($opts, array('table'));
1166         
1167         switch ($data['table']) {
1168             case 'importexport_definition':
1169                 Tinebase_ImportExportDefinition::getInstance()->repairTable();
1170                 $result = 0;
1171                 break;
1172             default:
1173                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1174                     . ' No repair script found for ' . $data['table']);
1175                 $result = 1;
1176         }
1177         
1178         exit($result);
1179     }
1180     
1181     /**
1182      * repairs container names
1183      * 
1184      * @param Zend_Console_Getopt $opts
1185      */
1186     public function repairContainerName($opts)
1187     {
1188         if (! $this->_checkAdminRight()) {
1189             return FALSE;
1190         }
1191         $dryrun = $opts->d;
1192         
1193         $this->_addOutputLogWriter();
1194         $args = $this->_parseArgs($opts);
1195         
1196         $containersWithBadNames = Tinebase_Container::getInstance()->getContainersWithBadNames();
1197         
1198         $locale = Tinebase_Translation::getLocale((isset($args['locale']) ?$args['locale'] : 'auto'));
1199
1200         if ($dryrun) {
1201             print_r($containersWithBadNames->toArray());
1202             echo "Using Locale " . $locale . "\n";
1203         }
1204         
1205         $appContainerNames = array(
1206             'Calendar' => 'calendar',
1207             'Tasks'    => 'tasks',
1208             'Addressbook'    => 'addressbook',
1209         );
1210         
1211         foreach ($containersWithBadNames as $container) {
1212             if (empty($container->owner_id)) {
1213                 if ($dryrun) {
1214                     echo "Don't rename shared container " . $container->id . "\n";
1215                 }
1216                 continue;
1217             }
1218             $app = Tinebase_Application::getInstance()->getApplicationById($container->application_id);
1219             $appContainerName = isset($appContainerNames[$app->name]) ? $appContainerNames[$app->name] : "container";
1220             $translation = Tinebase_Translation::getTranslation($app->name, $locale);
1221             $account = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $container->owner_id);
1222             $newName = $newBaseName = sprintf($translation->_("%s's personal " . $appContainerName), $account->accountFullName);
1223             
1224             $count = 1;
1225             do {
1226                 try {
1227                     Tinebase_Container::getInstance()->getContainerByName($app->name, $newName, Tinebase_Model_Container::TYPE_PERSONAL, $container->owner_id);
1228                     $found = true;
1229                     $newName = $newBaseName . ' ' . ++$count;
1230                 } catch (Tinebase_Exception_NotFound $tenf) {
1231                     $found = false;
1232                 }
1233                 
1234             } while ($found);
1235             if ($dryrun) {
1236                 echo "Rename container id " . $container->id . ' to ' . $newName . "\n";
1237             } else {
1238                 
1239                 $container->name = $newName;
1240                 Tinebase_Container::getInstance()->update($container);
1241             }
1242         }
1243         
1244         $result = 0;
1245         exit($result);
1246     }
1247     
1248     /**
1249      * transfer relations
1250      * 
1251      * @param Zend_Console_Getopt $opts
1252      */
1253     public function transferRelations($opts)
1254     {
1255         if (! $this->_checkAdminRight()) {
1256             return FALSE;
1257         }
1258         
1259         $this->_addOutputLogWriter();
1260         
1261         try {
1262             $args = $this->_parseArgs($opts, array('oldId', 'newId', 'model'));
1263         } catch (Tinebase_Exception_InvalidArgument $e) {
1264             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1265                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Parameters "oldId", "newId" and "model" are required!');
1266             }
1267             exit(1);
1268         }
1269         
1270         $skippedEntries = Tinebase_Relations::getInstance()->transferRelations($args['oldId'], $args['newId'], $args['model']);
1271
1272         if (! empty($skippedEntries) && Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1273             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . count($skippedEntries) . ' entries has been skipped:');
1274         }
1275         
1276         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1277             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' The operation has been terminated successfully.');
1278         }
1279
1280         return 0;
1281     }
1282
1283     /**
1284      * repair function for persistent filters (favorites) without grants: this adds default grants for those filters.
1285      *
1286      * @return int
1287      */
1288     public function setDefaultGrantsOfPersistentFilters()
1289     {
1290         if (! $this->_checkAdminRight()) {
1291             return -1;
1292         }
1293
1294         $this->_addOutputLogWriter(6);
1295
1296         // get all persistent filters without grants
1297         // TODO this could be enhanced by allowing to set default grants for other filters, too
1298         Tinebase_PersistentFilter::getInstance()->doContainerACLChecks(false);
1299         $filters = Tinebase_PersistentFilter::getInstance()->search(new Tinebase_Model_PersistentFilterFilter(array(),'', array('ignoreAcl' => true)));
1300         $filtersWithoutGrants = 0;
1301
1302         foreach ($filters as $filter) {
1303             if (count($filter->grants) == 0) {
1304                 // update to set default grants
1305                 $filter = Tinebase_PersistentFilter::getInstance()->update($filter);
1306                 $filtersWithoutGrants++;
1307
1308                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1309                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1310                         . ' Updated filter: ' . print_r($filter->toArray(), true));
1311                 }
1312             }
1313         }
1314
1315         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1316             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1317                 . ' Set default grants for ' . $filtersWithoutGrants . ' filters'
1318                 . ' (checked ' . count($filters) . ' in total).');
1319         }
1320
1321         return 0;
1322     }
1323
1324     /**
1325      *
1326      *
1327      * @return int
1328      */
1329     public function repairContainerOwner()
1330     {
1331         if (! $this->_checkAdminRight()) {
1332             return 2;
1333         }
1334
1335         $this->_addOutputLogWriter(6);
1336         Tinebase_Container::getInstance()->setContainerOwners();
1337
1338         return 0;
1339     }
1340
1341     /**
1342      * show user report (number of enabled, disabled, ... users)
1343      *
1344      * TODO add system user count
1345      * TODO use twig?
1346      */
1347     public function userReport()
1348     {
1349         if (! $this->_checkAdminRight()) {
1350             return 2;
1351         }
1352
1353         $translation = Tinebase_Translation::getTranslation('Tinebase');
1354
1355         $userStatus = array(
1356             'total' => array(),
1357             Tinebase_Model_User::ACCOUNT_STATUS_ENABLED => array(/* 'showUserNames' => true, 'showClients' => true */),
1358             Tinebase_Model_User::ACCOUNT_STATUS_DISABLED => array(),
1359             Tinebase_Model_User::ACCOUNT_STATUS_BLOCKED => array(),
1360             Tinebase_Model_User::ACCOUNT_STATUS_EXPIRED => array(),
1361             //'system' => array(),
1362             'lastmonth' => array('lastMonths' => 1, 'showUserNames' => true, 'showClients' => true),
1363             'last 3 months' => array('lastMonths' => 3),
1364         );
1365
1366         foreach ($userStatus as $status => $options) {
1367             switch ($status) {
1368                 case 'lastmonth':
1369                 case 'last 3 months':
1370                     $userCount = Tinebase_User::getInstance()->getActiveUserCount($options['lastMonths']);
1371                     $text = $translation->_("Number of distinct users") . " (" . $status . "): " . $userCount . "\n";
1372                     break;
1373                 case 'system':
1374                     $text = "TODO add me\n";
1375                     break;
1376                 default:
1377                     $userCount = Tinebase_User::getInstance()->getUserCount($status);
1378                     $text = $translation->_("Number of users") . " (" . $status . "): " . $userCount . "\n";
1379             }
1380             echo $text;
1381
1382             if (isset($options['showUserNames']) && $options['showUserNames']
1383                 && in_array($status, array('lastmonth', 'last 3 months'))
1384                 && isset($options['lastMonths'])
1385             ) {
1386                 // TODO allow this for other status
1387                 echo $translation->_("  User Accounts:\n");
1388                 $userIds = Tinebase_User::getInstance()->getActiveUserIds($options['lastMonths']);
1389                 foreach ($userIds as $userId) {
1390                     $user = Tinebase_User::getInstance()->getUserByProperty('accountId', $userId, 'Tinebase_Model_FullUser');
1391                     echo "  * " . $user->accountLoginName . ' / ' . $user->accountDisplayName . "\n";
1392                     if (isset($options['showClients']) && $options['showClients']) {
1393                         $userClients = Tinebase_AccessLog::getInstance()->getUserClients($user, $options['lastMonths']);
1394                         echo "    Clients: \n";
1395                         foreach ($userClients as $client) {
1396                             echo "     - $client\n";
1397                         }
1398                     }
1399                 }
1400             }
1401             echo "\n";
1402         }
1403
1404         return 0;
1405     }
1406 }