7c411e51fe533e7af14fe233cfdbcab970abe7ab
[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-2017 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 increaseReplicationMasterId($opts)
40     {
41         if (!$this->_checkAdminRight()) {
42             return -1;
43         }
44
45         Tinebase_Timemachine_ModificationLog::getInstance()->increaseReplicationMasterId();
46
47         return true;
48     }
49
50     /**
51      * @param Zend_Console_Getopt $_opts
52      * @return boolean success
53      */
54     public function readModifictionLogFromMaster($opts)
55     {
56         if (!$this->_checkAdminRight()) {
57             return -1;
58         }
59
60         Tinebase_Timemachine_ModificationLog::getInstance()->readModificationLogFromMaster();
61
62         return true;
63     }
64
65     /**
66      * rebuildPaths
67      *
68     * @param Zend_Console_Getopt $_opts
69     * @return integer success
70     */
71     public function rebuildPaths($opts)
72     {
73         if (! $this->_checkAdminRight()) {
74             return -1;
75         }
76
77         $result = Tinebase_Controller::getInstance()->rebuildPaths();
78
79         return $result ? true : -1;
80     }
81
82     /**
83      * forces containers that support sync token to resync via WebDAV sync tokens
84      *
85      * this will cause 2 BadRequest responses to sync token requests
86      * the first one as soon as the client notices that something changed and sends a sync token request
87      * eventually the client receives a false sync token (as we increased content sequence, but we dont have a content history entry)
88      * eventually not (if something really changed in the calendar in the meantime)
89      *
90      * in case the client got a fake sync token, the clients next sync token request (once something really changed) will fail again
91      * after something really changed valid sync tokens will be handed out again
92      *
93      * @param Zend_Console_Getopt $_opts
94      */
95     public function forceSyncTokenResync($_opts)
96     {
97         $args = $this->_parseArgs($_opts, array());
98
99         if (isset($args['containerIds'])) {
100             $resultStr = '';
101
102             if (!is_array($args['containerIds'])) {
103                 $args['containerIds'] = array($args['containerIds']);
104             }
105
106             $db = Tinebase_Core::getDb();
107
108             $container = Tinebase_Container::getInstance();
109             $contentBackend = $container->getContentBackend();
110             foreach($args['containerIds'] as $id) {
111                 $transactionId = Tinebase_TransactionManager::getInstance()->startTransaction($db);
112
113                 $container->increaseContentSequence($id);
114                 $resultStr .= ($resultStr!==''?', ':'') . $id . '(' . $contentBackend->deleteByProperty($id, 'container_id') . ')';
115
116                 Tinebase_TransactionManager::getInstance()->commitTransaction($transactionId);
117             }
118
119             echo "\nDeleted containers(num content history records): " . $resultStr . "\n";
120         }
121     }
122
123     /**
124      * clean timemachine_modlog for records that have been pruned (not deleted!)
125      */
126     public function cleanModlog()
127     {
128         if (! $this->_checkAdminRight()) {
129             return FALSE;
130         }
131
132         $deleted = Tinebase_Timemachine_ModificationLog::getInstance()->clean();
133
134         echo "\ndeleted $deleted modlogs records\n";
135     }
136
137     /**
138      * clean relations, set relation to deleted if at least one of the ends has been set to deleted or pruned
139      */
140     public function cleanRelations()
141     {
142         if (! $this->_checkAdminRight()) {
143             return FALSE;
144         }
145
146         $relations = Tinebase_Relations::getInstance();
147         $filter = new Tinebase_Model_Filter_FilterGroup();
148         $pagination = new Tinebase_Model_Pagination();
149         $pagination->limit = 10000;
150         $pagination->sort = 'id';
151
152         $totalCount = 0;
153         $date = Tinebase_DateTime::now()->subYear(1);
154
155         while ( ($recordSet = $relations->search($filter, $pagination)) && $recordSet->count() > 0 ) {
156             $filter = new Tinebase_Model_Filter_FilterGroup();
157             $pagination->start += $pagination->limit;
158             $models = array();
159
160             foreach($recordSet as $relation) {
161                 $models[$relation->own_model][$relation->own_id][] = $relation->id;
162                 $models[$relation->related_model][$relation->related_id][] = $relation->id;
163             }
164             foreach ($models as $model => &$ids) {
165                 $doAll = false;
166
167                 try {
168                     $app = Tinebase_Core::getApplicationInstance($model, '', true);
169                 } catch (Tinebase_Exception_NotFound $tenf) {
170                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
171                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' no application found for it');
172                     $doAll = true;
173                 }
174                 if (!$doAll) {
175                     if ($app instanceof Tinebase_Container)
176                     {
177                         $backend = $app;
178                     } else {
179                         if (!$app instanceof Tinebase_Controller_Record_Abstract) {
180                             if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
181                                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' controller: ' . get_class($app) . ' not an instance of Tinebase_Controller_Record_Abstract');
182                             continue;
183                         }
184
185                         $backend = $app->getBackend();
186                     }
187                     if (!$backend instanceof Tinebase_Backend_Interface) {
188                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
189                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' model: ' . $model . ' backend: ' . get_class($backend) . ' not an instance of Tinebase_Backend_Interface');
190                         continue;
191                     }
192                     $record = new $model(null, true);
193
194                     $modelFilter = $model . 'Filter';
195                     $idFilter = new $modelFilter(array(), '', array('ignoreAcl' => true));
196                     $idFilter->addFilter(new Tinebase_Model_Filter_Id(array(
197                         'field' => $record->getIdProperty(), 'operator' => 'in', 'value' => array_keys($ids)
198                     )));
199
200
201                     $existingIds = $backend->search($idFilter, null, true);
202
203                     if (!is_array($existingIds)) {
204                         throw new Exception('search for model: ' . $model . ' returned not an array!');
205                     }
206                     foreach ($existingIds as $id) {
207                         unset($ids[$id]);
208                     }
209                 }
210
211                 if ( count($ids) > 0 ) {
212                     $toDelete = array();
213                     foreach ($ids as $idArrays) {
214                         foreach ($idArrays as $id) {
215                             $toDelete[$id] = true;
216                         }
217                     }
218
219                     $toDelete = array_keys($toDelete);
220
221                     foreach($toDelete as $id) {
222                         if ( $recordSet->getById($id)->creation_time && $recordSet->getById($id)->creation_time->isLater($date) ) {
223                             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));
224                         }
225                     }
226
227                     $relations->delete($toDelete);
228                     $totalCount += count($toDelete);
229                 }
230             }
231         }
232
233         $message = 'Deleted ' . $totalCount . ' relations in total';
234         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
235             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $message);
236         echo $message . "\n";
237     }
238
239     /**
240      * authentication
241      *
242      * @param string $_username
243      * @param string $_password
244      */
245     public function authenticate($_username, $_password)
246     {
247         $authResult = Tinebase_Auth::getInstance()->authenticate($_username, $_password);
248         
249         if ($authResult->isValid()) {
250             $accountsController = Tinebase_User::getInstance();
251             try {
252                 $account = $accountsController->getFullUserByLoginName($authResult->getIdentity());
253             } catch (Tinebase_Exception_NotFound $e) {
254                 echo 'account ' . $authResult->getIdentity() . ' not found in account storage'."\n";
255                 exit();
256             }
257             
258             Tinebase_Core::set('currentAccount', $account);
259
260             $ipAddress = '127.0.0.1';
261             $account->setLoginTime($ipAddress);
262
263             Tinebase_AccessLog::getInstance()->create(new Tinebase_Model_AccessLog(array(
264                 'sessionid'     => 'cli call',
265                 'login_name'    => $authResult->getIdentity(),
266                 'ip'            => $ipAddress,
267                 'li'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
268                 'lo'            => Tinebase_DateTime::now()->get(Tinebase_Record_Abstract::ISO8601LONG),
269                 'result'        => $authResult->getCode(),
270                 'account_id'    => Tinebase_Core::getUser()->getId(),
271                 'clienttype'    => 'TineCli',
272             )));
273             
274         } else {
275             echo "Wrong username and/or password.\n";
276             exit();
277         }
278     }
279     
280     /**
281      * handle request (call -ApplicationName-_Cli.-MethodName- or -ApplicationName-_Cli.getHelp)
282      *
283      * @param Zend_Console_Getopt $_opts
284      * @return boolean success
285      */
286     public function handle($_opts)
287     {
288         list($application, $method) = explode('.', $_opts->method);
289         $class = $application . '_Frontend_Cli';
290         
291         if (@class_exists($class)) {
292             $object = new $class;
293             if ($_opts->info) {
294                 $result = $object->getHelp();
295             } else if (method_exists($object, $method)) {
296                 $result = call_user_func(array($object, $method), $_opts);
297             } else {
298                 $result = FALSE;
299                 echo "Method $method not found.\n";
300             }
301         } else {
302             echo "Class $class does not exist.\n";
303             $result = FALSE;
304         }
305         
306         return $result;
307     }
308
309     /**
310      * trigger async events (for example via cronjob)
311      *
312      * @param Zend_Console_Getopt $_opts
313      * @return boolean success
314      */
315     public function triggerAsyncEvents($_opts)
316     {
317         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
318             . ' Triggering async events from CLI.');
319
320         $freeLock = $this->_aquireMultiServerLock(__CLASS__ . '::' . __FUNCTION__);
321         if (! $freeLock) {
322             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
323                 .' Job already running.');
324             return false;
325         }
326         
327         $userController = Tinebase_User::getInstance();
328
329         try {
330             $cronuser = $userController->getFullUserByLoginName($_opts->username);
331         } catch (Tinebase_Exception_NotFound $tenf) {
332             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
333         }
334         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
335         
336         $scheduler = Tinebase_Core::getScheduler();
337         $responses = $scheduler->run();
338         
339         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .' ' . print_r(array_keys($responses), TRUE));
340         
341         $responseString = ($responses) ? implode(',', array_keys($responses)) : 'NULL';
342         echo "Tine 2.0 scheduler run (" . $responseString . ") complete.\n";
343         
344         return true;
345     }
346
347     /**
348      * process given queue job
349      *  --message json encoded task
350      *
351      * @TODO rework user management, jobs should be executed as the right user in future
352      * 
353      * @param Zend_Console_Getopt $_opts
354      * @return boolean success
355      */
356     public function executeQueueJob($_opts)
357     {
358         try {
359             $cronuser = Tinebase_User::getInstance()->getFullUserByLoginName($_opts->username);
360         } catch (Tinebase_Exception_NotFound $tenf) {
361             $cronuser = $this->_getCronuserFromConfigOrCreateOnTheFly();
362         }
363         
364         Tinebase_Core::set(Tinebase_Core::USER, $cronuser);
365         
366         $args = $_opts->getRemainingArgs();
367         $message = preg_replace('/^message=/', '', $args[0]);
368         
369         if (! $message) {
370             throw new Tinebase_Exception_InvalidArgument('mandatory parameter "message" is missing');
371         }
372         
373         Tinebase_ActionQueue::getInstance()->executeAction($message);
374         
375         return TRUE;
376     }
377     
378     /**
379      * clear table as defined in arguments
380      * can clear the following tables:
381      * - credential_cache
382      * - access_log
383      * - async_job
384      * - temp_files
385      * 
386      * if param date is given (date=2010-09-17), all records before this date are deleted (if the table has a date field)
387      * 
388      * @param $_opts
389      * @return boolean success
390      */
391     public function clearTable(Zend_Console_Getopt $_opts)
392     {
393         if (! $this->_checkAdminRight()) {
394             return FALSE;
395         }
396         
397         $args = $this->_parseArgs($_opts, array('tables'), 'tables');
398         $dateString = (isset($args['date']) || array_key_exists('date', $args)) ? $args['date'] : NULL;
399
400         $db = Tinebase_Core::getDb();
401         foreach ($args['tables'] as $table) {
402             switch ($table) {
403                 case 'access_log':
404                     $date = ($dateString) ? new Tinebase_DateTime($dateString) : NULL;
405                     Tinebase_AccessLog::getInstance()->clearTable($date);
406                     break;
407                 case 'async_job':
408                     $where = ($dateString) ? array(
409                         $db->quoteInto($db->quoteIdentifier('end_time') . ' < ?', $dateString)
410                     ) : array();
411                     $where[] = $db->quoteInto($db->quoteIdentifier('status') . ' < ?', 'success');
412                     
413                     echo "\nRemoving all successful async_job entries " . ($dateString ? "before $dateString " : "") . "...";
414                     $deleteCount = $db->delete(SQL_TABLE_PREFIX . $table, $where);
415                     echo "\nRemoved $deleteCount records.";
416                     break;
417                 case 'credential_cache':
418                     Tinebase_Auth_CredentialCache::getInstance()->clearCacheTable($dateString);
419                     break;
420                 case 'temp_files':
421                     Tinebase_TempFile::getInstance()->clearTableAndTempdir($dateString);
422                     break;
423                 default:
424                     echo 'Table ' . $table . " not supported or argument missing.\n";
425             }
426             echo "\nCleared table $table.";
427         }
428         echo "\n\n";
429         
430         return TRUE;
431     }
432     
433     /**
434      * purge deleted records
435      * 
436      * 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)
437      * if table names are given, purge only records from this tables
438      * 
439      * @param $_opts
440      * @return boolean success
441      *
442      * TODO move purge logic to applications, purge Tinebase tables at the end
443      */
444     public function purgeDeletedRecords(Zend_Console_Getopt $_opts)
445     {
446         if (! $this->_checkAdminRight()) {
447             return FALSE;
448         }
449
450         $args = $this->_parseArgs($_opts, array(), 'tables');
451         $doEverything = false;
452
453         if (! (isset($args['tables']) || array_key_exists('tables', $args)) || empty($args['tables'])) {
454             echo "No tables given.\nPurging records from all tables!\n";
455             $args['tables'] = $this->_getAllApplicationTables();
456             $doEverything = true;
457         }
458         
459         $db = Tinebase_Core::getDb();
460         
461         if ((isset($args['date']) || array_key_exists('date', $args))) {
462             echo "\nRemoving all deleted entries before {$args['date']} ...";
463             $where = array(
464                 $db->quoteInto($db->quoteIdentifier('deleted_time') . ' < ?', $args['date'])
465             );
466         } else {
467             echo "\nRemoving all deleted entries ...";
468             $where = array();
469         }
470         $where[] = $db->quoteInto($db->quoteIdentifier('is_deleted') . ' = ?', 1);
471
472         $orderedTables = $this->_orderTables($args['tables']);
473         $this->_purgeTables($orderedTables, $where);
474
475         if ($doEverything) {
476             echo "\nCleaning relations...";
477             $this->cleanRelations();
478
479             echo "\nCleaning modlog...";
480             $this->cleanModlog();
481
482             echo "\nCleaning customfields...";
483             $this->cleanCustomfields();
484
485             echo "\nCleaning notes...";
486             $this->cleanNotes();
487         }
488
489         echo "\n\n";
490         
491         return TRUE;
492     }
493
494     /**
495      * cleanNotes: removes notes of records that have been deleted
496      */
497     public function cleanNotes()
498     {
499         if (! $this->_checkAdminRight()) {
500             return FALSE;
501         }
502
503         $notesController = Tinebase_Notes::getInstance();
504         $notes = $notesController->getAllNotes();
505         $controllers = array();
506         $models = array();
507         $deleteIds = array();
508
509         /** @var Tinebase_Model_Note $note */
510         foreach ($notes as $note) {
511             if (!isset($controllers[$note->record_model])) {
512                 if (strpos($note->record_model, 'Tinebase') === 0) {
513                     continue;
514                 }
515                 try {
516                     $controllers[$note->record_model] = Tinebase_Core::getApplicationInstance($note->record_model);
517                 } catch(Tinebase_Exception_AccessDenied $e) {
518                     // TODO log
519                     continue;
520                 } catch(Tinebase_Exception_NotFound $tenf) {
521                     $deleteIds[] = $note->getId();
522                     continue;
523                 }
524                 $oldACLCheckValue = $controllers[$note->record_model]->doContainerACLChecks(false);
525                 $models[$note->record_model] = array(
526                     0 => new $note->record_model(),
527                     1 => ($note->record_model !== 'Filemanager_Model_Node' ? class_exists($note->record_model . 'Filter') : false),
528                     2 => $note->record_model . 'Filter',
529                     3 => $oldACLCheckValue
530                 );
531             }
532             $controller = $controllers[$note->record_model];
533             $model = $models[$note->record_model];
534
535             if ($model[1]) {
536                 $filter = new $model[2](array(
537                     array('field' => $model[0]->getIdProperty(), 'operator' => 'equals', 'value' => $note->record_id)
538                 ));
539                 if ($model[0]->has('is_deleted')) {
540                     $filter->addFilter(new Tinebase_Model_Filter_Int(array('field' => 'is_deleted', 'operator' => 'notnull', 'value' => NULL)));
541                 }
542                 $result = $controller->searchCount($filter);
543
544                 if (is_bool($result) || (is_string($result) && $result === ((string)intval($result)))) {
545                     $result = (int)$result;
546                 }
547
548                 if (!is_int($result)) {
549                     if (is_array($result) && isset($result['totalcount'])) {
550                         $result = (int)$result['totalcount'];
551                     } elseif(is_array($result) && isset($result['count'])) {
552                         $result = (int)$result['count'];
553                     } else {
554                         // todo log
555                         // dummy line, remove!
556                         $result = 1;
557                     }
558                 }
559
560                 if ($result === 0) {
561                     $deleteIds[] = $note->getId();
562                 }
563             } else {
564                 try {
565                     $controller->get($note->record_id, null, false, true);
566                 } catch(Tinebase_Exception_NotFound $tenf) {
567                     $deleteIds[] = $note->getId();
568                 }
569             }
570         }
571
572         if (count($deleteIds) > 0) {
573             $notesController->purgeNotes($deleteIds);
574         }
575
576         foreach($controllers as $model => $controller) {
577             $controller->doContainerACLChecks($models[$model][3]);
578         }
579
580         echo "\ndeleted " . count($deleteIds) . " notes\n";
581     }
582
583     /**
584      * cleanCustomfields
585      */
586     public function cleanCustomfields()
587     {
588         if (! $this->_checkAdminRight()) {
589             return FALSE;
590         }
591
592         $customFieldController = Tinebase_CustomField::getInstance();
593         $customFieldConfigs = $customFieldController->searchConfig();
594         $deleteCount = 0;
595
596         /** @var Tinebase_Model_CustomField_Config $customFieldConfig */
597         foreach($customFieldConfigs as $customFieldConfig) {
598             $deleteAll = false;
599             try {
600                 $controller = Tinebase_Core::getApplicationInstance($customFieldConfig->model);
601
602                 $oldACLCheckValue = $controller->doContainerACLChecks(false);
603                 if ($customFieldConfig->model !== 'Filemanager_Model_Node') {
604                     $filterClass = $customFieldConfig->model . 'Filter';
605                 } else {
606                     $filterClass = 'ClassThatDoesNotExist';
607                 }
608             } catch(Tinebase_Exception_AccessDenied $e) {
609                 // TODO log
610                 continue;
611             } catch(Tinebase_Exception_NotFound $tenf) {
612                 $deleteAll = true;
613             }
614
615
616
617             $filter = new Tinebase_Model_CustomField_ValueFilter(array(
618                 array('field' => 'customfield_id', 'operator' => 'equals', 'value' => $customFieldConfig->id)
619             ));
620             $customFieldValues = $customFieldController->search($filter);
621             $deleteIds = array();
622
623             if (true === $deleteAll) {
624                 $deleteIds = $customFieldValues->getId();
625             } elseif (class_exists($filterClass)) {
626                 $model = new $customFieldConfig->model();
627                 /** @var Tinebase_Model_CustomField_Value $customFieldValue */
628                 foreach ($customFieldValues as $customFieldValue) {
629                     $filter = new $filterClass(array(
630                         array('field' => $model->getIdProperty(), 'operator' => 'equals', 'value' => $customFieldValue->record_id)
631                     ));
632                     if ($model->has('is_deleted')) {
633                         $filter->addFilter(new Tinebase_Model_Filter_Int(array('field' => 'is_deleted', 'operator' => 'notnull', 'value' => NULL)));
634                     }
635
636                     $result = $controller->searchCount($filter);
637
638                     if (is_bool($result) || (is_string($result) && $result === ((string)intval($result)))) {
639                         $result = (int)$result;
640                     }
641
642                     if (!is_int($result)) {
643                         if (is_array($result) && isset($result['totalcount'])) {
644                             $result = (int)$result['totalcount'];
645                         } elseif(is_array($result) && isset($result['count'])) {
646                             $result = (int)$result['count'];
647                         } else {
648                             // todo log
649                             // dummy line, remove!
650                             $result = 1;
651                         }
652                     }
653
654                     if ($result === 0) {
655                         $deleteIds[] = $customFieldValue->getId();
656                     }
657                 }
658             } else {
659                 /** @var Tinebase_Model_CustomField_Value $customFieldValue */
660                 foreach ($customFieldValues as $customFieldValue) {
661                     try {
662                         $controller->get($customFieldValue->record_id, null, false, true);
663                     } catch(Tinebase_Exception_NotFound $tenf) {
664                         $deleteIds[] = $customFieldValue->getId();
665                     }
666                 }
667             }
668
669             if (count($deleteIds) > 0) {
670                 $customFieldController->deleteCustomFieldValue($deleteIds);
671                 $deleteCount += count($deleteIds);
672             }
673
674             if (true !== $deleteAll) {
675                 $controller->doContainerACLChecks($oldACLCheckValue);
676             }
677         }
678
679         echo "\ndeleted " . $deleteCount . " customfield values\n";
680     }
681     
682     /**
683      * get all app tables
684      * 
685      * @return array
686      */
687     protected function _getAllApplicationTables()
688     {
689         $result = array();
690         
691         $enabledApplications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED);
692         foreach ($enabledApplications as $application) {
693             $result = array_merge($result, Tinebase_Application::getInstance()->getApplicationTables($application));
694         }
695         
696         return $result;
697     }
698
699     /**
700      * order tables for purging deleted records in a defined order
701      *
702      * @param array $tables
703      * @return array
704      *
705      * TODO could be improved by using usort
706      */
707     protected function _orderTables($tables)
708     {
709         // tags should be deleted first
710         // containers should be deleted last
711
712         $orderedTables = array();
713         $lastTables = array();
714         foreach($tables as $table) {
715             switch ($table) {
716                 case 'container':
717                     $lastTables[] = $table;
718                     break;
719                 case 'tags':
720                     array_unshift($orderedTables, $table);
721                     break;
722                 default:
723                     $orderedTables[] = $table;
724             }
725         }
726         $orderedTables = array_merge($orderedTables, $lastTables);
727
728         return $orderedTables;
729     }
730
731     /**
732      * purge tables
733      *
734      * @param $orderedTables
735      * @param $where
736      */
737     protected function _purgeTables($orderedTables, $where)
738     {
739         foreach ($orderedTables as $table) {
740             try {
741                 $schema = Tinebase_Db_Table::getTableDescriptionFromCache(SQL_TABLE_PREFIX . $table);
742             } catch (Zend_Db_Statement_Exception $zdse) {
743                 echo "\nCould not get schema (" . $zdse->getMessage() . "). Skipping table $table";
744                 continue;
745             }
746             if (!(isset($schema['is_deleted']) || array_key_exists('is_deleted', $schema)) || !(isset($schema['deleted_time']) || array_key_exists('deleted_time', $schema))) {
747                 continue;
748             }
749
750             $deleteCount = 0;
751             try {
752                 $deleteCount = Tinebase_Core::getDb()->delete(SQL_TABLE_PREFIX . $table, $where);
753             } catch (Zend_Db_Statement_Exception $zdse) {
754                 echo "\nFailed to purge deleted records for table $table. " . $zdse->getMessage();
755             }
756             if ($deleteCount > 0) {
757                 echo "\nCleared table $table (deleted $deleteCount records).";
758             }
759             // TODO this should only be echoed with --verbose or written to the logs
760             else {
761                 echo "\nNothing to purge from $table";
762             }
763         }
764     }
765
766     /**
767      * add new customfield config
768      *
769      * example:
770      * $ php tine20.php --method=Tinebase.addCustomfield -- \
771          application="Addressbook" model="Addressbook_Model_Contact" name="datefield" \
772          definition='{"label":"Date","type":"datetime", "uiconfig": {"group":"Dates", "order": 30}}'
773      * @see Tinebase_Model_CustomField_Config for full list
774      *
775      * @param $_opts
776      * @return boolean success
777      */
778     public function addCustomfield(Zend_Console_Getopt $_opts)
779     {
780         if (! $this->_checkAdminRight()) {
781             return FALSE;
782         }
783         
784         // parse args
785         $args = $_opts->getRemainingArgs();
786         $data = array();
787         foreach ($args as $idx => $arg) {
788             list($key, $value) = explode('=', $arg);
789             if ($key == 'application') {
790                 $key = 'application_id';
791                 $value = Tinebase_Application::getInstance()->getApplicationByName($value)->getId();
792             }
793             $data[$key] = $value;
794         }
795         
796         $customfieldConfig = new Tinebase_Model_CustomField_Config($data);
797         $cf = Tinebase_CustomField::getInstance()->addCustomField($customfieldConfig);
798
799         echo "\nCreated customfield: ";
800         print_r($cf->toArray());
801         echo "\n";
802         
803         return TRUE;
804     }
805     
806     /**
807      * nagios monitoring for tine 2.0 database connection
808      * 
809      * @return integer
810      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
811      */
812     public function monitoringCheckDB()
813     {
814         $message = 'DB CONNECTION FAIL';
815         try {
816             if (! Setup_Core::isRegistered(Setup_Core::CONFIG)) {
817                 Setup_Core::setupConfig();
818             }
819             if (! Setup_Core::isRegistered(Setup_Core::LOGGER)) {
820                 Setup_Core::setupLogger();
821             }
822             $time_start = microtime(true);
823             $dbcheck = Setup_Core::setupDatabaseConnection();
824             $time = (microtime(true) - $time_start) * 1000;
825         } catch (Exception $e) {
826             $message .= ': ' . $e->getMessage();
827             $dbcheck = FALSE;
828         }
829         
830         if ($dbcheck) {
831             echo "DB CONNECTION OK | connecttime={$time}ms;;;;\n";
832             return 0;
833         } 
834         
835         echo $message . "\n";
836         return 2;
837     }
838     
839     /**
840      * nagios monitoring for tine 2.0 config file
841      * 
842      * @return integer
843      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
844      */
845     public function monitoringCheckConfig()
846     {
847         $message = 'CONFIG FAIL';
848         $configcheck = FALSE;
849         
850         $configfile = Setup_Core::getConfigFilePath();
851         if ($configfile) {
852             $configfile = escapeshellcmd($configfile);
853             if (preg_match('/^win/i', PHP_OS)) {
854                 exec("php -l $configfile 2> NUL", $error, $code);
855             } else {
856                 exec("php -l $configfile 2> /dev/null", $error, $code);
857             }
858             if ($code == 0) {
859                 $configcheck = TRUE;
860             } else {
861                 $message .= ': CONFIG FILE SYNTAX ERROR';
862             }
863         } else {
864             $message .= ': CONFIG FILE MISSING';
865         }
866         
867         if ($configcheck) {
868             echo "CONFIG FILE OK\n";
869             return 0;
870         } else {
871             echo $message . "\n";
872             return 2;
873         }
874     }
875     
876     /**
877     * nagios monitoring for tine 2.0 async cronjob run
878     *
879     * @return integer
880     * 
881     * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
882     * @see 0008038: monitoringCheckCron -> check if cron did run in the last hour
883     */
884     public function monitoringCheckCron()
885     {
886         $message = 'CRON FAIL';
887
888         try {
889             $lastJob = Tinebase_AsyncJob::getInstance()->getLastJob('Tinebase_Event_Async_Minutely');
890             
891             if ($lastJob === NULL) {
892                 $message .= ': NO LAST JOB FOUND';
893                 $result = 1;
894             } else {
895                 if ($lastJob->end_time instanceof Tinebase_DateTime) {
896                     $duration = $lastJob->end_time->getTimestamp() - $lastJob->start_time->getTimestamp();
897                     $valueString = ' | duration=' . $duration . 's;;;;';
898                     $valueString .= ' end=' . $lastJob->end_time->getIso() . ';;;;';
899                 } else {
900                     $valueString = '';
901                 }
902                 
903                 if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_RUNNING && Tinebase_DateTime::now()->isLater($lastJob->end_time)) {
904                     $message .= ': LAST JOB TOOK TOO LONG';
905                     $result = 1;
906                 } else if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_FAILURE) {
907                     $message .= ': LAST JOB FAILED';
908                     $result = 1;
909                 } else if (Tinebase_DateTime::now()->isLater($lastJob->start_time->addHour(1))) {
910                     $message .= ': NO JOB IN THE LAST HOUR';
911                     $result = 1;
912                 } else {
913                     $message = 'CRON OK';
914                     $result = 0;
915                 }
916                 $message .= $valueString;
917             }
918         } catch (Exception $e) {
919             $message .= ': ' . $e->getMessage();
920             $result = 2;
921         }
922         
923         echo $message . "\n";
924         return $result;
925     }
926     
927     /**
928      * nagios monitoring for tine 2.0 logins during the last 5 mins
929      * 
930      * @return number
931      * 
932      * @todo allow to configure timeslot
933      */
934     public function monitoringLoginNumber()
935     {
936         $message = 'LOGINS';
937         $result  = 0;
938         
939         try {
940             $filter = new Tinebase_Model_AccessLogFilter(array(
941                 array('field' => 'li', 'operator' => 'after', 'value' => Tinebase_DateTime::now()->subMinute(5))
942             ));
943             $accesslogs = Tinebase_AccessLog::getInstance()->search($filter, NULL, FALSE, TRUE);
944             $valueString = ' | count=' . count($accesslogs) . ';;;;';
945             $message .= ' OK' . $valueString;
946         } catch (Exception $e) {
947             $message .= ' FAIL: ' . $e->getMessage();
948             $result = 2;
949         }
950         
951         echo $message . "\n";
952         return $result;
953     }
954
955     /**
956      * nagios monitoring for tine 2.0 active users
957      *
958      * @return number
959      *
960      * @todo allow to configure timeslot / currently the active users of the last month are returned
961      */
962     public function monitoringActiveUsers()
963     {
964         $message = 'ACTIVE USERS';
965         $result  = 0;
966
967         try {
968             $userCount = Tinebase_User::getInstance()->getActiveUserCount();
969             $valueString = ' | count=' . $userCount . ';;;;';
970             $message .= ' OK' . $valueString;
971         } catch (Exception $e) {
972             $message .= ' FAIL: ' . $e->getMessage();
973             $result = 2;
974         }
975
976         echo $message . "\n";
977         return $result;
978     }
979
980     /**
981      * undo changes to records defined by certain criteria (user, date, fields, ...)
982      * 
983      * example: $ php tine20.php --username pschuele --method Tinebase.undo -d 
984      *   -- record_type=Addressbook_Model_Contact modification_time=2013-05-08 modification_account=3263
985      * 
986      * @param Zend_Console_Getopt $opts
987      */
988     public function undo(Zend_Console_Getopt $opts)
989     {
990         if (! $this->_checkAdminRight()) {
991             return FALSE;
992         }
993         
994         $data = $this->_parseArgs($opts, array('modification_time'));
995         
996         // build filter from params
997         $filterData = array();
998         $allowedFilters = array(
999             'record_type',
1000             'modification_time',
1001             'modification_account',
1002             'record_id'
1003         );
1004         foreach ($data as $key => $value) {
1005             if (in_array($key, $allowedFilters)) {
1006                 $operator = ($key === 'modification_time') ? 'within' : 'equals';
1007                 $filterData[] = array('field' => $key, 'operator' => $operator, 'value' => $value);
1008             }
1009         }
1010         $filter = new Tinebase_Model_ModificationLogFilter($filterData);
1011         
1012         $dryrun = $opts->d;
1013         $overwrite = (isset($data['overwrite']) && $data['overwrite']) ? TRUE : FALSE;
1014         $result = Tinebase_Timemachine_ModificationLog::getInstance()->undo($filter, $overwrite, $dryrun, (isset($data['modified_attribute'])?$data['modified_attribute']:null));
1015         
1016         if (! $dryrun) {
1017             echo 'Reverted ' . $result['totalcount'] . " change(s)\n";
1018         } else {
1019             echo "Dry run\n";
1020             echo 'Would revert ' . $result['totalcount'] . " change(s):\n";
1021             foreach ($result['undoneModlogs'] as $modlog) {
1022                 $modifiedAttribute = $modlog->modified_attribute;
1023                 if (!empty($modifiedAttribute)) {
1024                     echo 'id ' . $modlog->record_id . ' [' . $modifiedAttribute . ']: ' . $modlog->new_value . ' -> ' . $modlog->old_value . PHP_EOL;
1025                 } else {
1026                     if ($modlog->change_type === Tinebase_Timemachine_ModificationLog::CREATED) {
1027                         echo 'id ' . $modlog->record_id . ' DELETE' . PHP_EOL;
1028                     } elseif ($modlog->change_type === Tinebase_Timemachine_ModificationLog::DELETED) {
1029                         echo 'id ' . $modlog->record_id . ' UNDELETE' . PHP_EOL;
1030                     } else {
1031                         $diff = new Tinebase_Record_Diff(json_decode($modlog->new_value));
1032                         foreach($diff->diff as $key => $val) {
1033                             echo 'id ' . $modlog->record_id . ' [' . $key . ']: ' . $val . ' -> ' . $diff->oldData[$key] . PHP_EOL;
1034                         }
1035                     }
1036                 }
1037             }
1038         }
1039         echo 'Failcount: ' . $result['failcount'] . "\n";
1040         return 0;
1041     }
1042     
1043     /**
1044      * creates demo data for all applications
1045      * accepts same arguments as Tinebase_Frontend_Cli_Abstract::createDemoData
1046      * and the additional argument "skipAdmin" to force no user/group/role creation
1047      * 
1048      * @param Zend_Console_Getopt $_opts
1049      */
1050     public function createAllDemoData($_opts)
1051     {
1052         if (! $this->_checkAdminRight()) {
1053             return FALSE;
1054         }
1055         
1056         // fetch all applications and check if required are installed, otherwise remove app from array
1057         $applications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
1058         foreach($applications as $appName) {
1059             echo 'Searching for DemoData in application "' . $appName . '"...' . PHP_EOL;
1060             $className = $appName.'_Setup_DemoData';
1061             if (class_exists($className)) {
1062                 echo 'DemoData in application "' . $appName . '" found!' . PHP_EOL;
1063                 $required = $className::getRequiredApplications();
1064                 foreach($required as $requiredApplication) {
1065                     if (! Tinebase_Helper::in_array_case($applications, $requiredApplication)) {
1066                         echo 'Creating DemoData for Application ' . $appName . ' is impossible, because application "' . $requiredApplication . '" is not installed.' . PHP_EOL;
1067                         continue 2;
1068                     }
1069                 }
1070                 $this->_applicationsToWorkOn[$appName] = array('appName' => $appName, 'required' => $required);
1071             } else {
1072                 echo 'DemoData in application "' . $appName . '" not found.' . PHP_EOL . PHP_EOL;
1073             }
1074         }
1075         unset($applications);
1076         
1077         foreach($this->_applicationsToWorkOn as $app => $cfg) {
1078             $this->_createDemoDataRecursive($app, $cfg, $_opts);
1079         }
1080
1081         return 0;
1082     }
1083     
1084     /**
1085      * creates demo data and calls itself if there are required apps
1086      * 
1087      * @param string $app
1088      * @param array $cfg
1089      * @param Zend_Console_Getopt $opts
1090      */
1091     protected function _createDemoDataRecursive($app, $cfg, $opts)
1092     {
1093         if (isset($cfg['required']) && is_array($cfg['required'])) {
1094             foreach($cfg['required'] as $requiredApp) {
1095                 $this->_createDemoDataRecursive($requiredApp, $this->_applicationsToWorkOn[$requiredApp], $opts);
1096             }
1097         }
1098         
1099         $className = $app . '_Frontend_Cli';
1100         
1101         $classNameDD = $app . '_Setup_DemoData';
1102         
1103         if (class_exists($className)) {
1104             if (! $classNameDD::hasBeenRun()) {
1105                 echo 'Creating DemoData in application "' . $app . '"...' . PHP_EOL;
1106                 $class = new $className();
1107                 $class->createDemoData($opts, FALSE);
1108             } else {
1109                 echo 'DemoData for ' . $app . ' has been run already, skipping...' . PHP_EOL;
1110             }
1111         } else {
1112             echo 'Could not found ' . $className . ', so DemoData for application "' . $app . '" could not be created!';
1113         }
1114     }
1115     
1116     /**
1117      * clears deleted files from filesystem + database
1118      *
1119      * @return int
1120      */
1121     public function clearDeletedFiles()
1122     {
1123         if (! $this->_checkAdminRight()) {
1124             return -1;
1125         }
1126         
1127         $this->_addOutputLogWriter();
1128         
1129         Tinebase_FileSystem::getInstance()->clearDeletedFiles();
1130
1131         return 0;
1132     }
1133
1134     /**
1135      * recalculates the revision sizes and then the folder sizes
1136      *
1137      * @return int
1138      */
1139     public function fileSystemSizeRecalculation()
1140     {
1141         if (! $this->_checkAdminRight()) {
1142             return -1;
1143         }
1144
1145         Tinebase_FileSystem::getInstance()->recalculateRevisionSize();
1146
1147         Tinebase_FileSystem::getInstance()->recalculateFolderSize();
1148
1149         return 0;
1150     }
1151
1152     /**
1153      * checks if there are not yet indexed file objects and adds them to the index synchronously
1154      * that means this can be very time consuming
1155      *
1156      * @return int
1157      */
1158     public function fileSystemCheckIndexing()
1159     {
1160
1161         if (! $this->_checkAdminRight()) {
1162             return -1;
1163         }
1164
1165         Tinebase_FileSystem::getInstance()->checkIndexing();
1166
1167         return 0;
1168     }
1169     
1170     /**
1171      * repair a table
1172      * 
1173      * @param Zend_Console_Getopt $opts
1174      * 
1175      * @todo add more tables
1176      */
1177     public function repairTable($opts)
1178     {
1179         if (! $this->_checkAdminRight()) {
1180             return FALSE;
1181         }
1182         
1183         $this->_addOutputLogWriter();
1184         
1185         $data = $this->_parseArgs($opts, array('table'));
1186         
1187         switch ($data['table']) {
1188             case 'importexport_definition':
1189                 Tinebase_ImportExportDefinition::getInstance()->repairTable();
1190                 $result = 0;
1191                 break;
1192             default:
1193                 if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1194                     . ' No repair script found for ' . $data['table']);
1195                 $result = 1;
1196         }
1197         
1198         exit($result);
1199     }
1200     
1201     /**
1202      * repairs container names
1203      * 
1204      * @param Zend_Console_Getopt $opts
1205      */
1206     public function repairContainerName($opts)
1207     {
1208         if (! $this->_checkAdminRight()) {
1209             return FALSE;
1210         }
1211         $dryrun = $opts->d;
1212         
1213         $this->_addOutputLogWriter();
1214         $args = $this->_parseArgs($opts);
1215         
1216         $containersWithBadNames = Tinebase_Container::getInstance()->getContainersWithBadNames();
1217         
1218         $locale = Tinebase_Translation::getLocale((isset($args['locale']) ?$args['locale'] : 'auto'));
1219
1220         if ($dryrun) {
1221             print_r($containersWithBadNames->toArray());
1222             echo "Using Locale " . $locale . "\n";
1223         }
1224         
1225         $appContainerNames = array(
1226             'Calendar' => 'calendar',
1227             'Tasks'    => 'tasks',
1228             'Addressbook'    => 'addressbook',
1229         );
1230         
1231         foreach ($containersWithBadNames as $container) {
1232             if (empty($container->owner_id)) {
1233                 if ($dryrun) {
1234                     echo "Don't rename shared container " . $container->id . "\n";
1235                 }
1236                 continue;
1237             }
1238             $app = Tinebase_Application::getInstance()->getApplicationById($container->application_id);
1239             $appContainerName = isset($appContainerNames[$app->name]) ? $appContainerNames[$app->name] : "container";
1240             $translation = Tinebase_Translation::getTranslation($app->name, $locale);
1241             $account = Tinebase_User::getInstance()->getUserByPropertyFromSqlBackend('accountId', $container->owner_id);
1242             $newName = $newBaseName = sprintf($translation->_("%s's personal " . $appContainerName), $account->accountFullName);
1243             
1244             $count = 1;
1245             do {
1246                 try {
1247                     Tinebase_Container::getInstance()->getContainerByName($app->name, $newName, Tinebase_Model_Container::TYPE_PERSONAL, $container->owner_id);
1248                     $found = true;
1249                     $newName = $newBaseName . ' ' . ++$count;
1250                 } catch (Tinebase_Exception_NotFound $tenf) {
1251                     $found = false;
1252                 }
1253                 
1254             } while ($found);
1255             if ($dryrun) {
1256                 echo "Rename container id " . $container->id . ' to ' . $newName . "\n";
1257             } else {
1258                 
1259                 $container->name = $newName;
1260                 Tinebase_Container::getInstance()->update($container);
1261             }
1262         }
1263         
1264         $result = 0;
1265         exit($result);
1266     }
1267     
1268     /**
1269      * transfer relations
1270      * 
1271      * @param Zend_Console_Getopt $opts
1272      */
1273     public function transferRelations($opts)
1274     {
1275         if (! $this->_checkAdminRight()) {
1276             return FALSE;
1277         }
1278         
1279         $this->_addOutputLogWriter();
1280         
1281         try {
1282             $args = $this->_parseArgs($opts, array('oldId', 'newId', 'model'));
1283         } catch (Tinebase_Exception_InvalidArgument $e) {
1284             if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1285                 Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' Parameters "oldId", "newId" and "model" are required!');
1286             }
1287             exit(1);
1288         }
1289         
1290         $skippedEntries = Tinebase_Relations::getInstance()->transferRelations($args['oldId'], $args['newId'], $args['model']);
1291
1292         if (! empty($skippedEntries) && Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1293             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . count($skippedEntries) . ' entries has been skipped:');
1294         }
1295         
1296         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1297             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' The operation has been terminated successfully.');
1298         }
1299
1300         return 0;
1301     }
1302
1303     /**
1304      * repair function for persistent filters (favorites) without grants: this adds default grants for those filters.
1305      *
1306      * @return int
1307      */
1308     public function setDefaultGrantsOfPersistentFilters()
1309     {
1310         if (! $this->_checkAdminRight()) {
1311             return -1;
1312         }
1313
1314         $this->_addOutputLogWriter(6);
1315
1316         // get all persistent filters without grants
1317         // TODO this could be enhanced by allowing to set default grants for other filters, too
1318         Tinebase_PersistentFilter::getInstance()->doContainerACLChecks(false);
1319         $filters = Tinebase_PersistentFilter::getInstance()->search(new Tinebase_Model_PersistentFilterFilter(array(),'', array('ignoreAcl' => true)));
1320         $filtersWithoutGrants = 0;
1321
1322         foreach ($filters as $filter) {
1323             if (count($filter->grants) == 0) {
1324                 // update to set default grants
1325                 $filter = Tinebase_PersistentFilter::getInstance()->update($filter);
1326                 $filtersWithoutGrants++;
1327
1328                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1329                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1330                         . ' Updated filter: ' . print_r($filter->toArray(), true));
1331                 }
1332             }
1333         }
1334
1335         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1336             Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1337                 . ' Set default grants for ' . $filtersWithoutGrants . ' filters'
1338                 . ' (checked ' . count($filters) . ' in total).');
1339         }
1340
1341         return 0;
1342     }
1343
1344     /**
1345      *
1346      *
1347      * @return int
1348      */
1349     public function repairContainerOwner()
1350     {
1351         if (! $this->_checkAdminRight()) {
1352             return -1;
1353         }
1354
1355         $this->_addOutputLogWriter(6);
1356         Tinebase_Container::getInstance()->setContainerOwners();
1357
1358         return 0;
1359     }
1360
1361     public function createFullTextIndex()
1362     {
1363         if (! $this->_checkAdminRight()) {
1364             return -1;
1365         }
1366
1367         $setupBackend = Setup_Backend_Factory::factory();
1368         if (!$setupBackend->supports('mysql >= 5.6.4')) {
1369             return -2;
1370         }
1371
1372         $failures = array();
1373         try {
1374             $declaration = new Setup_Backend_Schema_Index_Xml('
1375                 <index>
1376                     <name>note</name>
1377                     <fulltext>true</fulltext>
1378                     <field>
1379                         <name>note</name>
1380                     </field>
1381                 </index>
1382             ');
1383             $setupBackend->addIndex('addressbook', $declaration);
1384         } catch (Exception $e) {
1385             $failures[] = 'addressbook';
1386         }
1387
1388         try {
1389             $declaration = new Setup_Backend_Schema_Index_Xml('
1390                 <index>
1391                     <name>description</name>
1392                     <fulltext>true</fulltext>
1393                     <field>
1394                         <name>description</name>
1395                     </field>
1396                 </index>
1397             ');
1398             $setupBackend->addIndex('cal_events', $declaration);
1399         } catch (Exception $e) {
1400             $failures[] = 'cal_events';
1401         }
1402
1403         try {
1404             $declaration = new Setup_Backend_Schema_Index_Xml('
1405                 <index>
1406                     <name>description</name>
1407                     <fulltext>true</fulltext>
1408                     <field>
1409                         <name>description</name>
1410                     </field>
1411                 </index>
1412             ');
1413             $setupBackend->addIndex('metacrm_lead', $declaration);
1414         } catch (Exception $e) {
1415             $failures[] = 'metacrm_lead';
1416         }
1417
1418         try {
1419             $declaration = new Setup_Backend_Schema_Index_Xml('
1420                 <index>
1421                     <name>description</name>
1422                     <fulltext>true</fulltext>
1423                     <field>
1424                         <name>description</name>
1425                     </field>
1426                 </index>
1427             ');
1428             $setupBackend->addIndex('events_event', $declaration);
1429         } catch (Exception $e) {
1430             $failures[] = 'events_event';
1431         }
1432
1433         try {
1434             $declaration = new Setup_Backend_Schema_Index_Xml('
1435                 <index>
1436                     <name>description</name>
1437                     <fulltext>true</fulltext>
1438                     <field>
1439                         <name>description</name>
1440                     </field>
1441                 </index>
1442             ');
1443             $setupBackend->addIndex('projects_project', $declaration);
1444         } catch (Exception $e) {
1445             $failures[] = 'projects_project';
1446         }
1447
1448         try {
1449             $declaration = new Setup_Backend_Schema_Index_Xml('
1450                 <index>
1451                     <name>description</name>
1452                     <fulltext>true</fulltext>
1453                     <field>
1454                         <name>description</name>
1455                     </field>
1456                 </index>
1457             ');
1458             $setupBackend->addIndex('sales_contracts', $declaration);
1459         } catch (Exception $e) {
1460             $failures[] = 'sales_contracts';
1461         }
1462
1463         try {
1464             $declaration = new Setup_Backend_Schema_Index_Xml('
1465                 <index>
1466                     <name>description</name>
1467                     <fulltext>true</fulltext>
1468                     <field>
1469                         <name>description</name>
1470                     </field>
1471                 </index>
1472             ');
1473             $setupBackend->addIndex('sales_products', $declaration);
1474         } catch (Exception $e) {
1475             $failures[] = 'sales_products';
1476         }
1477
1478         try {
1479             $declaration = new Setup_Backend_Schema_Index_Xml('
1480                 <index>
1481                     <name>description</name>
1482                     <fulltext>true</fulltext>
1483                     <field>
1484                         <name>description</name>
1485                     </field>
1486                 </index>
1487             ');
1488             $setupBackend->addIndex('sales_customers', $declaration);
1489         } catch (Exception $e) {
1490             $failures[] = 'sales_customers';
1491         }
1492
1493         try {
1494             $declaration = new Setup_Backend_Schema_Index_Xml('
1495                 <index>
1496                     <name>description</name>
1497                     <fulltext>true</fulltext>
1498                     <field>
1499                         <name>description</name>
1500                     </field>
1501                 </index>
1502             ');
1503             $setupBackend->addIndex('sales_suppliers', $declaration);
1504         } catch (Exception $e) {
1505             $failures[] = 'sales_suppliers';
1506         }
1507
1508         try {
1509             $declaration = new Setup_Backend_Schema_Index_Xml('
1510                 <index>
1511                     <name>description</name>
1512                     <fulltext>true</fulltext>
1513                     <field>
1514                         <name>description</name>
1515                     </field>
1516                 </index>
1517             ');
1518             $setupBackend->addIndex('sales_purchase_invoices', $declaration);
1519         } catch (Exception $e) {
1520             $failures[] = 'sales_purchase_invoices';
1521         }
1522
1523         try {
1524             $declaration = new Setup_Backend_Schema_Index_Xml('
1525                 <index>
1526                     <name>description</name>
1527                     <fulltext>true</fulltext>
1528                     <field>
1529                         <name>description</name>
1530                     </field>
1531                 </index>
1532             ');
1533             $setupBackend->addIndex('sales_sales_invoices', $declaration);
1534         } catch (Exception $e) {
1535             $failures[] = 'sales_sales_invoices';
1536         }
1537
1538         try {
1539             $declaration = new Setup_Backend_Schema_Index_Xml('
1540                 <index>
1541                     <name>description</name>
1542                     <fulltext>true</fulltext>
1543                     <field>
1544                         <name>description</name>
1545                     </field>
1546                 </index>
1547             ');
1548             $setupBackend->addIndex('sales_offers', $declaration);
1549         } catch (Exception $e) {
1550             $failures[] = 'sales_offers';
1551         }
1552
1553         try {
1554             $declaration = new Setup_Backend_Schema_Index_Xml('
1555                 <index>
1556                     <name>description</name>
1557                     <fulltext>true</fulltext>
1558                     <field>
1559                         <name>description</name>
1560                     </field>
1561                 </index>
1562             ');
1563             $setupBackend->addIndex('tasks', $declaration);
1564         } catch (Exception $e) {
1565             $failures[] = 'tasks';
1566         }
1567
1568         try {
1569             $declaration = new Setup_Backend_Schema_Index_Xml('
1570                 <index>
1571                     <name>description</name>
1572                     <fulltext>true</fulltext>
1573                     <field>
1574                         <name>description</name>
1575                     </field>
1576                 </index>
1577             ');
1578             $setupBackend->addIndex('timetracker_timesheet', $declaration);
1579         } catch (Exception $e) {
1580             $failures[] = 'timetracker_timesheet';
1581         }
1582
1583         try {
1584             $declaration = new Setup_Backend_Schema_Index_Xml('
1585                 <index>
1586                     <name>description</name>
1587                     <fulltext>true</fulltext>
1588                     <field>
1589                         <name>description</name>
1590                     </field>
1591                 </index>
1592             ');
1593             $setupBackend->addIndex('timetracker_timeaccount', $declaration);
1594         } catch (Exception $e) {
1595             $failures[] = 'timetracker_timeaccount';
1596         }
1597
1598         try {
1599             if ($setupBackend->tableExists('path')) {
1600                 $declaration = new Setup_Backend_Schema_Index_Xml('
1601                     <index>
1602                         <name>path</name>
1603                         <fulltext>true</fulltext>
1604                         <field>
1605                             <name>path</name>
1606                         </field>
1607                     </index>
1608                 ');
1609                 $setupBackend->addIndex('path', $declaration);
1610             }
1611         } catch (Exception $e) {
1612             $failures[] = 'path';
1613         }
1614
1615         try {
1616             if ($setupBackend->tableExists('path')) {
1617                 $declaration = new Setup_Backend_Schema_Index_Xml('
1618                     <index>
1619                         <name>shadow_path</name>
1620                         <fulltext>true</fulltext>
1621                         <field>
1622                             <name>shadow_path</name>
1623                         </field>
1624                     </index>
1625                 ');
1626                 $setupBackend->addIndex('path', $declaration);
1627             }
1628         } catch (Exception $e) {
1629             $failures[] = 'shadow_path';
1630         }
1631
1632         try {
1633             if (!$setupBackend->tableExists('path')) {
1634                 $declaration = new Setup_Backend_Schema_Table_Xml('<table>
1635                     <name>path</name>
1636                     <version>2</version>
1637                     <requirements>
1638                         <required>mysql >= 5.6.4</required>
1639                     </requirements>
1640                     <declaration>
1641                         <field>
1642                             <name>id</name>
1643                             <type>text</type>
1644                             <length>40</length>
1645                             <notnull>true</notnull>
1646                         </field>
1647                         <field>
1648                             <name>path</name>
1649                             <type>text</type>
1650                             <length>65535</length>
1651                             <notnull>true</notnull>
1652                         </field>
1653                         <field>
1654                             <name>shadow_path</name>
1655                             <type>text</type>
1656                             <length>65535</length>
1657                             <notnull>true</notnull>
1658                         </field>
1659                         <field>
1660                             <name>creation_time</name>
1661                             <type>datetime</type>
1662                         </field>
1663                         <index>
1664                             <name>id</name>
1665                             <primary>true</primary>
1666                             <field>
1667                                 <name>id</name>
1668                             </field>
1669                         </index>
1670                         <index>
1671                         <name>path</name>
1672                             <fulltext>true</fulltext>
1673                             <field>
1674                                 <name>path</name>
1675                             </field>
1676                         </index>
1677                         <index>
1678                             <name>shadow_path</name>
1679                             <fulltext>true</fulltext>
1680                             <field>
1681                                 <name>shadow_path</name>
1682                             </field>
1683                         </index>
1684                     </declaration>
1685                 </table>');
1686
1687                 $tmp = new Setup_Update_Abstract($setupBackend);
1688                 $tmp->createTable('path', $declaration, 'Tinebase', 2);
1689
1690                 $setupUser = Setup_Update_Abstract::getSetupFromConfigOrCreateOnTheFly();
1691                 if ($setupUser) {
1692                     Tinebase_Core::set(Tinebase_Core::USER, $setupUser);
1693                     Tinebase_Controller::getInstance()->rebuildPaths();
1694                 } else {
1695                     if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
1696                         Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__
1697                             . ' Could not find valid setupuser. Skipping rebuildPaths: you might need to run this manually.');
1698                     }
1699                 }
1700             }
1701         } catch (Exception $e) {
1702             $failures[] = 'create path';
1703         }
1704
1705         try {
1706             $declaration = new Setup_Backend_Schema_Index_Xml('
1707                 <index>
1708                     <name>text_data</name>
1709                     <fulltext>true</fulltext>
1710                     <field>
1711                         <name>text_data</name>
1712                     </field>
1713                 </index>
1714             ');
1715             $setupBackend->addIndex('external_fulltext', $declaration);
1716         } catch (Exception $e) {
1717             $failures[] = 'external_fulltext';
1718         }
1719
1720         try {
1721             $declaration = new Setup_Backend_Schema_Index_Xml('
1722                 <index>
1723                     <name>description</name>
1724                     <fulltext>true</fulltext>
1725                     <field>
1726                         <name>description</name>
1727                     </field>
1728                 </index>
1729             ');
1730             $setupBackend->addIndex('tree_fileobjects', $declaration);
1731         } catch (Exception $e) {
1732             $failures[] = 'tree_fileobjects';
1733         }
1734
1735         try {
1736             try {
1737                 $setupBackend->dropIndex('tags', 'description');
1738             } catch (Exception $e) {
1739                 // Ignore, if there is no index, we can just go on and create one.
1740             }
1741             $declaration = new Setup_Backend_Schema_Index_Xml('
1742                 <index>
1743                     <name>description</name>
1744                     <fulltext>true</fulltext>
1745                     <field>
1746                         <name>description</name>
1747                     </field>
1748                 </index>
1749             ');
1750             $setupBackend->addIndex('tags', $declaration);
1751         } catch (Exception $e) {
1752             $failures[] = 'tags';
1753         }
1754
1755         if (count($failures) > 0) {
1756             echo PHP_EOL . 'failures: ' . join(' ', $failures);
1757         }
1758
1759         echo PHP_EOL . 'done' . PHP_EOL . PHP_EOL;
1760     }
1761 }