initial setup of framework code
[cacert-mgr.git] / manager / library / config / Config_Db.php
1 <?php
2 /**
3 * Add database driven configuration to the framework, source based on Zend_Config_Ini
4 *
5 * Zend Framework
6 *
7 * LICENSE
8 *
9 * This source file is subject to the new BSD license that is bundled
10 * with this package in the file LICENSE.txt.
11 * It is also available through the world-wide-web at this URL:
12 * http://framework.zend.com/license/new-bsd
13 * If you did not receive a copy of the license and are unable to
14 * obtain it through the world-wide-web, please send an email
15 * to license@zend.com so we can send you a copy immediately.
16 *
17 * @category Zend
18 * @package Zend_Config
19 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
20 * @license http://framework.zend.com/license/new-bsd New BSD License
21 * @version $Id: Config_Db.php 27 2009-12-03 13:00:29Z markus $
22 */
23
24
25 /**
26 * @see Zend_Config
27 */
28 require_once 'Zend/Config.php';
29
30
31 /**
32 * @category Zend
33 * @package Zend_Config
34 * @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
35 * @license http://framework.zend.com/license/new-bsd New BSD License
36 */
37 class Config_Db extends Zend_Config
38 {
39 /**
40 * String that separates nesting levels of configuration data identifiers
41 *
42 * @var string
43 */
44 protected $_nestSeparator = '.';
45
46 /**
47 * String that separates the parent section name
48 *
49 * @var string
50 */
51 protected $_sectionSeparator = ':';
52
53 /**
54 * Wether to skip extends or not
55 *
56 * @var boolean
57 */
58 protected $_skipExtends = false;
59
60 /**
61 * Loads the section $section from the config file $filename for
62 * access facilitated by nested object properties.
63 *
64 * If the section name contains a ":" then the section name to the right
65 * is loaded and included into the properties. Note that the keys in
66 * this $section will override any keys of the same
67 * name in the sections that have been included via ":".
68 *
69 * If the $section is null, then all sections in the ini file are loaded.
70 *
71 * If any key includes a ".", then this will act as a separator to
72 * create a sub-property.
73 *
74 * example ini file:
75 * [all]
76 * db.connection = database
77 * hostname = live
78 *
79 * [staging : all]
80 * hostname = staging
81 *
82 * after calling $data = new Zend_Config_Ini($file, 'staging'); then
83 * $data->hostname === "staging"
84 * $data->db->connection === "database"
85 *
86 * The $options parameter may be provided as either a boolean or an array.
87 * If provided as a boolean, this sets the $allowModifications option of
88 * Zend_Config. If provided as an array, there are two configuration
89 * directives that may be set. For example:
90 *
91 * $options = array(
92 * 'allowModifications' => false,
93 * 'nestSeparator' => '->'
94 * );
95 *
96 * @param Zend_Db $dbc
97 * @param string $db_table
98 * @param string|null $section
99 * @param boolean|array $options
100 * @throws Zend_Config_Exception
101 * @return void
102 */
103 public function __construct($dbc, $db_table, $section = null, $options = false)
104 {
105 if (empty($dbc)) {
106 /**
107 * @see Zend_Config_Exception
108 */
109 require_once 'Zend/Config/Exception.php';
110 throw new Zend_Config_Exception('Database connection is not set');
111 }
112
113 if (empty($db_table)) {
114 /**
115 * @see Zend_Config_Exception
116 */
117 require_once 'Zend/Config/Exception.php';
118 throw new Zend_Config_Exception('Database table is not set');
119 }
120
121 $allowModifications = false;
122 if (is_bool($options)) {
123 $allowModifications = $options;
124 } elseif (is_array($options)) {
125 if (isset($options['allowModifications'])) {
126 $allowModifications = (bool) $options['allowModifications'];
127 }
128 if (isset($options['nestSeparator'])) {
129 $this->_nestSeparator = (string) $options['nestSeparator'];
130 }
131 if (isset($options['skipExtends'])) {
132 $this->_skipExtends = (bool) $options['skipExtends'];
133 }
134 }
135
136 $iniArray = $this->_loadIniFile($dbc, $db_table, $section);
137 $section = null;
138
139 if (null === $section) {
140 // Load entire file
141 $dataArray = array();
142 foreach ($iniArray as $sectionName => $sectionData) {
143 if(!is_array($sectionData)) {
144 $dataArray = array_merge_recursive($dataArray, $this->_processKey(array(), $sectionName, $sectionData));
145 } else {
146 $dataArray[$sectionName] = $this->_processSection($iniArray, $sectionName);
147 }
148 }
149 parent::__construct($dataArray, $allowModifications);
150 } else {
151 // Load one or more sections
152 if (!is_array($section)) {
153 $section = array($section);
154 }
155 $dataArray = array();
156 foreach ($section as $sectionName) {
157 if (!isset($iniArray[$sectionName])) {
158 /**
159 * @see Zend_Config_Exception
160 */
161 require_once 'Zend/Config/Exception.php';
162 throw new Zend_Config_Exception("Section '$sectionName' cannot be found");
163 }
164 $dataArray = array_merge($this->_processSection($iniArray, $sectionName), $dataArray);
165
166 }
167 parent::__construct($dataArray, $allowModifications);
168 }
169
170 $this->_loadedSection = $section;
171 }
172
173 /**
174 * Load data from database and preprocess the section separator (':' in the
175 * section name (that is used for section extension) so that the resultant
176 * array has the correct section names and the extension information is
177 * stored in a sub-key called ';extends'. We use ';extends' as this can
178 * never be a valid key name in an INI file that has been loaded using
179 * parse_ini_file().
180 *
181 * @param Zend_Db $dbc
182 * @param string $db_table
183 * @throws Zend_Config_Exception
184 * @return array
185 */
186 protected function _loadIniFile($dbc, $db_table, $section = null)
187 {
188 set_error_handler(array($this, '_loadFileErrorHandler'));
189 $loaded = $this->_parse_ini_db($dbc, $db_table, $section); // Warnings and errors are suppressed
190 restore_error_handler();
191 // Check if there was a error while loading file
192 if ($this->_loadFileErrorStr !== null) {
193 /**
194 * @see Zend_Config_Exception
195 */
196 require_once 'Zend/Config/Exception.php';
197 throw new Zend_Config_Exception($this->_loadFileErrorStr);
198 }
199
200 $iniArray = array();
201 foreach ($loaded as $key => $data)
202 {
203 $pieces = explode($this->_sectionSeparator, $key);
204 $thisSection = trim($pieces[0]);
205 switch (count($pieces)) {
206 case 1:
207 $iniArray[$thisSection] = $data;
208 break;
209
210 case 2:
211 $extendedSection = trim($pieces[1]);
212 $iniArray[$thisSection] = array_merge(array(';extends'=>$extendedSection), $data);
213 break;
214
215 default:
216 /**
217 * @see Zend_Config_Exception
218 */
219 require_once 'Zend/Config/Exception.php';
220 throw new Zend_Config_Exception("Section '$thisSection' may not extend multiple sections");
221 }
222 }
223
224 return $iniArray;
225 }
226
227 /**
228 * read config from (current db in $dbc).$db_table
229 *
230 * @param Zend_Db $dbc
231 * @param string $db_table
232 * @param string $section
233 * @return array
234 */
235 protected function _parse_ini_db($dbc, $db_table, $section) {
236 $sql = 'select * from ' . $db_table;
237 if ($section !== null) {
238 $sql .= ' where ' . $section;
239 }
240
241 $db_config = $dbc->query($sql);
242
243 $config = array();
244
245 while (($row = $db_config->fetch()) !== false) {
246 $key = explode('.', $row['config_key']);
247 $depth = count($key);
248 $ci = &$config;
249 for ($cnt = 0; $cnt < $depth; $cnt++) {
250 if ($cnt == ($depth - 1))
251 $ci[$key[$cnt]] = $row['config_value'];
252 elseif (!isset($ci[$key[$cnt]]))
253 $ci[$key[$cnt]] = array();
254 $ci = &$ci[$key[$cnt]];
255 }
256 }
257 return $config;
258 }
259
260 /**
261 * Process each element in the section and handle the ";extends" inheritance
262 * key. Passes control to _processKey() to handle the nest separator
263 * sub-property syntax that may be used within the key name.
264 *
265 * @param array $iniArray
266 * @param string $section
267 * @param array $config
268 * @throws Zend_Config_Exception
269 * @return array
270 */
271 protected function _processSection($iniArray, $section, $config = array())
272 {
273 $thisSection = $iniArray[$section];
274
275 foreach ($thisSection as $key => $value) {
276 if (strtolower($key) == ';extends') {
277 if (isset($iniArray[$value])) {
278 $this->_assertValidExtend($section, $value);
279
280 if (!$this->_skipExtends) {
281 $config = $this->_processSection($iniArray, $value, $config);
282 }
283 } else {
284 /**
285 * @see Zend_Config_Exception
286 */
287 require_once 'Zend/Config/Exception.php';
288 throw new Zend_Config_Exception("Parent section '$section' cannot be found");
289 }
290 } else {
291 $config = $this->_processKey($config, $key, $value);
292 }
293 }
294 return $config;
295 }
296
297 /**
298 * Assign the key's value to the property list. Handles the
299 * nest separator for sub-properties.
300 *
301 * @param array $config
302 * @param string $key
303 * @param string $value
304 * @throws Zend_Config_Exception
305 * @return array
306 */
307 protected function _processKey($config, $key, $value)
308 {
309 if (strpos($key, $this->_nestSeparator) !== false) {
310 $pieces = explode($this->_nestSeparator, $key, 2);
311 if (strlen($pieces[0]) && strlen($pieces[1])) {
312 if (!isset($config[$pieces[0]])) {
313 if ($pieces[0] === '0' && !empty($config)) {
314 // convert the current values in $config into an array
315 $config = array($pieces[0] => $config);
316 } else {
317 $config[$pieces[0]] = array();
318 }
319 } elseif (!is_array($config[$pieces[0]])) {
320 /**
321 * @see Zend_Config_Exception
322 */
323 require_once 'Zend/Config/Exception.php';
324 throw new Zend_Config_Exception("Cannot create sub-key for '{$pieces[0]}' as key already exists");
325 }
326 $config[$pieces[0]] = $this->_processKey($config[$pieces[0]], $pieces[1], $value);
327 } else {
328 /**
329 * @see Zend_Config_Exception
330 */
331 require_once 'Zend/Config/Exception.php';
332 throw new Zend_Config_Exception("Invalid key '$key'");
333 }
334 } else {
335 $config[$key] = $value;
336 }
337 return $config;
338 }
339 }