Tinebase_Core - remove redundant code
[tine20] / tine20 / Tinebase / Core.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  Server
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @copyright   Copyright (c) 2007-2013 Metaways Infosystems GmbH (http://www.metaways.de)
9  * @author      Philipp Schüle <p.schuele@metaways.de>
10  *
11  */
12
13 /**
14  * dispatcher and initialisation class (functions are static)
15  * - dispatchRequest() function
16  * - initXYZ() functions
17  * - has registry and config
18  *
19  * @package     Tinebase
20  * @subpackage  Server
21  */
22 class Tinebase_Core
23 {
24     /**************** registry indexes *************************/
25
26     /**
27      * constant for config registry index
28      */
29     const CONFIG = 'configFile';
30
31     /**
32      * constant for locale registry index
33      */
34     const LOCALE = 'locale';
35
36     /**
37      * constant for logger registry index
38      */
39     const LOGGER = 'logger';
40     
41     /**
42      * constant for loglevel registry index
43      *
44      */
45     const LOGLEVEL = 'loglevel';
46
47     /**
48      * constant for cache registry index
49      */
50     const CACHE = 'cache';
51     
52      /**
53      * constant for shared cache registry index
54      */
55     const SHAREDCACHE = 'sharedCache';
56
57     /**
58      * constant for session namespace (tinebase) registry index
59      */
60     const SESSION = 'session';
61     
62     /**
63      * session id constant
64      */
65     const SESSIONID = 'sessionId';
66
67     /**
68      * constant for application start time in ms registry index
69      */
70     const STARTTIME = 'starttime';
71     
72     const REQUEST = 'request';
73
74     /**
75      * constant for current account/user
76      */
77     const USER = 'currentAccount';
78
79     /**
80      * const for current users credentialcache
81      */
82     const USERCREDENTIALCACHE = 'usercredentialcache';
83
84     /**
85      * const for current users access log
86      */
87     const USERACCESSLOG = 'useraccesslog';
88
89     /**
90      * constant for database adapter
91      */
92     const DB = 'dbAdapter';
93     
94     /**
95      * constant for database adapter name
96      * 
97      */
98     const DBNAME = 'dbAdapterName';
99
100     /**
101      * constant for database adapter
102      */
103     const USERTIMEZONE = 'userTimeZone';
104
105     /**
106      * constant for preferences registry
107      */
108     const PREFERENCES = 'preferences';
109     
110     /**
111      * constant for preferences registry
112      */
113     const SCHEDULER = 'scheduler';
114     
115     /**
116      * constant temp dir registry
117      */
118     const TMPDIR = 'tmpdir';
119     
120     /**
121      * constant temp dir registry
122      */
123     const FILESDIR = 'filesdir';
124     
125     /**
126      * constant for request method registry
127      */
128     const METHOD = 'method';
129     
130     /**
131      * const PDO_MYSQL
132      *
133      */
134     const PDO_MYSQL = 'Pdo_Mysql';
135     
136     /**
137      * minimal version of MySQL supported
138      */
139     const MYSQL_MINIMAL_VERSION = '5.0.0';
140     
141     /**
142      * const PDO_PGSQL
143      *
144      */
145     const PDO_PGSQL = 'Pdo_Pgsql';
146     
147     /**
148      * minimal version of PostgreSQL supported
149      */
150     const PGSQL_MINIMAL_VERSION = '8.4.8';
151
152     /**
153      * const PDO_OCI
154      *
155      */
156     const PDO_OCI = 'Pdo_Oci';
157     
158     /**
159      * const ORACLE
160      * Zend_Db adapter name for the oci8 driver.
161      *
162      */
163     const ORACLE = 'Oracle';
164     
165     /**
166      * minimal version of Oracle supported
167      */
168     const ORACLE_MINIMAL_VERSION = '9.0.0';
169
170     /**
171      * Key for storing server plugins into cache
172      *
173      * @var string
174      */
175     const TINEBASE_SERVER_PLUGINS = 'Tinebase_Server_Plugins';
176
177     /**
178      * name of frontend server class
179      *
180      * @var string
181      */
182     const SERVER_CLASS_NAME = 'serverclassname';
183
184     /**
185      * Application Instance Cache
186      * @var array
187      */
188     protected static $appInstanceCache = array();
189     
190     /**
191      * current cache status, maybe NULL / uninitialized, true / enabled, false / disabled
192      * @var boolean
193      */
194     protected static $cacheStatus = NULL;
195     
196     /**
197      * variable to cache value of logLevel during request
198      * 
199      * @var int
200      */
201     protected static $logLevel = null;
202
203     /**
204      * Server classes provided by applications
205      *
206      * @var array
207      */
208     protected static $_serverPlugins = array();
209
210     /**
211      * contains the delegates fetched from configuration
212      *
213      * @var array
214      */
215     protected static $_delegatorCache = array();
216
217     /******************************* DISPATCH *********************************/
218     
219     /**
220      * dispatch request
221      */
222     public static function dispatchRequest()
223     {
224         $request = new \Zend\Http\PhpEnvironment\Request();
225         self::set(self::REQUEST, $request);
226         
227         // check transaction header
228         if ($request->getHeaders()->has('X-TINE20-TRANSACTIONID')) {
229             $transactionId = $request->getHeaders()->get('X-TINE20-TRANSACTIONID')->getFieldValue();
230             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
231                 . " Client transaction $transactionId");
232             Tinebase_Log_Formatter::setPrefix(substr($transactionId, 0, 5));
233         }
234         
235         $server = self::getDispatchServer($request);
236         
237         $server->handle($request);
238         $method = get_class($server) . '::' . $server->getRequestMethod();
239         self::set(self::METHOD, $method);
240         
241         self::finishProfiling();
242         self::getDbProfiling();
243     }
244
245     /**
246      * dispatch request
247      *
248      * @param \Zend\Http\Request $request
249      * @return null|Tinebase_Server_Interface
250      * @throws Tinebase_Exception
251      */
252     public static function getDispatchServer(\Zend\Http\Request $request)
253     {
254         // Test server conditions from server plugins
255         foreach (self::_getServerPlugins() as $serverPlugin){
256             $server = call_user_func_array(array($serverPlugin,'getServer'), array($request));
257             
258             if ($server instanceof Tinebase_Server_Interface) {
259                 Tinebase_Core::set('serverclassname', get_class($server));
260                 
261                 return $server;
262             }
263         }
264
265         if (Tinebase_Core::isLogLevel(Zend_Log::NOTICE)) {
266              Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . " Request: " . $request->toString());
267         }
268         throw new Tinebase_Exception('No valid server found for request');
269     }
270     
271     /**
272      * returns TRUE if request is HTTPS
273      * 
274      * @return boolean
275      */
276     public static function isHttpsRequest()
277     {
278         return (! empty($_SERVER['HTTPS']) && strtoupper($_SERVER['HTTPS']) != 'OFF');
279     }
280     
281     /**
282      * enable profiling
283      * - supports xhprof
284      */
285     public static function enableProfiling()
286     {
287         if (! self::getConfig() || ! self::getConfig()->profiler) {
288             return;
289         }
290         
291         $config = self::getConfig()->profiler;
292         
293         if ($config && $config->xhprof) {
294             $XHPROF_ROOT = $config->path ? $config->path : '/usr/share/php5-xhprof';
295             if (file_exists($XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php")) {
296                 define('XHPROF_LIB_ROOT', $XHPROF_ROOT . '/xhprof_lib');
297                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Enabling xhprof');
298                 xhprof_enable(XHPROF_FLAGS_MEMORY);
299             } else {
300                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Could not find xhprof lib root');
301             }
302         } 
303     }
304
305     /**
306      * finish profiling / save profiling data to a file
307      * - supports xhprof
308      */
309     public static function finishProfiling()
310     {
311         if (! self::getConfig() || ! self::getConfig()->profiler) {
312             return;
313         }
314         
315         $config = self::getConfig()->profiler;
316         $method = self::get(self::METHOD);
317     
318         if ($config->xhprof) {
319             $xhprof_data = xhprof_disable();
320             
321             if ($config->method) {
322                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Filtering xhprof profiling method: ' . $config->method);
323                 if (! preg_match($config->method, $method)) {
324                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Method mismatch, do not save profiling info.');
325                     return;
326                 }
327             }
328             
329             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Saving xhprof profiling run for method ' . $method);
330             
331             if (! defined('XHPROF_LIB_ROOT')) {
332                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . '  ' . print_r($xhprof_data, TRUE));
333             } else {
334                 /** @noinspection PhpIncludeInspection */
335                 include_once XHPROF_LIB_ROOT . "/utils/xhprof_lib.php";
336                 /** @noinspection PhpIncludeInspection */
337                 include_once XHPROF_LIB_ROOT . "/utils/xhprof_runs.php";
338                 /** @noinspection PhpUndefinedClassInspection */
339                 $xhprof_runs = new XHProfRuns_Default();
340                 /** @noinspection PhpUndefinedMethodInspection */
341                 $xhprof_runs->save_run($xhprof_data, "tine");
342             }
343         }
344     }
345     
346     /******************************* APPLICATION ************************************/
347
348     /**
349      * returns an instance of the controller of an application
350      *
351      * ATTENTION if ever refactored, this is called via ActionQueue with the single parameter 'Tinebase_FOO_User' to get Tinebase_User (triggered in Tinebase_User_Sql::deleteUserInSqlBackend()
352      * Tinebase_FOO_Filesystem
353      *
354      * @param   string $_applicationName appname / modelname
355      * @param   string $_modelName
356      * @param boolean $_ignoreACL
357      * @return Tinebase_Controller_Abstract|Tinebase_Controller_Record_Abstract the controller of the application
358      * @throws Tinebase_Exception_AccessDenied
359      * @throws Tinebase_Exception_NotFound
360      */
361     public static function getApplicationInstance($_applicationName, $_modelName = '', $_ignoreACL = FALSE)
362     {
363         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
364             . ' Params: application: ' . $_applicationName . ' / model: ' . $_modelName);
365         
366         $cacheKey = $_applicationName . '_' . $_modelName . '_' . ($_ignoreACL?1:0);
367         if (isset(self::$appInstanceCache[$cacheKey])) {
368             return self::$appInstanceCache[$cacheKey];
369         }
370
371         $extract = Tinebase_Application::extractAppAndModel($_applicationName, $_modelName);
372         $appName = $extract['appName'];
373         $modelName = $extract['modelName'];
374
375         $controllerName = ucfirst((string) $appName);
376         if ($appName !== 'Tinebase' || ($appName === 'Tinebase' && ! $modelName)) {
377             // only app controllers are called "App_Controller_Model"
378             $controllerName .= '_Controller';
379         }
380         
381         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
382             . ' controllerName: ' . $controllerName);
383
384         if (! empty($modelName)) {
385             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
386                 . ' Checking for model controller ...');
387             
388             $modelName = preg_replace('/^' . $appName . '_' . 'Model_/', '', $modelName);
389             $controllerNameModel = $controllerName . '_' . $modelName;
390             if (! class_exists($controllerNameModel)) {
391                 throw new Tinebase_Exception_NotFound('No Application Controller found (checked class ' . $controllerNameModel . ')!');
392             } else {
393                 $controllerName = $controllerNameModel;
394             }
395         } else if (! class_exists($controllerName)) {
396             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
397                 . ' Use generic application controller');
398
399             $controller = new Tinebase_Application_Controller($appName);
400         }
401         
402         if (! $_ignoreACL && is_object(Tinebase_Core::getUser()) && ! Tinebase_Core::getUser()->hasRight($appName, Tinebase_Acl_Rights_Abstract::RUN)) {
403             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
404                 . ' User ' . Tinebase_Core::getUser()->accountDisplayName . '/' . Tinebase_Core::getUser()->getId() . ' has no right to access ' . $appName);
405             throw new Tinebase_Exception_AccessDenied('No right to access application ' . $appName);
406         }
407
408         if (! isset($controller)) {
409             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
410                 . ' Getting instance of ' . $controllerName);
411
412             $controller = call_user_func(array($controllerName, 'getInstance'));
413             self::$appInstanceCache[$cacheKey] = $controller;
414         }
415         
416         return $controller;
417     }
418     
419     /******************************* SETUP ************************************/
420
421     /**
422      * init tine framework
423      */
424     public static function initFramework()
425     {
426         // avoid autostart of sessions
427         Zend_Session::setOptions(array(
428             'strict' => true
429         ));
430         
431         Tinebase_Core::setupTempDir();
432         
433         Tinebase_Core::setupStreamWrapper();
434         
435         //Cache must be setup before User Locale because otherwise Zend_Locale tries to setup 
436         //its own cache handler which might result in a open_basedir restriction depending on the php.ini settings
437         Tinebase_Core::setupCache();
438
439         Tinebase_Core::setupBuildConstants();
440         
441         // setup a temporary user locale. This will be overwritten later but we 
442         // need to handle exceptions during initialisation process such as session timeout
443         // @todo add fallback locale to config file
444         Tinebase_Core::set('locale', new Zend_Locale('en_US'));
445         
446         Tinebase_Core::setupUserLocale();
447         
448         Tinebase_Core::enableProfiling();
449         
450         if (PHP_SAPI !== 'cli') {
451             header('X-API: http://www.tine20.org/apidocs/tine20/');
452             if (isset($_SERVER['HTTP_X_TRANSACTIONID'])) {
453                 header('X-TransactionID: ' . substr($_SERVER['HTTP_X_TRANSACTIONID'], 1, -1) . ';' . $_SERVER['SERVER_NAME'] . ';16.4.5009.816;' . date('Y-m-d H:i:s') . ' UTC;265.1558 ms');
454             }
455         }
456     }
457     
458     /**
459      * start core session
460      *
461      * @throws Exception
462      */
463     public static function startCoreSession()
464     {
465         if (! Tinebase_Session::isStarted()) {
466             Tinebase_Session::setSessionBackend();
467             Zend_Session::start();
468         }
469
470         $coreSession = Tinebase_Session::getSessionNamespace();
471         
472         if (isset($coreSession->currentAccount)) {
473             self::set(self::USER, $coreSession->currentAccount);
474         }
475         
476         if (!isset($coreSession->jsonKey)) {
477             $coreSession->jsonKey = Tinebase_Record_Abstract::generateUID();
478         }
479         self::set('jsonKey', $coreSession->jsonKey);
480     }
481
482     /**
483      * return current session id
484      *
485      * @param boolean $generateUid
486      * @return mixed|null
487      */
488     public static function getSessionId($generateUid = true)
489     {
490         if (! self::isRegistered(self::SESSIONID)) {
491             $sessionId = null;
492             // TODO allow to access Tinebase/Core methods with Setup session and remove this workaround
493             if (Tinebase_Session::isStarted() && ! Tinebase_Session::isSetupSession()) {
494                 $sessionId = Tinebase_Session::getId();
495             }
496             if (empty($sessionId)) {
497                 $sessionId = 'NOSESSION';
498                 if ($generateUid) {
499                     $sessionId .= Tinebase_Record_Abstract::generateUID(31);
500                 }
501             }
502             self::set(self::SESSIONID, $sessionId);
503         }
504
505         return self::get(self::SESSIONID);
506     }
507     
508     /**
509      * initializes the build constants like buildtype, package information, ...
510      */
511     public static function setupBuildConstants()
512     {
513         if (defined('TINE20_BUILDTYPE')) {
514             // only define constants once
515             return;
516         }
517         $config = self::getConfig();
518
519         define('TINE20_BUILDTYPE', strtoupper($config->get('buildtype', 'DEVELOPMENT')));
520         define('TINE20_CODENAME', Tinebase_Helper::getDevelopmentRevision());
521         define('TINE20_PACKAGESTRING', 'none');
522         define('TINE20_RELEASETIME', 'none');
523     }
524     
525     /**
526      * tines error exception handler for catchable fatal errors
527      *
528      * NOTE: PHP < 5.3 don't throws exceptions for Catchable fatal errors per default,
529      * so we convert them into exceptions manually
530      *
531      * @param integer $severity
532      * @param string $errstr
533      * @param string $errfile
534      * @param integer $errline
535      * @throws ErrorException
536      */
537     public static function errorHandler($severity, $errstr, $errfile, $errline)
538     {
539         if (error_reporting() == 0) {
540             return;
541         }
542         
543         $logLine = " $errstr in {$errfile}::{$errline} ($severity)";
544         $e = new Exception('just to get trace');
545         $trace = $e->getTraceAsString();
546         
547         switch ($severity) {
548             case E_COMPILE_ERROR:
549             case E_CORE_ERROR:
550             case E_ERROR:
551             case E_PARSE:
552             case E_RECOVERABLE_ERROR:
553             case E_USER_ERROR:
554                 throw new ErrorException($errstr, 0, $severity, $errfile, $errline);
555                 break;
556                 
557             case E_COMPILE_WARNING:
558             case E_CORE_WARNING:
559             case E_USER_WARNING:
560             case E_WARNING:
561                 if (Tinebase_Core::isRegistered(Tinebase_Core::LOGGER)) {
562                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . $logLine);
563                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
564                 } else {
565                     error_log(__METHOD__ . '::' . __LINE__ . $logLine);
566                     error_log(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
567                 }
568                 break;
569                 
570             case E_NOTICE:
571             case E_STRICT:
572             case E_USER_NOTICE:
573             default:
574                 if (Tinebase_Core::isRegistered(Tinebase_Core::LOGGER)) {
575                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . $logLine);
576                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
577                 } else {
578                     error_log(__METHOD__ . '::' . __LINE__ . $logLine);
579                     error_log(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
580                 }
581                 break;
582         }
583     }
584
585     /**
586      * initializes the config
587      */
588     public static function setupConfig()
589     {
590         self::set(self::CONFIG, Tinebase_Config::getInstance());
591     }
592
593     /**
594      * setup temp dir registry setting retrieved by {@see _getTempDir()}
595      *
596      * @return void
597      */
598     public static function setupTempDir()
599     {
600         self::set(self::TMPDIR, self::guessTempDir());
601     }
602
603     /**
604      * figure out temp directory:
605      * config.inc.php > sys_get_temp_dir > session_save_path > /tmp
606      *
607      * @param array $config
608      * @return String
609      */
610     public static function guessTempDir($config = null)
611     {
612         if ($config === null) {
613             $config = self::getConfig();
614             $tmpdir = $config->tmpdir !== Tinebase_Model_Config::NOTSET ? $config->tmpdir : null;
615         } else {
616             $tmpdir = isset($config['tmpdir']) ? $config['tmpdir'] : null;
617         }
618
619         if (! $tmpdir || !@is_writable($tmpdir)) {
620             $tmpdir = sys_get_temp_dir();
621             if (empty($tmpdir) || !@is_writable($tmpdir)) {
622                 $tmpdir = session_save_path();
623                 if (empty($tmpdir) || !@is_writable($tmpdir)) {
624                     $tmpdir = '/tmp';
625                 }
626             }
627         }
628         
629         return $tmpdir;
630     }
631     
632     /**
633      * initializes the logger
634      *
635      * @param $_defaultWriter Zend_Log_Writer_Abstract default log writer
636      */
637     public static function setupLogger(Zend_Log_Writer_Abstract $_defaultWriter = NULL)
638     {
639         $config = self::getConfig();
640         $logger = new Tinebase_Log();
641         
642         if (isset($config->logger) && $config->logger->active) {
643             try {
644                 $logger->addWriterByConfig($config->logger);
645                 if ($config->logger->additionalWriters) {
646                     foreach ($config->logger->additionalWriters as $writerConfig) {
647                         $logger->addWriterByConfig($writerConfig);
648                     }
649                 }
650             } catch (Exception $e) {
651                 error_log("Tine 2.0 can't setup the configured logger! The Server responded: $e");
652                 $writer = ($_defaultWriter === NULL) ? new Zend_Log_Writer_Null() : $_defaultWriter;
653                 $logger->addWriter($writer);
654             }
655             
656             // For saving log into syslog too, create a key syslog into logger (value does not matter)
657             if ((bool) $config->logger->syslog){
658                 $writer = new Zend_Log_Writer_Syslog(array(
659                         'application'   => 'Tine 2.0'
660                 ));
661                 $prio = ($config->logger->priority) ? (int) $config->logger->priority : 3;
662                 $filter = new Zend_Log_Filter_Priority($prio);
663                 $writer->addFilter($filter);
664                 $logger->addWriter($writer);
665             }
666         } else {
667             $writer = new Zend_Log_Writer_Syslog(array(
668                 'application'   => 'Tine 2.0'
669             ));
670             
671             $filter = new Zend_Log_Filter_Priority(Zend_Log::WARN);
672             $writer->addFilter($filter);
673             $logger->addWriter($writer);
674         }
675         
676         self::set(self::LOGGER, $logger);
677         
678         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) $logger->info(__METHOD__ . '::' . __LINE__ .' Logger initialized.'
679             . (isset($writer) ? ' Writer: ' . get_class($writer) : ''));
680         if (isset($config->logger) && Tinebase_Core::isLogLevel(Zend_Log::TRACE)) $logger->trace(__METHOD__ . '::' . __LINE__ 
681             .' Logger settings: ' . print_r($config->logger->toArray(), TRUE));
682     }
683     
684     /**
685      * setup the cache and add it to zend registry
686      *
687      * @param bool $_enabled disabled cache regardless what's configured in config.inc.php
688      * 
689      * @todo use the same config keys as Zend_Cache (backend + frontend) to simplify this
690      */
691     public static function setupCache($_enabled = true)
692     {
693         if ( self::$cacheStatus !== NULL && self::$cacheStatus === $_enabled ) {
694             return;
695         }
696         
697         $config = self::getConfig();
698         if ($config->caching && $config->caching->active) {
699             if (isset($config->caching->shared) && ($config->caching->shared === true)) {
700                 self::set(self::SHAREDCACHE, true);
701             } else {
702                 self::set(self::SHAREDCACHE, false);
703             }
704         }
705
706         $backendOptions = array();
707
708         // create zend cache
709         if ($_enabled === true && $config->caching && $config->caching->active) {
710             $logging = isset($config->caching->logging) ? $config->caching->logging : false;
711
712             if ($logging) {
713                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
714                     . " Cache logging enabled");
715                 $logger = self::getLogger();
716             } else {
717                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
718                     . " Cache logging disabled");
719                 $logger = null;
720             }
721
722             $frontendOptions = array(
723                 'lifetime'                  => ($config->caching->lifetime) ? $config->caching->lifetime : 7200,
724                 'automatic_serialization'   => true, // turn that off for more speed
725                 'caching'                   => true,
726                 'automatic_cleaning_factor' => 0,    // no garbage collection as this is done by a scheduler task
727                 'write_control'             => false, // don't read cache entry after it got written
728                 'logging'                   => $logging,
729                 'logger'                    => $logger,
730             );
731             
732             $backendType = ($config->caching->backend) ? ucfirst($config->caching->backend) : 'File';
733             $backendOptions = ($config->caching->backendOptions) ? $config->caching->backendOptions->toArray() : array();
734             
735             if (empty($backendOptions)) {
736                 switch ($backendType) {
737                     case 'File':
738                         $backendOptions = array(
739                             'cache_dir'              => ($config->caching->path)     ? $config->caching->path     : Tinebase_Core::getTempDir(),
740                             'hashed_directory_level' => ($config->caching->dirlevel) ? $config->caching->dirlevel : 4, 
741                             'logging'                => $logging,
742                             'logger'                 => $logger,
743                         );
744                         break;
745                         
746                     case 'Memcached':
747                         $host = $config->caching->host ? $config->caching->host : (isset($config->caching->memcached->host)
748                             ? $config->caching->memcached->host : 'localhost');
749                         $port = $config->caching->port ? $config->caching->port : (isset($config->caching->memcached->port)
750                             ? $config->caching->memcached->port : 11211);
751                         $backendOptions = array(
752                             'servers' => array(
753                                 'host' => $host,
754                                 'port' => $port,
755                                 'persistent' => TRUE
756                         ));
757                         break;
758                         
759                     case 'Redis':
760                         $host = $config->caching->host ? $config->caching->host : ($config->caching->redis->host ? $config->caching->redis->host : 'localhost');
761                         $port = $config->caching->port ? $config->caching->port : ($config->caching->redis->port ? $config->caching->redis->port : 6379);
762                         if ($config->caching && $config->caching->prefix) {
763                             $prefix = $config->caching->prefix;
764                         } else if ($config->caching && $config->caching->redis && $config->caching->redis->prefix) {
765                             $prefix = $config->caching->redis->prefix;
766                         } else {
767                             $prefix = ($config->database && $config->database->tableprefix) ? $config->database->tableprefix : 'tine20';
768                         }
769                         $prefix .= '_CACHE_';
770                         $backendOptions = array(
771                             'servers' => array(
772                                 'host'   => $host,
773                                 'port'   => $port,
774                                 'prefix' => $prefix
775                         ));
776                         break;
777                         
778                     default:
779                         $backendOptions = array();
780                         break;
781                 }
782             }
783
784             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " cache of backend type '{$backendType}' enabled");
785             
786             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
787                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " Caching options: "
788                     . print_r($config->caching->toArray(), TRUE));
789             }
790             
791         } else {
792             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Cache disabled');
793             $backendType = 'Test';
794             $frontendOptions = array(
795                 'caching' => false
796             );
797         }
798         
799         // getting a Zend_Cache_Core object
800         try {
801             $cache = Zend_Cache::factory('Core', $backendType, $frontendOptions, $backendOptions);
802             
803         } catch (Exception $e) {
804             Tinebase_Exception::log($e);
805
806             $enabled = false;
807             if ('File' === $backendType && isset($backendOptions['cache_dir']) && ! is_dir($backendOptions['cache_dir'])) {
808                 if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Create cache directory and re-try');
809                 if (@mkdir($backendOptions['cache_dir'], 0770, true)) {
810                     $enabled = $_enabled;
811                 }
812             }
813
814             self::setupCache($enabled);
815             return;
816         }
817         
818         // some important caches
819         Zend_Locale::setCache($cache);
820         Zend_Translate::setCache($cache);
821         
822         Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);
823         self::set(self::CACHE, $cache);
824         self::$cacheStatus = $_enabled;
825     }
826     
827     /**
828      * setup stream wrapper for tine20:// prefix
829      * 
830      */
831     public static function setupStreamWrapper()
832     {
833         if (empty(Tinebase_Core::getConfig()->filesdir)) {
834             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
835                 . " Filesdir config value not set. tine20:// streamwrapper not registered, virtual filesystem not available.");
836             return;
837         }
838         if (! in_array('tine20', stream_get_wrappers())) {
839             stream_wrapper_register('tine20', 'Tinebase_FileSystem_StreamWrapper');
840         }
841     }
842     
843     /**
844      * initializes the database connection
845      */
846     public static function setupDatabaseConnection()
847     {
848         // check if database connection is setup already 
849         if (self::get(self::DB) instanceof Zend_Db_Adapter_Abstract) {
850             return self::get(self::DB);
851         }
852
853         // make sure cache is setup or we get in trouble in config_abstract. after db is available, cache needs to be present too!
854         self::getCache();
855         
856         $config = self::getConfig();
857         
858         if (!isset($config->database)) {
859             die ("Database section not found in central configuration file.\n");
860         }
861         
862         $dbConfig = $config->database;
863         
864         if (!empty($dbConfig->password)) {
865             self::getLogger()->getFormatter()->addReplacement($dbConfig->password);
866         }
867         
868         if (! defined('SQL_TABLE_PREFIX')) {
869             define('SQL_TABLE_PREFIX', $dbConfig->get('tableprefix') ? $dbConfig->get('tableprefix') : 'tine20_');
870         }
871         
872         $db = self::createAndConfigureDbAdapter($dbConfig->toArray());
873         Zend_Db_Table_Abstract::setDefaultAdapter($db);
874         
875         // place table prefix into the concrete adapter
876         $db->table_prefix = SQL_TABLE_PREFIX;
877         
878         self::set(self::DB, $db);
879         
880         return $db;
881     }
882     
883     /**
884      * create db adapter and configure it for Tine 2.0
885      * 
886      * @param array $dbConfigArray
887      * @param string $dbBackend
888      * @return Zend_Db_Adapter_Abstract
889      * @throws Tinebase_Exception_Backend_Database
890      * @throws Tinebase_Exception_UnexpectedValue
891      */
892     public static function createAndConfigureDbAdapter($dbConfigArray, $dbBackend = NULL)
893     {
894         if ($dbBackend === NULL) {
895             $constName = 'self::' . strtoupper($dbConfigArray['adapter']);
896             if (empty($dbConfigArray['adapter']) || ! defined($constName)) {
897                 self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Wrong/no db adapter configured (' . $dbConfigArray['adapter'] . '). Using default: ' . self::PDO_MYSQL);
898                 $dbBackend = self::PDO_MYSQL;
899                 $dbConfigArray['adapter'] = self::PDO_MYSQL;
900             } else {
901                 $dbBackend = constant($constName);
902             }
903         }
904
905         if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
906             . ' Creating ' . $dbBackend . ' DB adapter'
907             . (isset($dbConfigArray['dbname']) ? ' (db name: ' . $dbConfigArray['dbname'] . ')' : ''));
908
909         // set utf8 charset
910         $dbConfigArray['charset'] = 'UTF8';
911         $dbConfigArray['adapterNamespace'] = 'Tinebase_Backend_Sql_Adapter';
912         
913         switch ($dbBackend) {
914             case self::PDO_MYSQL:
915                 foreach (array('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY', 'PDO::MYSQL_ATTR_INIT_COMMAND') as $pdoConstant) {
916                     if (! defined($pdoConstant)) {
917                         throw new Tinebase_Exception_Backend_Database($pdoConstant . ' is not defined. Please check PDO extension.');
918                     }
919                 }
920                 
921                 // @todo set charset to utf8mb4 / @see 0008708: switch to mysql utf8mb4
922                 
923                 // force some driver options
924                 $dbConfigArray['driver_options'] = array(
925                     PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => FALSE,
926                 );
927                 $dbConfigArray['options']['init_commands'] = array(
928                     "SET time_zone = '+0:00'",
929                     "SET SQL_MODE = 'STRICT_ALL_TABLES'",
930                     "SET SESSION group_concat_max_len = 4294967295"
931                 );
932                 $db = Zend_Db::factory('Pdo_Mysql', $dbConfigArray);
933                 break;
934                 
935             case self::PDO_OCI:
936                 $db = Zend_Db::factory('Pdo_Oci', $dbConfigArray);
937                 break;
938                 
939             case self::ORACLE:
940                 $db = Zend_Db::factory(self::ORACLE, $dbConfigArray);
941                 /** @noinspection PhpUndefinedMethodInspection */
942                 $db->supportPositionalParameters(true);
943                 /** @noinspection PhpUndefinedMethodInspection */
944                 $db->setLobAsString(true);
945                 break;
946                 
947             case self::PDO_PGSQL:
948                 unset($dbConfigArray['adapter']);
949                 unset($dbConfigArray['tableprefix']);
950                 if (empty($dbConfigArray['port'])) {
951                     $dbConfigArray['port'] = 5432;
952                 }
953                 $dbConfigArray['options']['init_commands'] = array(
954                     "SET timezone = '+0:00'"
955                 );
956                 $db = Zend_Db::factory('Pdo_Pgsql', $dbConfigArray);
957                 
958                 break;
959                 
960             default:
961                 throw new Tinebase_Exception_UnexpectedValue('Invalid database adapter defined. Please set adapter to ' . self::PDO_MYSQL . ' or ' . self::PDO_OCI . ' in config.inc.php.');
962                 break;
963         }
964         
965         return $db;
966     }
967     
968     /**
969      * get db profiling
970      * 
971      * Enable db profiling like this (in config.inc.php):
972      * 
973      *   'database' => 
974      *      array(
975      *         [...] // db connection params  
976      *         'profiler' => TRUE
977      *      ),
978      *   'profiler' =>
979      *      array(
980      *         'queryProfiles' => TRUE,
981      *         'queryProfilesDetails' => TRUE,
982      *         'user' => 'loginname',             // only profile this user 
983      *         'profilerFilterElapsedSecs' => 1,  // only show queries whose elapsed time is equal or greater than this
984      *      )
985      *    ),
986      * 
987      */
988     public static function getDbProfiling()
989     {
990         if (! self::getConfig() || ! self::getConfig()->database || ! (bool) self::getConfig()->database->profiler) {
991             return;
992         }
993         
994         $config = self::getConfig()->profiler;
995         
996         if ($config->user && is_object(self::getUser()) && $config->user !== self::getUser()->accountLoginName) {
997             return;
998         }
999         
1000         $profiler = Zend_Db_Table::getDefaultAdapter()->getProfiler();
1001         
1002         if (! empty($config->profilerFilterElapsedSecs)) {
1003             $profiler->setFilterElapsedSecs($config->profilerFilterElapsedSecs);
1004         }
1005         
1006         $data = array(
1007             'totalNumQueries' => $profiler->getTotalNumQueries(),
1008             'totalElapsedSec' => $profiler->getTotalElapsedSecs(),
1009             'longestTime'        => 0,
1010             'longestQuery'       => ''
1011         );
1012         
1013         if ($config && (bool) $config->queryProfiles) {
1014             $queryProfiles = $profiler->getQueryProfiles();
1015             if (is_array($queryProfiles)) {
1016                 $data['queryProfiles'] = array();
1017                 foreach ($queryProfiles as $profile) {
1018                     if ((bool) $config->queryProfilesDetails) {
1019                         $data['queryProfiles'][] = array(
1020                             'query'       => $profile->getQuery(),
1021                             'elapsedSecs' => $profile->getElapsedSecs(),
1022                         );
1023                     }
1024                     
1025                     if ($profile->getElapsedSecs() > $data['longestTime']) {
1026                         $data['longestTime']  = $profile->getElapsedSecs();
1027                         $data['longestQuery'] = $profile->getQuery();
1028                     }
1029                 }
1030                 $profiler->clear();
1031             }
1032         }
1033         
1034         self::getLogger()->debug(__METHOD__ . ' (' . __LINE__ . ') value: ' . print_r($data, true));
1035     }
1036
1037     /**
1038      * sets the user locale
1039      *
1040      * @param  string $localeString
1041      */
1042     public static function setupUserLocale($localeString = 'auto')
1043     {
1044         try {
1045             $session = Tinebase_Session::getSessionNamespace();
1046         } catch (Zend_Session_Exception $zse) {
1047             $session = null;
1048         }
1049         
1050         if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " given localeString '$localeString'");
1051         
1052         // get locale object from session or ...
1053         if (   $session !== NULL
1054             && isset($session->userLocale)
1055             && is_object($session->userLocale)
1056             && ($session->userLocale->toString() === $localeString || $localeString === 'auto')
1057         ) {
1058             $locale = $session->userLocale;
1059
1060             if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
1061                 . " Got locale from session : " . (string)$locale);
1062             
1063         // ... create new locale object
1064         } else {
1065             if ($localeString === 'auto') {
1066                 // check if cookie or pref with language is available
1067                 if (isset($_COOKIE['TINE20LOCALE'])) {
1068                     $localeString = $_COOKIE['TINE20LOCALE'];
1069                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
1070                         . " Got locale from cookie: '$localeString'");
1071                     
1072                 } elseif (isset($session->currentAccount)) {
1073                     $localeString = self::getPreference()->getValue(Tinebase_Preference::LOCALE, 'auto');
1074                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1075                         . " Got locale from preference: '$localeString'");
1076                 } else {
1077                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1078                         . " Try to detect the locale of the user (browser, environment, default)");
1079                 }
1080             }
1081             
1082             $locale = Tinebase_Translation::getLocale($localeString);
1083             
1084             // save in session
1085             if ($session !== NULL) {
1086                 $session->userLocale = $locale;
1087             }
1088             
1089             // check if the detected locale should be saved in preferences
1090             if ($localeString === 'auto' && is_object(Tinebase_Core::getUser()) && (string)$locale !== 'en') {
1091                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
1092                     . " Saving locale: " . (string)$locale);
1093                 self::getPreference()->{Tinebase_Preference::LOCALE} = (string)$locale;
1094             }
1095         }
1096         
1097         // save in registry
1098         self::set('locale', $locale);
1099         
1100         $localeString = (string)$locale;
1101         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) self::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Setting user locale: " . $localeString);
1102         
1103         // set correct ctype locale, to make sure that the filesystem functions like basename() are working correctly with utf8 chars
1104         $ctypeLocale = setlocale(LC_CTYPE, 0);
1105         if (! preg_match('/utf-?8/i', $ctypeLocale)) {
1106             // use en_US as fallback locale if region string is missing
1107             $newCTypeLocale = ((strpos($localeString, '_') !== FALSE) ? $localeString : 'en_US') . '.UTF8';
1108             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1109                 . ' Setting CTYPE locale from "' . $ctypeLocale . '" to "' . $newCTypeLocale . '".');
1110             setlocale(LC_CTYPE, $newCTypeLocale);
1111         }
1112     }
1113
1114     /**
1115      * intializes the timezone handling
1116      *
1117      * @param  string $_timezone
1118      * @param  bool   $_saveaspreference
1119      * @return string
1120      */
1121     public static function setupUserTimezone($_timezone = null, $_saveaspreference = FALSE)
1122     {
1123         try {
1124             $session = Tinebase_Session::getSessionNamespace();
1125         } catch (Zend_Session_Exception $zse) {
1126             $session = null;
1127         }
1128         
1129         // get timezone from session, parameter or preference
1130         if ($_timezone === null && $session instanceof Zend_Session_Namespace && isset($session->timezone)) {
1131             $timezone = $session->timezone;
1132         } else {
1133             $timezone = $_timezone;
1134         }
1135         if ($timezone === null) {
1136             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1137                 . ' get timezone from preferences');
1138             $timezone = self::getPreference()->getValue(Tinebase_Preference::TIMEZONE);
1139         }
1140         
1141         // set timezone in registry, session and preference
1142         self::set(self::USERTIMEZONE, $timezone);
1143         if ($session instanceof Zend_Session_Namespace && Tinebase_Session::isWritable()) {
1144             $session->timezone = $timezone;
1145         }
1146         if ($_saveaspreference) {
1147             self::getPreference()->setValue(Tinebase_Preference::TIMEZONE, $timezone);
1148         }
1149         
1150         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1151                 . ' User timezone: ' . $timezone);
1152         
1153         return $timezone;
1154     }
1155
1156     /**
1157      * set php execution life (max) time
1158      *
1159      * @param int $_seconds
1160      * @return int old max execution time in seconds
1161      */
1162     public static function setExecutionLifeTime($_seconds)
1163     {
1164         $oldMaxExcecutionTime = ini_get('max_execution_time');
1165         
1166         if ($oldMaxExcecutionTime > 0) {
1167             if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
1168                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' setting execution life time to: ' . $_seconds);
1169             }
1170             set_time_limit($_seconds);
1171         }
1172         
1173         return $oldMaxExcecutionTime;
1174     }
1175     
1176     /**
1177      * set php memory (max) limit
1178      *
1179      * @param string $_limit
1180      * @return string old max memory limit
1181      */
1182     public static function setMemoryLimit($_limit)
1183     {
1184         $oldMaxMemoryLimit = ini_get('memory_limit');
1185         
1186         if (! empty($oldMaxMemoryLimit)) {
1187             if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1188                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' setting memory limit to: ' . $_limit);
1189             }
1190             ini_set('memory_limit', $_limit);
1191         }
1192         
1193         return $oldMaxMemoryLimit;
1194     }
1195     
1196     /**
1197      * log memory usage
1198      *
1199      */
1200     public static function logMemoryUsage()
1201     {
1202         if (function_exists('memory_get_peak_usage')) {
1203             $memory = memory_get_peak_usage(true);
1204         } else {
1205             $memory = memory_get_usage(true);
1206         }
1207         
1208         return  ' Memory usage: ' . ($memory / 1024 / 1024) . ' MB';
1209     }
1210     
1211     public static function logCacheSize()
1212     {
1213         if(function_exists('realpath_cache_size')) {
1214             $realPathCacheSize = realpath_cache_size();
1215         } else {
1216             $realPathCacheSize = 'unknown';
1217         }
1218         
1219         return ' Real patch cache size: ' . $realPathCacheSize;
1220     }
1221     
1222     /******************************* REGISTRY ************************************/
1223
1224     /**
1225      * get a value from the registry
1226      * @param $index
1227      * @return mixed|null
1228      */
1229     public static function get($index)
1230     {
1231         try {
1232             return Zend_Registry::get($index);
1233         } catch (Zend_Exception $ze) {
1234             return null;
1235         }
1236     }
1237
1238     /**
1239      * set a registry value
1240      *
1241      * @param string $index
1242      * @param mixed $value
1243      * @throws Tinebase_Exception_InvalidArgument
1244      */
1245     public static function set($index, $value)
1246     {
1247         if ($index === self::USER) {
1248             if ($value === null) {
1249                 throw new Tinebase_Exception_InvalidArgument('Invalid user object!');
1250             }
1251             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1252                 if ($value instanceof Tinebase_Model_FullUser) {
1253                     $userString =  $value->accountLoginName;
1254                 } else if ($value instanceof Tinebase_Model_User) {
1255                     $userString = $value->accountDisplayName;
1256                 } else {
1257                     $userString = var_export($value, true);
1258                 }
1259                 
1260                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Setting user ' . $userString);
1261                 Tinebase_Log_Formatter::resetUsername();
1262             }
1263         }
1264         
1265         Zend_Registry::set($index, $value);
1266     }
1267
1268     /**
1269      * checks a registry value
1270      *
1271      * @param string $index
1272      * @return boolean
1273      */
1274     public static function isRegistered($index)
1275     {
1276         return Zend_Registry::isRegistered($index);
1277     }
1278
1279     /**
1280      * Returns the auth typ from config or default value
1281      *
1282      * @return String
1283      */
1284     public static function getAuthType()
1285     {
1286         if (isset(Tinebase_Core::getConfig()->authentication)) {
1287             $authType = Tinebase_Core::getConfig()->authentication->get('backend', Tinebase_Auth::SQL);
1288         } else {
1289             $authType = Tinebase_Auth::SQL;
1290         }
1291
1292         return ucfirst($authType);
1293     }
1294     
1295     /**
1296      * get config from the registry
1297      *
1298      * @return Tinebase_Config
1299      */
1300     public static function getConfig()
1301     {
1302         if (! self::get(self::CONFIG)) {
1303             self::setupConfig();
1304         }
1305         return self::get(self::CONFIG);
1306     }
1307     
1308     /**
1309      * get max configured loglevel
1310      * 
1311      * @return integer
1312      */
1313     public static function getLogLevel()
1314     {
1315         if (! ($logLevel = self::get(self::LOGLEVEL))) {
1316             $config = self::getConfig();
1317             $logLevel = Tinebase_Log::getMaxLogLevel(isset($config->logger) ? $config->logger : NULL);
1318             self::set(self::LOGLEVEL, $logLevel);
1319         }
1320         
1321         return $logLevel;
1322     }
1323     
1324     /**
1325      * check if given loglevel should be logged
1326      * 
1327      * @param  integer $_prio
1328      * @return boolean
1329      */
1330     public static function isLogLevel($_prio)
1331     {
1332         if (! isset(self::$logLevel) || self::$logLevel === 0 ) {
1333             self::$logLevel = self::getLogLevel();
1334         }
1335
1336         return self::$logLevel >= $_prio;
1337     }
1338     
1339     /**
1340      * get config from the registry
1341      *
1342      * @return Tinebase_Log the logger
1343      */
1344     public static function getLogger()
1345     {
1346         if (! self::get(self::LOGGER) instanceof Tinebase_Log) {
1347             Tinebase_Core::setupLogger();
1348         }
1349         
1350         return self::get(self::LOGGER);
1351     }
1352     
1353     /**
1354      * get cache from the registry
1355      *
1356      * @return Zend_Cache_Core the cache
1357      */
1358     public static function getCache()
1359     {
1360         if (! self::get(self::CACHE) instanceof Zend_Cache_Core) {
1361             self::$cacheStatus = null;
1362             Tinebase_Core::setupCache();
1363         }
1364         return self::get(self::CACHE);
1365     }
1366     
1367     /**
1368      * get credentials cache from the registry or initialize it
1369      *
1370      * @return  Tinebase_Model_CredentialCache
1371      */
1372     public static function getUserCredentialCache()
1373     {
1374         if (! self::get(self::USERCREDENTIALCACHE) instanceof Tinebase_Model_CredentialCache && self::getUser()) {
1375             try {
1376                 $cache = Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->getCache();
1377             } catch (Zend_Db_Statement_Exception $zdse) {
1378                 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1379                     . " Could not get credential cache adapter, perhaps Tine 2.0 is not installed yet");
1380                 $cache = NULL;
1381             }
1382         
1383             if ($cache !== NULL) {
1384                 self::set(self::USERCREDENTIALCACHE, $cache);
1385             }
1386         }
1387         
1388         return self::get(self::USERCREDENTIALCACHE);
1389     }
1390     
1391     /**
1392      * get locale from the registry
1393      *
1394      * @return Zend_Locale
1395      */
1396     public static function getLocale()
1397     {
1398         return self::get(self::LOCALE);
1399     }
1400         
1401     /**
1402      * get current user account
1403      *
1404      * @return Tinebase_Model_FullUser the user account record
1405      */
1406     public static function getUser()
1407     {
1408         return self::get(self::USER);
1409     }
1410
1411     public static function unsetUser()
1412     {
1413         Zend_Registry::set(self::USER, null);
1414         Tinebase_Log_Formatter::resetUsername();
1415     }
1416
1417     /**
1418      * get current users timezone
1419      *
1420      * @return string the current users timezone string
1421      */
1422     public static function getUserTimezone()
1423     {
1424         if (!self::isRegistered(self::USERTIMEZONE) || ($return = self::get(self::USERTIMEZONE)) === NULL) {
1425             return Tinebase_Core::setupUserTimezone();
1426         }
1427         
1428         return $return;
1429     }
1430
1431     /**
1432      * get preferences instance by application name (create+save it to registry if it doesn't exist)
1433      *
1434      * @param string $_application
1435      * @param boolean $_throwException throws exception if class does not exist
1436      * @return Tinebase_Preference_Abstract
1437      * @throws Tinebase_Exception_NotFound
1438      */
1439     public static function getPreference($_application = 'Tinebase', $_throwException = FALSE)
1440     {
1441         $result = NULL;
1442
1443         if (self::isRegistered(self::PREFERENCES)) {
1444             $prefs = self::get(self::PREFERENCES);
1445             if (isset($prefs[$_application])) {
1446                 $result = $prefs[$_application];
1447             }
1448         } else {
1449             $prefs = array();
1450         }
1451
1452         if ($result === NULL) {
1453             $prefClassName = $_application . '_Preference';
1454             if (@class_exists($prefClassName)) {
1455                 $result = new $prefClassName();
1456                 $prefs[$_application] = $result;
1457                 self::set(self::PREFERENCES, $prefs);
1458             } else if ($_throwException) {
1459                 throw new Tinebase_Exception_NotFound('No preference class found for app ' . $_application);
1460             }
1461         }
1462
1463         return $result;
1464     }
1465     
1466     /**
1467      * get db adapter
1468      *
1469      * @return Zend_Db_Adapter_Abstract
1470      */
1471     public static function getDb()
1472     {
1473         if (! self::get(self::DB) instanceof Zend_Db_Adapter_Abstract) {
1474             Tinebase_Core::setupDatabaseConnection();
1475         }
1476         
1477         return self::get(self::DB);
1478     }
1479
1480     /**
1481      * checks if the DB is registered already
1482      *
1483      * @return bool
1484      */
1485     public static function hasDb()
1486     {
1487         return self::isRegistered(self::DB);
1488     }
1489
1490     /**
1491      * get db name
1492      *
1493      * @return string
1494      * @throws Tinebase_Exception
1495      */
1496     public static function getDbName()
1497     {
1498         if (! self::get(self::DBNAME)) {
1499             $db = self::getDb();
1500             $adapterName = get_class($db);
1501     
1502             if (empty($adapterName) || strpos($adapterName, '_') === FALSE) {
1503                 throw new Tinebase_Exception('Could not get DB adapter name.');
1504             }
1505     
1506             $adapterNameParts = explode('_', $adapterName);
1507             $type = array_pop($adapterNameParts);
1508     
1509             // special handling for Oracle
1510             $type = str_replace('Oci', self::ORACLE, $type);
1511             self::set(self::DBNAME, $type);
1512         }
1513         
1514         return self::get(self::DBNAME);
1515     }
1516     
1517     /**
1518      * get temp dir string (without PATH_SEP at the end)
1519      *
1520      * @return string
1521      */
1522     public static function getTempDir()
1523     {
1524         return self::get(self::TMPDIR);
1525     }
1526     
1527     /**
1528      * returns protocol + hostname
1529      * 
1530      * @return string
1531      */
1532     public static function getHostname()
1533     {
1534         $hostname = self::get('HOSTNAME');
1535         if (! $hostname) {
1536             $request = new Sabre\HTTP\Request();
1537             $hostname = strlen($request->getUri()) > 1 ?
1538                 str_replace($request->getUri(), '', $request->getAbsoluteUri()) :
1539                 $request->getAbsoluteUri();
1540
1541             self::set('HOSTNAME', $hostname);
1542         }
1543         
1544         return $hostname;
1545     }
1546
1547     /**
1548      * returns requested url part
1549      *
1550      * @param string $part
1551      * @return string
1552      */
1553     public static function getUrl($part = 'full')
1554     {
1555         $request = new Zend_Controller_Request_Http();
1556         $pathname = $request->getBasePath();
1557         $hostname = $request->getHttpHost();
1558         $protocol = $request->getScheme();
1559
1560         switch ($part) {
1561             case 'path':
1562                 $url = $pathname;
1563                 break;
1564             case 'host':
1565                 $url = $hostname;
1566                 break;
1567             case 'protocol':
1568                 $url = $protocol;
1569                 break;
1570             case 'full':
1571             default:
1572                 $url = $protocol . '://' . $hostname . $pathname;
1573                 break;
1574         }
1575
1576         return $url;
1577     }
1578
1579     /**
1580      * Singleton instance
1581      *
1582      * @return Zend_Scheduler
1583      */
1584     public static function getScheduler()
1585     {
1586         if (! self::get(self::SCHEDULER) instanceof Zend_Scheduler) {
1587             $scheduler =  new Zend_Scheduler();
1588             $scheduler->setBackend(new Zend_Scheduler_Backend_Db(array(
1589                 'DbAdapter' => self::getDb(),
1590                 'tableName' => SQL_TABLE_PREFIX . 'scheduler',
1591                 'taskClass' => 'Tinebase_Scheduler_Task'
1592             )));
1593             
1594             self::set(self::SCHEDULER, $scheduler);
1595         }
1596         return self::get(self::SCHEDULER);
1597     }
1598     
1599     /**
1600      * filter input string for database as some databases (looking at you, MySQL) can't cope with some chars
1601      * 
1602      * @param string $string
1603      * @return string
1604      *
1605      * @see 0008644: error when sending mail with note (wrong charset)
1606      * @see http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string/8215387#8215387
1607      * @see http://stackoverflow.com/questions/8491431/remove-4-byte-characters-from-a-utf-8-string
1608      */
1609     public static function filterInputForDatabase($string)
1610     {
1611         if (self::getDb() instanceof Zend_Db_Adapter_Pdo_Mysql) {
1612             $string = Tinebase_Helper::mbConvertTo($string);
1613             
1614             // remove 4 byte utf8
1615             $result = preg_replace('/[\xF0-\xF7].../s', '?', $string);
1616         } else {
1617             $result = $string;
1618         }
1619         
1620         return $result;
1621     }
1622     
1623     /**
1624      * checks if a system command exists. Works on POSIX systems.
1625      * 
1626      * @param string $name
1627      * @return bool
1628      */
1629     public static function systemCommandExists($name)
1630     {
1631         $ret = shell_exec('which ' . $name);
1632         return ! empty($ret);
1633     }
1634     
1635     /**
1636      * calls a system command with escapeshellcmd
1637      * 
1638      * @param string $cmd
1639      * @return bool
1640      */
1641     public static function callSystemCommand($cmd)
1642     {
1643         return shell_exec(escapeshellcmd($cmd));
1644     }
1645
1646     /**
1647      * Search server plugins from applications configuration
1648      *
1649      * @return array
1650      */
1651     protected static function _searchServerPlugins()
1652     {
1653         $cache = Tinebase_Core::getCache();
1654         
1655         if ($cache &&
1656             $plugins = $cache->load(self::TINEBASE_SERVER_PLUGINS)
1657         ) {
1658             return $plugins;
1659         }
1660
1661         if (! Setup_Controller::getInstance()->isInstalled('Tinebase')) {
1662             // get apps by scanning directory
1663             $applications = array();
1664             $d = dir(dirname(__DIR__));
1665             while (false !== ($entry = $d->read())) {
1666                 if ($entry[0] == '.') {
1667                     continue;
1668                 }
1669
1670                 if (ctype_upper($entry[0]) && is_dir($d->path . DIRECTORY_SEPARATOR . $entry)) {
1671                     $applications[] = $entry;
1672                 }
1673             }
1674             $d->close();
1675         } else {
1676             // get list of available applications
1677             $applications = Tinebase_Application::getInstance()->getApplicationsByState(Tinebase_Application::ENABLED)->name;
1678         }
1679
1680         // get list of plugins
1681         $plugins = array();
1682         foreach ($applications as $application) {
1683             $config = $application . '_Config';
1684             
1685             try {
1686                 if (@class_exists($config)) {
1687                     $reflectedClass = new ReflectionClass($config);
1688                     
1689                     if ($reflectedClass->isSubclassOf('Tinebase_Config_Abstract')) {
1690                         $plugins = array_merge(
1691                             $plugins,
1692                             call_user_func(array($config,'getServerPlugins'))
1693                         );
1694                     }
1695                 }
1696             } catch (Exception $e) {
1697                 Tinebase_Exception::log($e);
1698             }
1699         }
1700         
1701         // sort plugins by priority
1702         asort($plugins);
1703         
1704         $plugins = array_keys($plugins);
1705         
1706         if ($cache) {
1707             $cache->save($plugins, self::TINEBASE_SERVER_PLUGINS);
1708         }
1709         
1710         return $plugins;
1711     }
1712
1713     /**
1714      * Return server plugins ensuring that they were found
1715      *
1716      * @return array
1717      */
1718     protected static function _getServerPlugins()
1719     {
1720         if (empty(self::$_serverPlugins)) {
1721             self::$_serverPlugins = self::_searchServerPlugins();
1722         }
1723
1724         return self::$_serverPlugins;
1725     }
1726
1727     /**
1728      * returns TRUE if filesystem is available
1729      *
1730      *  - value is stored in session and registry for caching
1731      *
1732      * @return boolean
1733      */
1734     public static function isFilesystemAvailable()
1735     {
1736         $isFileSystemAvailable = self::get('FILESYSTEM');
1737         if ($isFileSystemAvailable === null) {
1738             try {
1739                 $session = Tinebase_Session::getSessionNamespace();
1740
1741                 if (isset($session->filesystemAvailable)) {
1742                     $isFileSystemAvailable = $session->filesystemAvailable;
1743
1744                     self::set('FILESYSTEM', $isFileSystemAvailable);
1745                     return $isFileSystemAvailable;
1746                 }
1747             } catch (Zend_Session_Exception $zse) {
1748                 $session = null;
1749             }
1750
1751             $isFileSystemAvailable = (!empty(Tinebase_Core::getConfig()->filesdir) && is_writeable(Tinebase_Core::getConfig()->filesdir));
1752
1753             if ($session instanceof Zend_Session_Namespace) {
1754                 if (Tinebase_Session::isWritable()) {
1755                     $session->filesystemAvailable = $isFileSystemAvailable;
1756                 }
1757             }
1758
1759             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
1760                 . ' Filesystem available: ' . ($isFileSystemAvailable ? 'yes' : 'no'));
1761
1762             self::set('FILESYSTEM', $isFileSystemAvailable);
1763         }
1764
1765         return $isFileSystemAvailable;
1766     }
1767
1768     /**
1769      * returns true if installation is in maintenance mode
1770      *
1771      * @return bool
1772      */
1773     public static function inMaintenanceMode()
1774     {
1775         $config = self::getConfig();
1776         return !! $config->maintenanceMode;
1777     }
1778
1779     /**
1780      * returns Tine 2.0 user agent string
1781      *
1782      * @param string $submodule
1783      * @return string
1784      */
1785     public static function getTineUserAgent($submodule = '')
1786     {
1787         return 'Tine 2.0 ' . $submodule . '(version ' . TINE20_CODENAME . ' - ' . TINE20_PACKAGESTRING . ')';
1788     }
1789
1790     /**
1791      * get http client
1792      *
1793      * @param null $uri
1794      * @param null $config
1795      * @return Zend_Http_Client
1796      */
1797     public static function getHttpClient($uri = null, $config = null)
1798     {
1799         $proxyConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::INTERNET_PROXY);
1800         if (! empty($proxyConfig)) {
1801             $proxyConfig['adapter'] = 'Zend_Http_Client_Adapter_Proxy';
1802             if (is_array($config)) {
1803                 $config = array_merge($proxyConfig);
1804             } else {
1805                 $config = $proxyConfig;
1806             }
1807         }
1808         $httpClient = new Zend_Http_Client($uri, $config);
1809
1810         return $httpClient;
1811     }
1812
1813     /**
1814      * returns the tinebase id
1815      *
1816      * @return string
1817      */
1818     public static function getTinebaseId()
1819     {
1820         if (! isset(self::$appInstanceCache['TinebaseId'])) {
1821             self::$appInstanceCache['TinebaseId'] = Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->getId();
1822         }
1823
1824         return self::$appInstanceCache['TinebaseId'];
1825     }
1826
1827     public static function unsetTinebaseId()
1828     {
1829         if (isset(self::$appInstanceCache['TinebaseId'])) {
1830             unset(self::$appInstanceCache['TinebaseId']);
1831         }
1832     }
1833
1834     /**
1835      * clear application instance cache
1836      */
1837     public static function clearAppInstanceCache()
1838     {
1839         self::$appInstanceCache = array();
1840     }
1841
1842     public static function getDelegate($applicationName, $configKey, $interfaceName)
1843     {
1844         if (!isset(static::$_delegatorCache[$configKey])) {
1845             $config = Tinebase_Config_Abstract::factory($applicationName);
1846             $delegate = $config->get($configKey);
1847             if (null === $delegate || ! class_exists($delegate) || ! in_array($interfaceName, class_implements($delegate))) {
1848                 $delegate = false;
1849             } else {
1850                 $delegate = new $delegate();
1851             }
1852             static::$_delegatorCache[$configKey] = $delegate;
1853         }
1854
1855         return static::$_delegatorCache[$configKey];
1856     }
1857
1858     public static function clearDelegatorCache()
1859     {
1860         static::$_delegatorCache = array();
1861     }
1862 }