make sure we reload the config if empty
[tine20] / tine20 / Tinebase / Config / Abstract.php
1 <?php
2 /**
3  * @package     Tinebase
4  * @subpackage  Config
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @copyright   Copyright (c) 2007-2016 Metaways Infosystems GmbH (http://www.metaways.de)
7  * @author      Cornelius Weiss <c.weiss@metaways.de>
8  */
9
10 /**
11  * base for config classes
12  * 
13  * @package     Tinebase
14  * @subpackage  Config
15  * 
16  * @todo support protected function interceptor for get property: _get<PropertyName>(&$data)
17  * @todo support protected function interceptor for set property: _set<PropertyName>(&$data)
18  * @todo update db to json encode all configs
19  * @todo support array collections definitions
20  */
21 abstract class Tinebase_Config_Abstract implements Tinebase_Config_Interface
22 {
23     /**
24      * object config type
25      * 
26      * @var string
27      */
28     const TYPE_OBJECT = 'object';
29
30     /**
31      * integer config type
32      * 
33      * @var string
34      */
35     const TYPE_INT = 'int';
36     
37     /**
38      * boolean config type
39      * 
40      * @var string
41      */
42     const TYPE_BOOL = 'bool';
43     
44     /**
45      * string config type
46      * 
47      * @var string
48      */
49     const TYPE_STRING = 'string';
50     
51     /**
52      * float config type
53      * 
54      * @var string
55      */
56     const TYPE_FLOAT = 'float';
57     
58     /**
59      * dateTime config type
60      * 
61      * @var string
62      */
63     const TYPE_DATETIME = 'dateTime';
64
65     /**
66      * keyField config type
67      *
68      * @var string
69      */
70     const TYPE_KEYFIELD = 'keyField';
71
72     /**
73      * array config type
74      *
75      * @var string
76      */
77     const TYPE_ARRAY = 'array';
78
79     /**
80      * keyFieldConfig config type
81      * 
82      * @var string
83      */
84     const TYPE_KEYFIELD_CONFIG = 'keyFieldConfig';
85     
86     /**
87      * config key for enabled features / feature switch
88      *
89      * @var string
90      */
91     const ENABLED_FEATURES = 'features';
92     
93     /**
94      * application name this config belongs to
95      *
96      * @var string
97      */
98     protected $_appName;
99     
100     /**
101      * config file data.
102      * 
103      * @var array
104      */
105     private static $_configFileData;
106     
107     /**
108      * config database backend
109      * 
110      * @var Tinebase_Backend_Sql
111      */
112     private static $_backend;
113     
114     /**
115      * application config class cache (name => config record)
116      * 
117      * @var array
118      */
119     protected $_cachedApplicationConfig = NULL;
120
121     protected $_mergedConfigCache = array();
122
123     /**
124      * server classes
125      *
126      * @var array
127      */
128     protected static $_serverPlugins = array();
129     
130     /**
131      * get config object for application
132      * 
133      * @param string $applicationName
134      * @return Tinebase_Config_Abstract
135      */
136     public static function factory($applicationName)
137     {
138         if ($applicationName === 'Tinebase') {
139             $config = Tinebase_Core::getConfig();
140             // NOTE: this is a Zend_Config object in the Setup
141             if ($config instanceof Tinebase_Config_Abstract) {
142                 return $config;
143             }
144         }
145         
146         $configClassName = $applicationName . '_Config';
147         if (@class_exists($configClassName)) {
148             $config = call_user_func(array($configClassName, 'getInstance'));
149         } else {
150             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
151                 . ' Application ' . $applicationName . ' has no config class.');
152             $config = NULL;
153         }
154         
155         return $config;
156     }
157     
158     /**
159      * retrieve a value and return $default if there is no element set.
160      *
161      * @param  string $name
162      * @param  mixed  $default
163      * @return mixed
164      */
165     public function get($name, $default = NULL)
166     {
167         if (isset($this->_mergedConfigCache[$name]) || array_key_exists($name, $this->_mergedConfigCache)) {
168             if (!isset($this->_mergedConfigCache[$name]) && null !== $default) {
169                 return $default;
170             }
171             return $this->_mergedConfigCache[$name];
172         }
173
174         $fileConfigArray = null;
175         $dbConfigArray = null;
176         $dbAvailable = ('database' === $name || 'caching' === $name || 'logger' === $name) ? Tinebase_Core::hasDb() : true;
177
178         // NOTE: we return here (or in the default handling) if db is not available. That is to prevent db lookup when db is not yet setup
179         $configFileSection = $this->getConfigFileSection($name);
180         if (null !== $configFileSection) {
181             $fileConfigArray = $configFileSection[$name];
182             if (false === $dbAvailable) {
183                 return $this->_rawToConfig($fileConfigArray, $name);
184             }
185         }
186         
187         if (true === $dbAvailable && null !== ($config = $this->_loadConfig($name))) {
188             $dbConfigArray = json_decode($config->value, TRUE);
189             // @todo JSON encode all config data via update script!
190             if (null === $dbConfigArray && strtolower($config->value) !== '{null}') $dbConfigArray = $config->value;
191         }
192
193         $data = null;
194         if (null === $fileConfigArray && null === $dbConfigArray) {
195             if ($default !== null) {
196                 return $default;
197             }
198
199             $data = $this->_getDefault($name);
200         } else {
201
202             if (null === $fileConfigArray) {
203                 $fileConfigArray = array();
204             } elseif(!is_array($fileConfigArray)) {
205                 $data = $fileConfigArray;
206             }
207
208             if (null === $dbConfigArray) {
209                 $dbConfigArray = array();
210             } elseif(!is_array($dbConfigArray) && null === $data) {
211                 if (count($fileConfigArray) > 0) {
212                     $dbConfigArray = array();
213                 } else {
214                     $data = $dbConfigArray;
215                 }
216             }
217
218             if (null === $data) {
219                 $data = array_replace_recursive($dbConfigArray, $fileConfigArray);
220             }
221         }
222
223         $this->_mergedConfigCache[$name] = $this->_rawToConfig($data, $name);
224
225         return $this->_mergedConfigCache[$name];
226     }
227     
228     /**
229      * get config default
230      * - checks if application config.inc.php is available for defaults first
231      * - checks definition default second
232      * 
233      * @param string $name
234      * @return mixed
235      */
236     protected function _getDefault($name)
237     {
238         $default = null;
239         $definition = self::getDefinition($name);
240         
241         $appDefaultConfig = $this->_getAppDefaultsConfigFileData();
242         if (isset($appDefaultConfig[$name])) {
243             $default = $appDefaultConfig[$name];
244         } else if (null !== $definition) {
245             if (isset($definition['default']) || array_key_exists('default', $definition)) {
246                 $default = $definition['default'];
247             } elseif (isset($definition['type']) && isset($definition['class']) && $definition['type'] === self::TYPE_OBJECT) {
248                 $default = array();
249             }
250         }
251
252         return $default;
253     }
254     
255     /**
256      * store a config value
257      *
258      * if you store a value here, it will be valid for the current process and be persisted in the db, BUT
259      * it maybe overwritten by a config file entry. So other process that merge the config from db and config
260      * file again may not get the value you set here.
261      *
262      * @TODO validate config (rawToString?)
263      *
264      * @param  string   $_name      config name
265      * @param  mixed    $_value     config value
266      * @return void
267      */
268     public function set($_name, $_value)
269     {
270         if (null === $_value) {
271             $this->delete($_name);
272             if (isset($this->_mergedConfigCache[$_name]) || array_key_exists($_name, $this->_mergedConfigCache)) {
273                 unset($this->_mergedConfigCache[$_name]);
274             }
275             return;
276         }
277
278         $configRecord = new Tinebase_Model_Config(array(
279             "application_id"    => Tinebase_Application::getInstance()->getApplicationByName($this->_appName)->getId(),
280             "name"              => $_name,
281             "value"             => json_encode($_value),
282         ));
283         
284         $this->_saveConfig($configRecord);
285
286         $this->_mergedConfigCache[$_name] = $this->_rawToConfig($_value, $_name);
287     }
288     
289     /**
290      * delete a config from database
291      * 
292      * @param  string   $_name
293      * @return void
294      */
295     public function delete($_name)
296     {
297         $config = $this->_loadConfig($_name);
298         if ($config) {
299             $this->_getBackend()->delete($config->getId());
300             $this->clearCache();
301         }
302     }
303     
304     /**
305      * delete all config for a application
306      *
307      * @param  string   $_applicationId
308      * @return integer  number of deleted configs
309      * 
310      * @todo remove param as this should be known?
311      */
312     public function deleteConfigByApplicationId($_applicationId)
313     {
314         $count = $this->_getBackend()->deleteByProperty($_applicationId, 'application_id');
315         $this->clearCache(array("id" => $_applicationId));
316         
317         return $count;
318     }
319     
320     /**
321      * Magic function so that $obj->value will work.
322      *
323      * @param string $_name
324      * @return mixed
325      */
326     public function __get($_name)
327     {
328         return $this->get($_name);
329     }
330     
331     /**
332      * Magic function so that $obj->configName = configValue will work.
333      *
334      * @param  string   $_name      config name
335      * @param  mixed    $_value     config value
336      * @return void
337      */
338     public function __set($_name, $_value)
339     {
340         $this->set($_name, $_value);
341     }
342     
343     /**
344      * checks if a config name is set
345      * isset means that the config key is present either in config file or in db
346      * 
347      * @param  string $_name
348      * @return bool
349      */
350     public function __isset($_name)
351     {
352         // NOTE: we can't test more precise here due to cacheing
353         $value = $this->get($_name, Tinebase_Model_Config::NOTSET);
354         
355         return $value !== Tinebase_Model_Config::NOTSET;
356     }
357     
358     /**
359      * returns data from central config.inc.php file
360      * 
361      * @return array
362      */
363     protected function _getConfigFileData()
364     {
365         if (! self::$_configFileData) {
366             /** @noinspection PhpIncludeInspection */
367             self::$_configFileData = include('config.inc.php');
368             
369             if (self::$_configFileData === false) {
370                 die('Central configuration file config.inc.php not found in includepath: ' . get_include_path() . "\n");
371             }
372             
373             if (isset(self::$_configFileData['confdfolder'])) {
374                 $tmpDir = Tinebase_Core::guessTempDir(self::$_configFileData);
375                 $cachedConfigFile = $tmpDir . DIRECTORY_SEPARATOR . 'cachedConfig.inc.php';
376
377                 if (file_exists($cachedConfigFile)) {
378                     /** @noinspection PhpIncludeInspection */
379                     $cachedConfigData = include($cachedConfigFile);
380                 } else {
381                     $cachedConfigData = false;
382                 }
383                 
384                 if ($this->_doCreateCachedConfig($cachedConfigData)) {
385                     $this->_createCachedConfig($tmpDir);
386                 } else {
387                     self::$_configFileData = $cachedConfigData;
388                 }
389             }
390         }
391         
392         return self::$_configFileData;
393     }
394
395     /**
396      * returns true if a new cached config file should be created
397      *
398      * @param $cachedConfigData
399      * @return bool
400      */
401     protected function _doCreateCachedConfig($cachedConfigData)
402     {
403         return (
404                false === $cachedConfigData
405             || (defined('TINE20_BUILDTYPE') && TINE20_BUILDTYPE == 'DEVELOPMENT')
406             || (defined('TINE20_BUILDTYPE') && TINE20_BUILDTYPE == 'DEBUG')
407             || $cachedConfigData['ttlstamp'] < time()
408         );
409     }
410     
411     /**
412      * composes config files from conf.d and saves array to tmp file
413      *
414      * @param string $tmpDir
415      */
416     protected function _createCachedConfig($tmpDir)
417     {
418         $confdFolder = self::$_configFileData['confdfolder'];
419
420         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
421             . ' Creating new cached config file in ' . $tmpDir);
422
423         if (! is_readable($confdFolder)) {
424             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
425                 . ' can\'t open conf.d folder "' . $confdFolder . '"');
426             return;
427         }
428
429         $dh = opendir($confdFolder);
430
431         if ($dh === false) {
432             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
433                 . ' opendir() failed on folder "' . $confdFolder . '"');
434             return;
435         }
436
437         while (false !== ($direntry = readdir($dh))) {
438             if (strpos($direntry, '.inc.php') === (strlen($direntry) - 8)) {
439                 // TODO do lint!?! php -l $confdFolder . DIRECTORY_SEPARATOR . $direntry
440                 /** @noinspection PhpIncludeInspection */
441                 $tmpArray = include($confdFolder . DIRECTORY_SEPARATOR . $direntry);
442                 if (false !== $tmpArray) {
443                     foreach ($tmpArray as $key => $value) {
444                         self::$_configFileData[$key] = $value;
445                     }
446                 }
447             }
448         }
449         closedir($dh);
450
451         $ttl = 60;
452         if (isset(self::$_configFileData['composeConfigTTL'])) {
453             $ttl = (int) self::$_configFileData['composeConfigTTL'];
454             if ($ttl < 1) {
455                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
456                     . ' composeConfigTTL needs to be an integer > 0, current value: "'
457                     . print_r(self::$_configFileData['composeConfigTTL'],true) . '"');
458                 $ttl = 60;
459             }
460         }
461         self::$_configFileData['ttlstamp'] = time() + $ttl;
462         
463         $filename = $tmpDir . DIRECTORY_SEPARATOR . 'cachedConfig.inc.php';
464         $filenameTmp = $filename . uniqid('tine20', true);
465         $fh = fopen($filenameTmp, 'w');
466         if (false === $fh) {
467             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
468                     . ' can\'t create cached composed config file "' .$filename );
469         } else {
470             
471             fputs($fh, "<?php\n\nreturn ");
472             fputs($fh, var_export(self::$_configFileData, true));
473             fputs($fh, ';');
474             fclose($fh);
475
476             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
477                 . ' Wrote config to file ' . $filenameTmp);
478             
479             if (false === rename($filenameTmp, $filename) ) {
480                 if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__
481                     . ' can\'t rename "' . $filenameTmp . '" to "' . $filename . '"' );
482             }
483
484             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
485                 . ' Renamed to file ' . $filename);
486         }
487     }
488     
489     /**
490      * returns data from application specific config.inc.php file
491      *
492      * @return array
493      */
494     protected function _getAppDefaultsConfigFileData()
495     {
496         $cacheId = $this->_appName;
497         try {
498             $configData = Tinebase_Cache_PerRequest::getInstance()->load(__CLASS__, __METHOD__, $cacheId);
499         } catch (Tinebase_Exception_NotFound $tenf) {
500
501             $configFilename = dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . $this->_appName . DIRECTORY_SEPARATOR . 'config.inc.php';
502
503             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
504                 . ' Looking for defaults config.inc.php at ' . $configFilename);
505             if (file_exists($configFilename)) {
506                 /** @noinspection PhpIncludeInspection */
507                 $configData = include($configFilename);
508                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
509                     . ' Found default config.inc.php for app ' . $this->_appName);
510                 if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
511                     . ' ' . print_r($configData, true));
512             } else {
513                 $configData = array();
514             }
515             Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __METHOD__, $cacheId, $configData);
516         }
517         
518         return $configData;
519     }
520     
521     /**
522      * get config file section where config identified by name is in
523      * 
524      * @param  string $name
525      * @return array
526      */
527     public function getConfigFileSection($name)
528     {
529         $configFileData = $this->_getConfigFileData();
530         
531         // appName section overwrites global section in config file
532         // TODO: this needs improvement -> it is currently not allowed to have configs with the same names in
533         //       an Application and Tinebase as this leads to strange/unpredictable results here ...
534         return (isset($configFileData[$this->_appName]) || array_key_exists($this->_appName, $configFileData)) && (isset($configFileData[$this->_appName][$name]) || array_key_exists($name, $configFileData[$this->_appName])) ? $configFileData[$this->_appName] :
535               ((isset($configFileData[$name]) || array_key_exists($name, $configFileData)) ? $configFileData : NULL);
536     }
537     
538     /**
539      * load a config record from database
540      * 
541      * @param  string                   $_name
542      * @return Tinebase_Model_Config|NULL
543      */
544     protected function _loadConfig($_name)
545     {
546         if ($this->_cachedApplicationConfig === NULL || empty($this->_cachedApplicationConfig)) {
547             $this->_loadAllAppConfigsInCache();
548         }
549         $result = (isset($this->_cachedApplicationConfig[$_name])) ? $this->_cachedApplicationConfig[$_name] :  NULL;
550         
551         return $result;
552     }
553
554     /**
555     * fill class cache with all config records for this app
556     */
557     protected function _loadAllAppConfigsInCache()
558     {
559         if (empty($this->_appName)) {
560             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . ' appName not set');
561             $this->_cachedApplicationConfig = array();
562         }
563
564         if (! Tinebase_Application::getInstance()->getInstance()->isInstalled('Tinebase')) {
565             $this->_cachedApplicationConfig = array();
566             return;
567         }
568         
569         $cache = Tinebase_Core::getCache();
570         if (!is_object($cache)) {
571            Tinebase_Core::setupCache();
572            $cache = Tinebase_Core::getCache();
573         }
574         
575         if (Tinebase_Core::get(Tinebase_Core::SHAREDCACHE)) {
576             if ($cachedApplicationConfig = $cache->load('cachedAppConfig_' . $this->_appName)) {
577                 $this->_cachedApplicationConfig = $cachedApplicationConfig;
578                 return;
579             }
580         }
581         
582         try {
583             $applicationId = Tinebase_Model_Application::convertApplicationIdToInt($this->_appName);
584             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Loading all configs for app ' . $this->_appName);
585
586             $filter = new Tinebase_Model_ConfigFilter(array(
587                 array('field' => 'application_id', 'operator' => 'equals', 'value' => $applicationId),
588             ));
589             $allConfigs = $this->_getBackend()->search($filter);
590         } catch (Zend_Db_Exception $zdae) {
591             // DB might not exist or tables are not created, yet
592             Tinebase_Exception::log($zdae);
593             $this->_cachedApplicationConfig = array();
594             return;
595         }
596
597         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . ' Found ' . count($allConfigs) . ' configs.');
598         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . ' ' . print_r($allConfigs->toArray(), TRUE));
599         
600         foreach ($allConfigs as $config) {
601             $this->_cachedApplicationConfig[$config->name] = $config;
602         }
603         
604         if (Tinebase_Core::get(Tinebase_Core::SHAREDCACHE)) {
605             $cache->save($this->_cachedApplicationConfig, 'cachedAppConfig_' . $this->_appName);
606         }
607     }
608     
609     /**
610      * store a config record in database
611      * 
612      * @param   Tinebase_Model_Config $_config record to save
613      * @return  Tinebase_Model_Config
614      * 
615      * @todo only allow to save records for this app ($this->_appName)
616      */
617     protected function _saveConfig(Tinebase_Model_Config $_config)
618     {
619         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
620             . ' Setting config ' . $_config->name);
621         if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__
622             . ' ' . print_r($_config->value, true));
623         
624         $config = $this->_loadConfig($_config->name);
625         
626         if ($config) {
627             $config->value = $_config->value;
628             try {
629                 $result = $this->_getBackend()->update($config);
630             } catch (Tinebase_Exception_NotFound $tenf) {
631                 // config might be deleted but cache has not been cleaned
632                 $result = $this->_getBackend()->create($_config);
633             }
634         } else {
635             $result = $this->_getBackend()->create($_config);
636         }
637         
638         $this->clearCache();
639         
640         return $result;
641     }
642
643     /**
644      * clear the cache
645      * @param   array $appFilter
646      */
647     public function clearCache($appFilter = null)
648     {
649         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
650             . ' Clearing config cache');
651
652         if (Tinebase_Core::get(Tinebase_Core::SHAREDCACHE)) {
653             if (isset($appFilter)) {
654                 list($key, $value) = each($appFilter);
655                 $appName = $key === 'name' ? $value : Tinebase_Application::getInstance()->getApplicationById($value)->name;
656             } else {
657                 $appName = $this->_appName;
658             }
659             Tinebase_Core::getCache()->remove('cachedAppConfig_' . $appName);
660         }
661
662         Tinebase_Cache_PerRequest::getInstance()->reset('Tinebase_Config_Abstract');
663
664         $cachedConfigFile = Tinebase_Core::guessTempDir() . DIRECTORY_SEPARATOR . 'cachedConfig.inc.php';
665         if (file_exists($cachedConfigFile)) {
666             unlink($cachedConfigFile);
667         }
668
669         // reset class caches last because they would be filled again by Tinebase_Core::guessTempDir()
670         self::$_configFileData = null;
671         $this->_cachedApplicationConfig = null;
672         $this->_mergedConfigCache = array();
673     }
674     
675     /**
676      * returns config database backend
677      * 
678      * @return Tinebase_Backend_Sql
679      */
680     protected function _getBackend()
681     {
682         if (! self::$_backend) {
683             self::$_backend = new Tinebase_Backend_Sql(array(
684                 'modelName' => 'Tinebase_Model_Config', 
685                 'tableName' => 'config',
686             ));
687         }
688         
689         return self::$_backend;
690     }
691
692     /**
693      * converts raw data to config values of defined type
694      *
695      * @param   mixed     $_rawData
696      * @param   string    $_name
697      * @return  mixed
698      */
699     protected function _rawToConfig($_rawData, $_name)
700     {
701         return static::rawToConfig($_rawData, $this, $_name, self::getDefinition($_name), $this->_appName);
702     }
703
704     /**
705      * converts raw data to config values of defined type
706      * 
707      * @TODO support array contents conversion
708      * @TODO support interceptors
709      * 
710      * @param   mixed     $_rawData
711      * @param   object    $parent
712      * @param   string    $parentKey
713      * @param   array     $definition
714      * @param   string    $appName
715      * @return  mixed
716      */
717     public static function rawToConfig($_rawData, $parent, $parentKey, $definition, $appName)
718     {
719         if (null === $_rawData) {
720             return $_rawData;
721         }
722
723         // TODO make definition mandatory => should be an error
724         if (!is_array($definition) || !isset($definition['type'])) {
725             return is_array($_rawData) ? new Tinebase_Config_Struct($_rawData) : $_rawData;
726         }
727         if ($definition['type'] === self::TYPE_OBJECT && isset($definition['class']) && @class_exists($definition['class'])) {
728             if (is_object($_rawData) && $_rawData instanceof $definition['class']) {
729                 return $_rawData;
730             }
731             return new $definition['class'](is_array($_rawData) ? $_rawData : array(), $parent, $parentKey,
732                 isset($definition['content']) ? $definition['content'] : null, $appName);
733         }
734
735         switch ($definition['type']) {
736             case self::TYPE_INT:        return (int) $_rawData;
737             case self::TYPE_BOOL:       return $_rawData === "true" || (bool) (int) $_rawData;
738             case self::TYPE_STRING:     return (string) $_rawData;
739             case self::TYPE_FLOAT:      return (float) $_rawData;
740             case self::TYPE_ARRAY:      return (array) $_rawData;
741             case self::TYPE_DATETIME:   return new DateTime($_rawData);
742             case self::TYPE_KEYFIELD_CONFIG:
743                 if (is_object($_rawData) && $_rawData instanceof Tinebase_Config_KeyField) {
744                     return $_rawData;
745                 }
746                 $options = (isset($definition['options']) || array_key_exists('options', $definition)) ? (array) $definition['options'] : array();
747                 $options['appName'] = $appName;
748                 return Tinebase_Config_KeyField::create($_rawData, $options);
749
750             // TODO this should be an error
751             default:                    return is_array($_rawData) ? new Tinebase_Config_Struct($_rawData, $parent, $parentKey) : $_rawData;
752         }
753     }
754     
755     /**
756      * get definition of given property
757      * 
758      * @param   string  $_name
759      * @return  array
760      */
761     public function getDefinition($_name)
762     {
763         $properties = static::getProperties();
764         
765         return (isset($properties[$_name]) || array_key_exists($_name, $properties)) ? $properties[$_name] : NULL;
766     }
767     
768     /**
769      * Get list of server classes
770      *
771      * @return array
772      */
773     public static function getServerPlugins()
774     {
775         return static::$_serverPlugins;
776     }
777     
778     /**
779      * check if config system is ready
780      * 
781      * @todo check db setup
782      * @return bool
783      */
784     public static function isReady()
785     {
786         $configFile = @file_get_contents('config.inc.php', FILE_USE_INCLUDE_PATH);
787         
788         return !! $configFile;
789     }
790
791     /**
792      * returns true if a certain feature is enabled
793      * 
794      * @param string $featureName
795      * @return boolean
796      */
797     public function featureEnabled($featureName)
798     {
799         $cacheId = $this->_appName;
800         try {
801             $features = Tinebase_Cache_PerRequest::getInstance()->load(__CLASS__, __METHOD__, $cacheId);
802         } catch (Tinebase_Exception_NotFound $tenf) {
803             $features = $this->get(self::ENABLED_FEATURES);
804             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
805                 . ' Features config of app ' . $this->_appName . ': '
806                 . print_r($features->toArray(), true));
807             Tinebase_Cache_PerRequest::getInstance()->save(__CLASS__, __METHOD__, $cacheId, $features);
808         }
809
810         if (isset($features->{$featureName})) {
811             if (Tinebase_Config::FEATURE_SEARCH_PATH === $featureName && 'Tinebase' === $this->_appName &&
812                 !Setup_Backend_Factory::factory()->supports('mysql >= 5.6.4')) {
813                 return false;
814             }
815             return $features->{$featureName};
816         }
817
818         return false;
819     }
820 }