improves user change logging
[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             throw new Tinebase_Exception_AccessDenied('No right to access application ' . $appName);
395         }
396         
397         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ 
398             . ' Getting instance of ' . $controllerName);
399         
400         $controller = call_user_func(array($controllerName, 'getInstance'));
401         self::$appInstanceCache[$cacheKey] = $controller;
402         
403         return $controller;
404     }
405     
406     /******************************* SETUP ************************************/
407
408     /**
409      * init tine framework
410      */
411     public static function initFramework()
412     {
413         Tinebase_Core::setupTempDir();
414         
415         Tinebase_Core::setupStreamWrapper();
416         
417         //Cache must be setup before User Locale because otherwise Zend_Locale tries to setup 
418         //its own cache handler which might result in a open_basedir restriction depending on the php.ini settings
419         Tinebase_Core::setupCache();
420         
421         Tinebase_Core::setupBuildConstants();
422         
423         Tinebase_Core::setupSession();
424         
425         if (Zend_Session::sessionExists()) {
426             Tinebase_Core::startSession();
427         }
428         
429         // setup a temporary user locale/timezone. This will be overwritten later but we 
430         // need to handle exceptions during initialisation process such as session timeout
431         // @todo add fallback locale to config file
432         Tinebase_Core::set('locale', new Zend_Locale('en_US'));
433         Tinebase_Core::set('userTimeZone', 'UTC');
434         
435         Tinebase_Core::setupUserCredentialCache();
436         
437         Tinebase_Core::setupUserTimezone();
438         
439         Tinebase_Core::setupUserLocale();
440         
441         Tinebase_Core::enableProfiling();
442         
443         if (PHP_SAPI !== 'cli') {
444             header('X-API: http://www.tine20.org/apidocs/tine20/');
445             if (isset($_SERVER['HTTP_X_TRANSACTIONID'])) {
446                 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');
447             }
448         }
449     }
450     
451     /**
452      * initializes the build constants like buildtype, package information, ...
453      */
454     public static function setupBuildConstants()
455     {
456         $config = self::getConfig();
457         define('TINE20_BUILDTYPE',     strtoupper($config->get('buildtype', 'DEVELOPMENT')));
458         define('TINE20_CODENAME',      getDevelopmentRevision());
459         define('TINE20_PACKAGESTRING', 'none');
460         define('TINE20_RELEASETIME',   'none');
461     }
462     
463     /**
464      * tines error exception handler for catchable fatal errors
465      *
466      * NOTE: PHP < 5.3 don't throws exceptions for Catchable fatal errors per default,
467      * so we convert them into exceptions manually
468      *
469      * @param integer $severity
470      * @param string $errstr
471      * @param string $errfile
472      * @param integer $errline
473      * @throws ErrorException
474      */
475     public static function errorHandler($severity, $errstr, $errfile, $errline)
476     {
477         if (error_reporting() == 0) {
478             return;
479         }
480
481         $logLine = " $errstr in {$errfile}::{$errline} ($severity)";
482         $e = new Exception('just to get trace');
483         $trace = $e->getTraceAsString();
484         
485         switch ($severity) {
486             case E_COMPILE_ERROR:
487             case E_CORE_ERROR:
488             case E_ERROR:
489             case E_PARSE:
490             case E_RECOVERABLE_ERROR:
491             case E_USER_ERROR:
492                 throw new ErrorException($errstr, 0, $severity, $errfile, $errline);
493                 break;
494
495             case E_COMPILE_WARNING:
496             case E_CORE_WARNING:
497             case E_USER_WARNING:
498             case E_WARNING:
499                 if (Tinebase_Core::isRegistered(Tinebase_Core::LOGGER)) {
500                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . $logLine);
501                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
502                 } else {
503                     error_log(__METHOD__ . '::' . __LINE__ . $logLine);
504                     error_log(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
505                 }
506                 break;
507
508             case E_NOTICE:
509             case E_STRICT:
510             case E_USER_NOTICE:
511             default:
512                 if (Tinebase_Core::isRegistered(Tinebase_Core::LOGGER)) {
513                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . $logLine);
514                     Tinebase_Core::getLogger()->notice(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
515                 } else {
516                     error_log(__METHOD__ . '::' . __LINE__ . $logLine);
517                     error_log(__METHOD__ . '::' . __LINE__ . ' ' . $trace);
518                 }
519                 break;
520         }
521     }
522
523     /**
524      * initializes the config
525      */
526     public static function setupConfig()
527     {
528         self::set(self::CONFIG, Tinebase_Config::getInstance());
529     }
530
531     /**
532      * setup temp dir registry setting retrieved by {@see _getTempDir()}
533      *
534      * @return void
535      */
536     public static function setupTempDir()
537     {
538         self::set(self::TMPDIR, self::guessTempDir());
539     }
540
541     /**
542      * figure out temp directory:
543      * config.inc.php > sys_get_temp_dir > session_save_path > /tmp
544      *
545      * @return String
546      */
547     public static function guessTempDir()
548     {
549         $config = self::getConfig();
550
551         $tmpdir = $config->tmpdir;
552         if ($tmpdir == Tinebase_Model_Config::NOTSET || !@is_writable($tmpdir)) {
553             $tmpdir = sys_get_temp_dir();
554             if (empty($tmpdir) || !@is_writable($tmpdir)) {
555                 $tmpdir = session_save_path();
556                 if (empty($tmpdir) || !@is_writable($tmpdir)) {
557                     $tmpdir = '/tmp';
558                 }
559             }
560         }
561
562         return $tmpdir;
563     }
564
565     /**
566      * initializes the logger
567      *
568      * @param $_defaultWriter Zend_Log_Writer_Abstract default log writer
569      */
570     public static function setupLogger(Zend_Log_Writer_Abstract $_defaultWriter = NULL)
571     {
572         $config = self::getConfig();
573         $logger = new Tinebase_Log();
574
575         if (isset($config->logger) && $config->logger->active) {
576             try {
577                 $logger->addWriterByConfig($config->logger);
578                 if ($config->logger->additionalWriters) {
579                     foreach ($config->logger->additionalWriters as $writerConfig) {
580                         $logger->addWriterByConfig($writerConfig);
581                     }
582                 }
583             } catch (Exception $e) {
584                 error_log("Tine 2.0 can't setup the configured logger! The Server responded: $e");
585                 $writer = ($_defaultWriter === NULL) ? new Zend_Log_Writer_Null() : $_defaultWriter;
586                 $logger->addWriter($writer);
587             }
588         } else {
589             $writer = new Zend_Log_Writer_Syslog(array(
590                 'application'   => 'Tine 2.0'
591             ));
592             
593             $filter = new Zend_Log_Filter_Priority(Zend_Log::WARN);
594             $writer->addFilter($filter);
595             $logger->addWriter($writer);
596         }
597         
598         self::set(self::LOGGER, $logger);
599
600         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) $logger->info(__METHOD__ . '::' . __LINE__ .' Logger initialized');
601         if (isset($loggerConfig) && Tinebase_Core::isLogLevel(Zend_Log::TRACE)) $logger->trace(__METHOD__ . '::' . __LINE__ 
602             .' Logger settings: ' . print_r($loggerConfig->toArray(), TRUE));
603     }
604
605     /**
606      * setup the cache and add it to zend registry
607      *
608      * @param bool $_enabled disabled cache regardless what's configured in config.inc.php
609      * 
610      * @todo use the same config keys as Zend_Cache (backend + frontend) to simplify this
611      */
612     public static function setupCache($_enabled = true)
613     {
614         $config = self::getConfig();
615         if ($config->caching && $config->caching->active) {
616             if (isset($config->caching->shared) && ($config->caching->shared === true)) {
617                 self::set(self::SHAREDCACHE, true);
618             } else {
619                 self::set(self::SHAREDCACHE, false);
620             }
621         }
622
623         // create zend cache
624         if ($_enabled === true && $config->caching && $config->caching->active) {
625             $frontendOptions = array(
626                 'lifetime'                  => ($config->caching->lifetime) ? $config->caching->lifetime : 7200,
627                 'automatic_serialization'   => true, // turn that off for more speed
628                 'caching'                   => true,
629                 'automatic_cleaning_factor' => 0,    // no garbage collection as this is done by a scheduler task
630                 'write_control'             => false, // don't read cache entry after it got written
631                 'logging'                   => (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)),
632                 'logger'                    => self::getLogger(),
633             );
634
635             $backendType = ($config->caching->backend) ? ucfirst($config->caching->backend) : 'File';
636             $backendOptions = ($config->caching->backendOptions) ? $config->caching->backendOptions->toArray() : false;
637
638             if (! $backendOptions) {
639                 switch ($backendType) {
640                     case 'File':
641                         $backendOptions = array(
642                             'cache_dir'              => ($config->caching->path)     ? $config->caching->path     : Tinebase_Core::getTempDir(),
643                             'hashed_directory_level' => ($config->caching->dirlevel) ? $config->caching->dirlevel : 4, 
644                             'logging'                => (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)),
645                             'logger'                 => self::getLogger(),
646                         );
647                         break;
648                         
649                     case 'Memcached':
650                         $host = $config->caching->host ? $config->caching->host : ($config->caching->memcached->host ? $config->caching->memcached->host : 'localhost');
651                         $port = $config->caching->port ? $config->caching->port : ($config->caching->memcached->port ? $config->caching->memcached->port : 11211);
652                         $backendOptions = array(
653                             'servers' => array(
654                                 'host' => $host,
655                                 'port' => $port,
656                                 'persistent' => TRUE
657                         ));
658                         break;
659                         
660                     case 'Redis':
661                         $host = $config->caching->host ? $config->caching->host : ($config->caching->redis->host ? $config->caching->redis->host : 'localhost');
662                         $port = $config->caching->port ? $config->caching->port : ($config->caching->redis->port ? $config->caching->redis->port : 6379);
663                         $prefix = (Setup_Controller::getInstance()->isInstalled('Tinebase')) ? Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->getId() : 'TINESETUP';
664                         $prefix .= '_CACHE_';
665                         $backendOptions = array(
666                             'servers' => array(
667                                 'host'   => $host,
668                                 'port'   => $port,
669                                 'prefix' => $prefix
670                         ));
671                         break;
672                         
673                     default:
674                         $backendOptions = array();
675                         break;
676                 }
677             }
678
679             Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__ . " cache of backend type '{$backendType}' enabled");
680             
681             if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) {
682                 // logger is an object, that makes ugly traces :)
683                 $backendOptionsWithoutLogger = $backendOptions;
684                 if (isset($backendOptionsWithoutLogger['logger'])) {
685                     unset($backendOptionsWithoutLogger['logger']);
686                 }
687                 Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " backend options: " . print_r($backendOptionsWithoutLogger, TRUE));
688             }
689
690         } else {
691             Tinebase_Core::getLogger()->INFO(__METHOD__ . '::' . __LINE__ . ' cache disabled');
692             $backendType = 'Test';
693             $frontendOptions = array(
694                 'caching' => false
695             );
696             $backendOptions = array(
697             );
698         }
699
700         // getting a Zend_Cache_Core object
701         try {
702             $cache = Zend_Cache::factory('Core', $backendType, $frontendOptions, $backendOptions);
703             
704         } catch (Zend_Cache_Exception $e) {
705             $enabled = FALSE;
706             if ('File' === $backendType && !is_dir($backendOptions['cache_dir'])) {
707                 // create cache directory and re-try
708                 if (mkdir($backendOptions['cache_dir'], 0770, true)) {
709                     $enabled = $_enabled;
710                 }
711             }
712             
713             Tinebase_Core::getLogger()->WARN(__METHOD__ . '::' . __LINE__ . ' Cache error: ' . $e->getMessage());
714
715             self::setupCache($enabled);
716             return;
717         }
718
719
720         // some important caches
721         Zend_Locale::setCache($cache);
722         Zend_Translate::setCache($cache);
723         
724         Zend_Db_Table_Abstract::setDefaultMetadataCache($cache);
725         self::set(self::CACHE, $cache);
726     }
727     
728     /**
729      * places user credential cache id from cache adapter (if present) into registry
730      */
731     public static function setupUserCredentialCache()
732     {
733         try {
734             $cache = Tinebase_Auth_CredentialCache::getInstance()->getCacheAdapter()->getCache();
735         } catch (Zend_Db_Statement_Exception $zdse) {
736             // could not get credential cache adapter, perhaps Tine 2.0 is not installed yet
737             $cache = NULL;
738         }
739         if ($cache !== NULL) {
740             self::set(self::USERCREDENTIALCACHE, $cache);
741         }
742     }
743
744     /**
745      * initializes the session
746      */
747     public static function setupSession()
748     {
749         self::setSessionOptions(array(
750             'name' => 'TINE20SESSID'
751         ));
752         self::setSessionBackend();
753     }
754     
755     /**
756      * setup stream wrapper for tine20:// prefix
757      * 
758      */
759     public static function setupStreamWrapper()
760     {
761         if (empty(Tinebase_Core::getConfig()->filesdir)) {
762             Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
763                 . " Filesdir config value not set. tine20:// streamwrapper not registered, virtual filesystem not available.");
764             return;
765         }
766         
767         stream_wrapper_register('tine20', 'Tinebase_FileSystem_StreamWrapper');
768     }
769     
770     /**
771      * start session helper function
772      * 
773      * @param array $_options
774      * @throws Exception
775      */
776     public static function startSession()
777     {
778         try {
779             $session = new Zend_Session_Namespace('TinebaseCore');
780         } catch (Exception $e) {
781             self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Session error: ' . $e->getMessage());
782             self::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' ' . $e->getTraceAsString());
783             
784             Zend_Session::expireSessionCookie();
785             
786             throw $e;
787         }
788
789         if (isset($session->currentAccount)) {
790             self::set(self::USER, $session->currentAccount);
791         }
792         if (isset($session->setupuser)) {
793             self::set(self::USER, $session->setupuser);
794         }
795         if (!isset($session->jsonKey)) {
796             $session->jsonKey = Tinebase_Record_Abstract::generateUID();
797         }
798         self::set('jsonKey', $session->jsonKey);
799         
800         self::set(self::SESSION, $session);
801         self::set(self::SESSIONID, session_id());
802         self::setDbCapabilitiesInSession($session);
803     }
804     
805     /**
806      * set database capabilities in session
807      * 
808      * @param Zend_Session_Namespace $session
809      */
810     public static function setDbCapabilitiesInSession($session)
811     {
812         if (! isset($session->dbcapabilities)) {
813             $db = Tinebase_Core::getDb();
814             $capabilities = array();
815             if ($db instanceof Zend_Db_Adapter_Pdo_Pgsql) {
816                 $capabilities['unaccent'] = Tinebase_Core::checkUnaccentExtension($db);
817             }
818             $session->dbcapabilities = $capabilities;
819         }
820     }
821     
822     /**
823      * set session options
824      * 
825      * @param array $_options
826      */
827     public static function setSessionOptions($_options = array())
828     {
829         Zend_Session::setOptions(array_merge($_options, array(
830             'cookie_httponly'   => true,
831             'hash_function'     => 1
832         )));
833         
834         if (isset($_SERVER['REQUEST_URI'])) {
835             // cut of path behind caldav/webdav (removeme when dispatching is refactored)
836             if (isset($_SERVER['REDIRECT_WEBDAV']) && $_SERVER['REDIRECT_WEBDAV'] == 'true') {
837                 $decodedUri = Sabre\DAV\URLUtil::decodePath($_SERVER['REQUEST_URI']);
838                 $baseUri = '/' . substr($decodedUri, 0, strpos($decodedUri, 'webdav/') + strlen('webdav/'));
839             } else if (isset($_SERVER['REDIRECT_CALDAV']) && $_SERVER['REDIRECT_CALDAV'] == 'true') {
840                 $decodedUri = Sabre\DAV\URLUtil::decodePath($_SERVER['REQUEST_URI']);
841                 $baseUri = '/' . substr($decodedUri, 0, strpos($decodedUri, 'caldav/') + strlen('caldav/'));
842             } else {
843                 $baseUri = dirname($_SERVER['REQUEST_URI']);
844             }
845             
846             if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
847                 $exploded = explode("/", $_SERVER['REQUEST_URI']);
848                 if (strtolower($exploded[1]) == strtolower($_SERVER['HTTP_HOST'])) {
849                      $baseUri = '/' . $_SERVER['HTTP_HOST'] . (($baseUri == '/') ? '' : $baseUri);
850                 }
851             }
852             
853             // fix for windows server with backslash directory separator
854             $baseUri = str_replace(DIRECTORY_SEPARATOR, '/', $baseUri);
855             
856             Zend_Session::setOptions(array(
857                 'cookie_path'     => $baseUri
858             ));
859         }
860         
861         if (self::isHttpsRequest()) {
862             Zend_Session::setOptions(array(
863                 'cookie_secure'     => true
864             ));
865         }
866     }
867         
868     /**
869      * set session backend
870      */
871     public static function setSessionBackend()
872     {
873         $config = self::getConfig();
874         $backendType = ($config->session && $config->session->backend) ? ucfirst($config->session->backend) : 'File';
875         $maxLifeTime = ($config->session && $config->session->lifetime) ? $config->session->lifetime : 86400; // one day is default
876         
877         switch ($backendType) {
878             case 'File':
879                 if ($config->gc_maxlifetime) {
880                     self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key 'gc_maxlifetime' should be renamed to 'lifetime' and moved to 'session' group.");
881                     $maxLifeTime = $config->get('gc_maxlifetime', 86400);
882                 }
883                 Zend_Session::setOptions(array(
884                     'gc_maxlifetime'     => $maxLifeTime
885                 ));
886                 
887                 $sessionSavepath = self::getSessionDir();
888                 if (ini_set('session.save_path', $sessionSavepath) !== FALSE) {
889                     if (!is_dir($sessionSavepath)) {
890                         mkdir($sessionSavepath, 0700);
891                     }
892                 }
893                 
894                 $lastSessionCleanup = Tinebase_Config::getInstance()->get(Tinebase_Config::LAST_SESSIONS_CLEANUP_RUN);
895                 if ($lastSessionCleanup instanceof DateTime && $lastSessionCleanup > Tinebase_DateTime::now()->subHour(2)) {
896                     Zend_Session::setOptions(array(
897                         'gc_probability' => 0,
898                         'gc_divisor'     => 100
899                     ));
900                 } else if (@opendir(ini_get('session.save_path')) !== FALSE) {
901                     Zend_Session::setOptions(array(
902                         'gc_probability' => 1,
903                         'gc_divisor'     => 100
904                     ));
905                 } else {
906                     self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " Unable to initialize automatic session cleanup. Check permissions to " . ini_get('session.save_path'));
907                 }
908                 
909                 break;
910                 
911             case 'Redis':
912                 $host   = ($config->session->host) ? $config->session->host : 'localhost';
913                 $port   = ($config->session->port) ? $config->session->port : 6379;
914                 $prefix = Tinebase_Application::getInstance()->getApplicationByName('Tinebase')->getId() . '_SESSION_';
915                 
916                 Zend_Session::setOptions(array(
917                     'gc_maxlifetime' => $maxLifeTime,
918                     'save_handler'   => 'redis',
919                     'save_path'      => "tcp://$host:$port?prefix=$prefix"
920                 ));
921                 break;
922                 
923             default:
924                 break;
925         }
926         
927         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Session of backend type '{$backendType}' configured.");
928     }
929     
930     /**
931      * initializes the database connection
932      */
933     public static function setupDatabaseConnection()
934     {
935         $config = self::getConfig();
936         
937         if (isset($config->database)) {
938             $dbConfig = $config->database;
939             
940             if (! defined('SQL_TABLE_PREFIX')) {
941                 define('SQL_TABLE_PREFIX', $dbConfig->get('tableprefix') ? $dbConfig->get('tableprefix') : 'tine20_');
942             }
943             
944             $db = self::createAndConfigureDbAdapter($dbConfig->toArray());
945             Zend_Db_Table_Abstract::setDefaultAdapter($db);
946             
947             // place table prefix into the concrete adapter
948             $db->table_prefix = SQL_TABLE_PREFIX;
949             
950             self::set(self::DB, $db);
951             
952         } else {
953             die ('database section not found in central configuration file');
954         }
955     }
956     
957     /**
958      * create db adapter and configure it for Tine 2.0
959      * 
960      * @param array $dbConfigArray
961      * @param string $dbBackend
962      * @return Zend_Db_Adapter_Abstract
963      * @throws Tinebase_Exception_Backend_Database
964      * @throws Tinebase_Exception_UnexpectedValue
965      */
966     public static function createAndConfigureDbAdapter($dbConfigArray, $dbBackend = NULL)
967     {
968         if ($dbBackend === NULL) {
969             $constName = 'self::' . strtoupper($dbConfigArray['adapter']);
970             if (empty($dbConfigArray['adapter']) || ! defined($constName)) {
971                 self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Wrong/no db adapter configured (' . $dbConfigArray['adapter'] . '). Using default: ' . self::PDO_MYSQL);
972                 $dbBackend = self::PDO_MYSQL;
973                 $dbConfigArray['adapter'] = self::PDO_MYSQL;
974             } else {
975                 $dbBackend = constant($constName);
976             }
977         }
978         
979         self::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Creating ' . $dbBackend . ' DB adapter');
980         
981         switch ($dbBackend) {
982             case self::PDO_MYSQL:
983                 foreach (array('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY', 'PDO::MYSQL_ATTR_INIT_COMMAND') as $pdoConstant) {
984                     if (! defined($pdoConstant)) {
985                         throw new Tinebase_Exception_Backend_Database($pdoConstant . ' is not defined. Please check PDO extension.');
986                     }
987                 }
988                 
989                 // force some driver options
990                 $dbConfigArray['driver_options'] = array(
991                     PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => FALSE,
992                     // set utf8 charset
993                     // @todo set to utf8mb4 / @see 0008708: switch to mysql utf8mb4
994                     PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES UTF8;",
995                 );
996                 $db = Zend_Db::factory('Pdo_Mysql', $dbConfigArray);
997                 try {
998                     // set mysql timezone to utc and activate strict mode
999                     $db->query("SET time_zone ='+0:00';");
1000                     $db->query("SET SQL_MODE = 'STRICT_ALL_TABLES'");
1001                     $db->query("SET SESSION group_concat_max_len = 81920");
1002                 } catch (Exception $e) {
1003                     self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Failed to set "SET SQL_MODE to STRICT_ALL_TABLES or timezone: ' . $e->getMessage());
1004                 }
1005                 break;
1006                 
1007             case self::PDO_OCI:
1008                 $db = Zend_Db::factory('Pdo_Oci', $dbConfigArray);
1009                 break;
1010                 
1011             case self::ORACLE:
1012                 $db = Zend_Db::factory(self::ORACLE, $dbConfigArray);
1013                 $db->supportPositionalParameters(true);
1014                 $db->setLobAsString(true);
1015                 break;
1016                 
1017             case self::PDO_PGSQL:
1018                 unset($dbConfigArray['adapter']);
1019                 unset($dbConfigArray['tableprefix']);
1020                 if (empty($dbConfigArray['port'])) {
1021                     $dbConfigArray['port'] = 5432;
1022                 }
1023                 $db = Zend_Db::factory('Pdo_Pgsql', $dbConfigArray);
1024                 try {
1025                     // set mysql timezone to utc and activate strict mode
1026                     $db->query("SET timezone ='+0:00';");
1027                     // PostgreSQL has always been strict about making sure data is valid before allowing it into the database
1028                 } catch (Exception $e) {
1029                     self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' Failed to set "SET timezone: ' . $e->getMessage());
1030                 }
1031                 break;
1032                 
1033             default:
1034                 throw new Tinebase_Exception_UnexpectedValue('Invalid database adapter defined. Please set adapter to ' . self::PDO_MYSQL . ' or ' . self::PDO_OCI . ' in config.inc.php.');
1035                 break;
1036         }
1037         
1038         return $db;
1039     }
1040     
1041     /**
1042      * get value of session variable "unaccent"
1043      * 
1044      * @param Zend_Db_Adapter_Abstract $db
1045      * @return boolean $valueUnaccent
1046      * 
1047      * @todo should be moved to pgsql adapter / helper functions
1048      */
1049     public static function checkUnaccentExtension($db)
1050     {
1051         $tableName = 'pg_extension';
1052         $cols = 'COUNT(*)';
1053
1054         $select = $db->select()
1055             ->from($tableName, $cols)
1056             ->where("extname = 'unaccent'");
1057
1058         // if there is no table pg_extension, returns 0 (false)
1059         try {
1060             // checks if unaccent extension is installed or not
1061             // (1 - yes; unaccent found)
1062             $result = (bool) $db->fetchOne($select);
1063         } catch (Zend_Db_Statement_Exception $zdse) {
1064             // (0 - no; unaccent not found)
1065             self::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Unaccent extension disabled (' . $zdse->getMessage() . ')');
1066             $result = FALSE;
1067         }
1068         
1069         return $result;
1070     }
1071
1072     /**
1073      * get db profiling
1074      * 
1075      * Enable db profiling like this (in config.inc.php):
1076      * 
1077      *   'database' => 
1078      *      array(
1079      *         [...] // db connection params  
1080      *         'profiler' => TRUE
1081      *      ),
1082      *   'profiler' =>
1083      *      array(
1084      *         'queryProfiles' => TRUE,
1085      *         'queryProfilesDetails' => TRUE,
1086      *         'user' => 'loginname',             // only profile this user 
1087      *         'profilerFilterElapsedSecs' => 1,  // only show queries whose elapsed time is equal or greater than this
1088      *      )
1089      *    ),
1090      * 
1091      */
1092     public static function getDbProfiling()
1093     {
1094         if (! self::getConfig() || ! self::getConfig()->database || ! (bool) self::getConfig()->database->profiler) {
1095             return;
1096         }
1097         
1098         $config = self::getConfig()->profiler;
1099         
1100         if ($config->user && is_object(self::getUser()) && $config->user !== self::getUser()->accountLoginName) {
1101             return;
1102         }
1103         
1104         $profiler = Zend_Db_Table::getDefaultAdapter()->getProfiler();
1105         
1106         if (! empty($config->profilerFilterElapsedSecs)) {
1107             $profiler->setFilterElapsedSecs($config->profilerFilterElapsedSecs);
1108         }
1109         
1110         $data = array(
1111             'totalNumQueries' => $profiler->getTotalNumQueries(),
1112             'totalElapsedSec' => $profiler->getTotalElapsedSecs(),
1113             'longestTime'        => 0,
1114             'longestQuery'       => ''
1115         );
1116         
1117         if ($config && (bool) $config->queryProfiles) {
1118             $queryProfiles = $profiler->getQueryProfiles();
1119             if (is_array($queryProfiles)) {
1120                 $data['queryProfiles'] = array();
1121                 foreach ($queryProfiles as $profile) {
1122                     if ((bool) $config->queryProfilesDetails) {
1123                         $data['queryProfiles'][] = array(
1124                             'query'       => $profile->getQuery(),
1125                             'elapsedSecs' => $profile->getElapsedSecs(),
1126                         );
1127                     }
1128                     
1129                     if ($profile->getElapsedSecs() > $data['longestTime']) {
1130                         $data['longestTime']  = $profile->getElapsedSecs();
1131                         $data['longestQuery'] = $profile->getQuery();
1132                     }
1133                 }
1134             }
1135         }
1136         
1137         self::getLogger()->debug(__METHOD__ . ' (' . __LINE__ . ') value: ' . print_r($data, true));
1138     }
1139
1140     /**
1141      * sets the user locale
1142      *
1143      * @param  string $localeString
1144      * @param  bool   $saveaspreference
1145      */
1146     public static function setupUserLocale($localeString = 'auto', $saveaspreference = FALSE)
1147     {
1148         $session = self::get(self::SESSION);
1149
1150         if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " given localeString '$localeString'");
1151
1152         // get locale object from session or ...
1153         if (   $session !== NULL 
1154             && isset($session->userLocale) 
1155             && is_object($session->userLocale) 
1156             && ($session->userLocale->toString() === $localeString || $localeString === 'auto')
1157         ) {
1158             $locale = $session->userLocale;
1159
1160             if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
1161                 . " Got locale from session : " . (string)$locale);
1162             
1163         // ... create new locale object
1164         } else {
1165             if ($localeString === 'auto') {
1166                 // check if cookie or pref with language is available
1167                 if (isset($_COOKIE['TINE20LOCALE'])) {
1168                     $localeString = $_COOKIE['TINE20LOCALE'];
1169                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__
1170                         . " Got locale from cookie: '$localeString'");
1171                     
1172                 } elseif (isset($session->currentAccount)) {
1173                     $localeString = self::getPreference()->getValue(Tinebase_Preference::LOCALE, 'auto');
1174                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1175                         . " Got locale from preference: '$localeString'");
1176                 } else {
1177                     if (self::isLogLevel(Zend_Log::DEBUG)) self::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1178                         . " Try to detect the locale of the user (browser, environment, default)");
1179                 }
1180             }
1181             
1182             $locale = Tinebase_Translation::getLocale($localeString);
1183     
1184             // save in session
1185             if ($session !== NULL) {
1186                 $session->userLocale = $locale;
1187             }
1188         }
1189         
1190         // save in registry
1191         self::set('locale', $locale);
1192         
1193         $localeString = (string)$locale;
1194         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) self::getLogger()->info(__METHOD__ . '::' . __LINE__ . " Setting user locale: " . $localeString);
1195         
1196         // save locale as preference
1197         if (is_object(Tinebase_Core::getUser()) && ($saveaspreference || self::getPreference()->{Tinebase_Preference::LOCALE} === 'auto')) {
1198             self::getPreference()->{Tinebase_Preference::LOCALE} = $localeString;
1199         }
1200         
1201         // set correct ctype locale, to make sure that the filesystem functions like basename() are working correctly with utf8 chars
1202         $ctypeLocale = setlocale(LC_CTYPE, 0);
1203         if (! preg_match('/utf-?8/i', $ctypeLocale)) {
1204             // use en_US as fallback locale if region string is missing
1205             $newCTypeLocale = ((strpos($localeString, '_') !== FALSE) ? $localeString : 'en_US') . '.UTF8';
1206             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
1207                 . ' Setting CTYPE locale from "' . $ctypeLocale . '" to "' . $newCTypeLocale . '".');
1208             setlocale(LC_CTYPE, $newCTypeLocale);
1209         }
1210     }
1211
1212     /**
1213      * intializes the timezone handling
1214      *
1215      * @param  string $_timezone
1216      * @param  bool   $_saveaspreference
1217      * @return string
1218      */
1219     public static function setupUserTimezone($_timezone = NULL, $_saveaspreference = FALSE)
1220     {
1221         $session = self::get(self::SESSION);
1222
1223         if ($_timezone === NULL) {
1224             
1225             if ($session instanceof Zend_Session_Namespace && isset($session->timezone)) {
1226                 $timezone = $session->timezone;
1227             } else {
1228                 // get timezone from preferences
1229                 $timezone = self::getPreference()->getValue(Tinebase_Preference::TIMEZONE);
1230                 if ($session instanceof Zend_Session_Namespace) {
1231                     $session->timezone = $timezone;
1232                 }
1233             }
1234
1235         } else {
1236             $timezone = $_timezone;
1237             if ($session instanceof Zend_Session_Namespace) {
1238                 $session->timezone = $timezone;
1239             }
1240             
1241             if ($_saveaspreference) {
1242                 // save as user preference
1243                 self::getPreference()->setValue(Tinebase_Preference::TIMEZONE, $timezone);
1244             }
1245         }
1246
1247         self::set(self::USERTIMEZONE, $timezone);
1248
1249         return $timezone;
1250     }
1251
1252     /**
1253      * set php execution life (max) time
1254      *
1255      * @param int $_seconds
1256      * @return int old max exexcution time in seconds
1257      */
1258     public static function setExecutionLifeTime($_seconds)
1259     {
1260         $oldMaxExcecutionTime = ini_get('max_execution_time');
1261         
1262         if ($oldMaxExcecutionTime > 0) {
1263             if ((bool)ini_get('safe_mode') === true) {
1264                 if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
1265                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
1266                         . ' max_execution_time(' . $oldMaxExcecutionTime . ') is too low. Can\'t set limit to ' 
1267                         . $_seconds . ' because of safe mode restrictions.');
1268                 }
1269             } else {
1270                 if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1271                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' setting execution life time to: ' . $_seconds);
1272                 }
1273                 set_time_limit($_seconds);
1274             }
1275         }
1276         
1277         return $oldMaxExcecutionTime;
1278     }
1279     
1280     /**
1281      * set php memory (max) limit
1282      *
1283      * @param string $_limit
1284      * @return string old max memory limit
1285      */
1286     public static function setMemoryLimit($_limit)
1287     {
1288         $oldMaxMemoryLimit = ini_get('memory_limit');
1289         
1290         if (! empty($oldMaxMemoryLimit)) {
1291             if ((bool)ini_get('safe_mode') === true) {
1292                 if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
1293                     Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ 
1294                         . ' memory_limit(' . $oldMaxMemoryLimit . ') is too low. Can\'t set limit to ' 
1295                         . $_limit . ' because of safe mode restrictions.');
1296                 }
1297             } else {
1298                 if (Tinebase_Core::isRegistered(self::LOGGER) && Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1299                     Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' setting memory limit to: ' . $_limit);
1300                 }
1301                 ini_set('memory_limit', $_limit);
1302             }
1303         }
1304         
1305         return $oldMaxMemoryLimit;
1306     }
1307     
1308     /**
1309      * log memory usage
1310      *
1311      */
1312     public static function logMemoryUsage()
1313     {
1314         if (function_exists('memory_get_peak_usage')) {
1315             $memory = memory_get_peak_usage(true);
1316         } else {
1317             $memory = memory_get_usage(true);
1318         }
1319         
1320         return  ' Memory usage: ' . ($memory / 1024 / 1024) . ' MB';
1321     }
1322     
1323     public static function logCacheSize()
1324     {
1325         if(function_exists('realpath_cache_size')) {
1326             $realPathCacheSize = realpath_cache_size();
1327         } else {
1328             $realPathCacheSize = 'unknown';
1329         }
1330         
1331         return ' Real patch cache size: ' . $realPathCacheSize;
1332     }
1333
1334     /******************************* REGISTRY ************************************/
1335
1336     /**
1337      * get a value from the registry
1338      *
1339      */
1340     public static function get($index)
1341     {
1342         return (Zend_Registry::isRegistered($index)) ? Zend_Registry::get($index) : NULL;
1343     }
1344
1345     /**
1346      * set a registry value
1347      * 
1348      * @throws Tinebase_Exception_InvalidArgument
1349      * @return mixed value
1350      */
1351     public static function set($index, $value)
1352     {
1353         if ($index === self::USER) {
1354             if ($value === null) {
1355                 throw new Tinebase_Exception_InvalidArgument('Invalid user object!');
1356             }
1357             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
1358                 if ($value instanceof Tinebase_Model_FullUser) {
1359                     $userString =  $value->accountLoginName;
1360                 } else if ($value instanceof Tinebase_Model_User) {
1361                     $userString = $value->accountDisplayName;
1362                 } else {
1363                     $userString = var_export($value, true);
1364                 }
1365                 
1366                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Setting user ' . $userString);
1367             }
1368         }
1369         
1370         Zend_Registry::set($index, $value);
1371     }
1372
1373     /**
1374      * checks a registry value
1375      *
1376      * @return boolean
1377      */
1378     public static function isRegistered($index)
1379     {
1380         return Zend_Registry::isRegistered($index);
1381     }
1382
1383     /**
1384      * Returns the auth typ from config or default value
1385      *
1386      * @return String
1387      */
1388     public static function getAuthType()
1389     {
1390         if (isset(Tinebase_Core::getConfig()->authentication)) {
1391             $authType = Tinebase_Core::getConfig()->authentication->get('backend', Tinebase_Auth::SQL);
1392         } else {
1393             $authType = Tinebase_Auth::SQL;
1394         }
1395
1396         return ucfirst($authType);
1397     }
1398
1399     /**
1400      * get config from the registry
1401      *
1402      * @return Zend_Config|Zend_Config_Ini|Tinebase_Config
1403      */
1404     public static function getConfig()
1405     {
1406         if (! self::get(self::CONFIG)) {
1407             self::setupConfig();
1408         }
1409         return self::get(self::CONFIG);
1410     }
1411
1412     /**
1413      * get max configured loglevel
1414      * 
1415      * @return integer
1416      */
1417     public static function getLogLevel()
1418     {
1419         if (! ($logLevel = self::get(self::LOGLEVEL))) {
1420             $config = self::getConfig();
1421             $logLevel = Tinebase_Log::getMaxLogLevel(isset($config->logger) ? $config->logger : NULL);
1422             self::set(self::LOGLEVEL, $logLevel);
1423         }
1424         return $logLevel;
1425     }
1426     
1427     /**
1428      * check if given loglevel should be logged
1429      * 
1430      * @param  integer $_prio
1431      * @return boolean
1432      */
1433     public static function isLogLevel($_prio)
1434     {
1435         return self::getLogLevel() >= $_prio;
1436     }
1437     
1438     /**
1439      * get config from the registry
1440      *
1441      * @return Tinebase_Log the logger
1442      */
1443     public static function getLogger()
1444     {
1445         if (! self::get(self::LOGGER) instanceof Tinebase_Log) {
1446             Tinebase_Core::setupLogger();
1447         }
1448         
1449         return self::get(self::LOGGER);
1450     }
1451
1452     /**
1453      * get cache from the registry
1454      *
1455      * @return Zend_Cache_Core the cache
1456      */
1457     public static function getCache()
1458     {
1459         return self::get(self::CACHE);
1460     }
1461         
1462     /**
1463      * get session namespace from the registry
1464      *
1465      * @return Zend_Session_Namespace tinebase session namespace
1466      */
1467     public static function getSession()
1468     {
1469         return self::get(self::SESSION);
1470     }
1471
1472     /**
1473      * get locale from the registry
1474      *
1475      * @return Zend_Locale
1476      */
1477     public static function getLocale()
1478     {
1479         return self::get(self::LOCALE);
1480     }
1481         
1482     /**
1483      * get current user account
1484      *
1485      * @return Tinebase_Model_FullUser the user account record
1486      */
1487     public static function getUser()
1488     {
1489         $result = (self::isRegistered(self::USER)) ? self::get(self::USER) : NULL;
1490         return $result;
1491     }
1492
1493     /**
1494      * get preferences instance by application name (create+save it to registry if it doesn't exist)
1495      *
1496      * @param string $_application
1497      * @param boolean $_throwException throws exception if class does not exist
1498      * @return Tinebase_Preference_Abstract
1499      */
1500     public static function getPreference($_application = 'Tinebase', $_throwException = FALSE)
1501     {
1502         $result = NULL;
1503
1504         if (self::isRegistered(self::PREFERENCES)) {
1505             $prefs = self::get(self::PREFERENCES);
1506             if (isset($prefs[$_application])) {
1507                 $result = $prefs[$_application];
1508             }
1509         } else {
1510             $prefs = array();
1511         }
1512
1513         if ($result === NULL) {
1514             $prefClassName = $_application . '_Preference';
1515             if (@class_exists($prefClassName)) {
1516                 $result = new $prefClassName();
1517                 $prefs[$_application] = $result;
1518                 self::set(self::PREFERENCES, $prefs);
1519             } else if ($_throwException) {
1520                 throw new Tinebase_Exception_NotFound('No preference class found for app ' . $_application);
1521             }
1522         }
1523
1524         return $result;
1525     }
1526
1527     /**
1528      * get db adapter
1529      *
1530      * @return Zend_Db_Adapter_Abstract
1531      */
1532     public static function getDb()
1533     {
1534         if (! self::get(self::DB) instanceof Zend_Db_Adapter_Abstract) {
1535             Tinebase_Core::setupDatabaseConnection();
1536         }
1537         
1538         return self::get(self::DB);
1539     }
1540     
1541     /**
1542      * get db name
1543      * 
1544      * @return string
1545      */
1546     public static function getDbName()
1547     {
1548         if (! self::get(self::DBNAME)) {
1549             $db = self::getDb();
1550             $adapterName = get_class($db);
1551     
1552             if (empty($adapterName) || strpos($adapterName, '_') === FALSE) {
1553                 throw new Tinebase_Exception('Could not get DB adapter name.');
1554             }
1555     
1556             $adapterNameParts = explode('_', $adapterName);
1557             $type = array_pop($adapterNameParts);
1558     
1559             // special handling for Oracle
1560             $type = str_replace('Oci', self::ORACLE, $type);
1561             self::set(self::DBNAME, $type);
1562         }
1563         
1564         return self::get(self::DBNAME);
1565     }
1566
1567     /**
1568      * get temp dir string (without PATH_SEP at the end)
1569      *
1570      * @return string
1571      */
1572     public static function getTempDir()
1573     {
1574         return self::get(self::TMPDIR);
1575     }
1576
1577     /**
1578      * get session dir string (without PATH_SEP at the end)
1579      *
1580      * @return string
1581      * 
1582      * @todo remove obsolete session config paths in 2011-05
1583      */
1584     public static function getSessionDir()
1585     {
1586         $sessionDirName ='tine20_sessions';
1587         $config = self::getConfig();
1588         
1589         $sessionDir = ($config->session && $config->session->path) ? $config->session->path : null;
1590         
1591         #####################################
1592         # LEGACY/COMPATIBILITY: 
1593         # (1) had to rename session.save_path key to sessiondir because otherwise the
1594         # generic save config method would interpret the "_" as array key/value seperator
1595         # (2) moved session config to subgroup 'session'
1596         if (empty($sessionDir)) {
1597             foreach (array('session.save_path', 'sessiondir') as $deprecatedSessionDir) {
1598                 $sessionDir = $config->get($deprecatedSessionDir, null);
1599                 if ($sessionDir) {
1600                     self::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " config.inc.php key '{$deprecatedSessionDir}' should be renamed to 'path' and moved to 'session' group.");
1601                 }
1602             }
1603         }
1604         #####################################
1605
1606         if (empty($sessionDir) || !@is_writable($sessionDir)) {
1607
1608             $sessionDir = session_save_path();
1609             if (empty($sessionDir) || !@is_writable($sessionDir)) {
1610                 $sessionDir = self::guessTempDir();
1611             }
1612
1613             $sessionDir .= DIRECTORY_SEPARATOR . $sessionDirName;
1614         }
1615         return $sessionDir;
1616     }
1617     
1618     /**
1619      * returns protocol + hostname
1620      * 
1621      * @return string
1622      */
1623     public static function getHostname()
1624     {
1625         $hostname = self::get('HOSTNAME');
1626         if (! $hostname) {
1627             $request = new Sabre\HTTP\Request();
1628             $hostname = str_replace($request->getUri(), '', $request->getAbsoluteUri());
1629             self::set('HOSTNAME', $hostname);
1630         }
1631         
1632         return $hostname;
1633     }
1634     
1635     /**
1636      * Singleton instance
1637      *
1638      * @return Zend_Scheduler
1639      */
1640     public static function getScheduler()
1641     {
1642         if (! self::get(self::SCHEDULER) instanceof Zend_Scheduler) {
1643             $scheduler =  new Zend_Scheduler();
1644             $scheduler->setBackend(new Zend_Scheduler_Backend_Db(array(
1645                 'DbAdapter' => self::getDb(),
1646                 'tableName' => SQL_TABLE_PREFIX . 'scheduler',
1647                 'taskClass' => 'Tinebase_Scheduler_Task'
1648             )));
1649             
1650             self::set(self::SCHEDULER, $scheduler);
1651         }
1652         return self::get(self::SCHEDULER);
1653     }
1654
1655     /**
1656      * filter input string for database as some databases (looking at you, MySQL) can't cope with some chars
1657      * 
1658      * @param string $string
1659      * @return string
1660      *
1661      * @see 0008644: error when sending mail with note (wrong charset)
1662      * @see http://stackoverflow.com/questions/1401317/remove-non-utf8-characters-from-string/8215387#8215387
1663      * @see http://stackoverflow.com/questions/8491431/remove-4-byte-characters-from-a-utf-8-string
1664      */
1665     public static function filterInputForDatabase($string)
1666     {
1667         $db = self::getDb();
1668         if ($db && $db instanceof Zend_Db_Adapter_Pdo_Mysql) {
1669             $string = mbConvertTo($string);
1670             
1671             // remove 4 byte utf8
1672             $result = preg_replace('/[\xF0-\xF7].../s', '?', $string);
1673         } else {
1674             $result = $string;
1675         }
1676         
1677         return $result;
1678     }
1679     
1680     /**
1681      * checks if a system command exists. Works on POSIX systems.
1682      * 
1683      * @param string $name
1684      */
1685     public static function systemCommandExists($name)
1686     {
1687         $ret = shell_exec('which ' . $name);
1688         return ! empty($ret);
1689     }
1690     
1691     /**
1692      * calls a system command with escapeshellcmd
1693      * 
1694      * @param unknown $cmd
1695      */
1696     public static function callSystemCommand($cmd)
1697     {
1698         return shell_exec(escapeshellcmd($cmd));
1699     }
1700 }