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