1: <?php
2: /**
3: * IniReader
4: *
5: * PHP 5
6: *
7: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * Redistributions of files must retain the above copyright notice.
12: *
13: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
14: * @link http://cakephp.org CakePHP(tm) Project
15: * @package Cake.Configure
16: * @since CakePHP(tm) v 2.0
17: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
18: */
19: App::uses('Hash', 'Utility');
20:
21: /**
22: * Ini file configuration engine.
23: *
24: * Since IniReader uses parse_ini_file underneath, you should be aware that this
25: * class shares the same behavior, especially with regards to boolean and null values.
26: *
27: * In addition to the native `parse_ini_file` features, IniReader also allows you
28: * to create nested array structures through usage of `.` delimited names. This allows
29: * you to create nested arrays structures in an ini config file. For example:
30: *
31: * `db.password = secret` would turn into `array('db' => array('password' => 'secret'))`
32: *
33: * You can nest properties as deeply as needed using `.`'s. In addition to using `.` you
34: * can use standard ini section notation to create nested structures:
35: *
36: * {{{
37: * [section]
38: * key = value
39: * }}}
40: *
41: * Once loaded into Configure, the above would be accessed using:
42: *
43: * `Configure::read('section.key');
44: *
45: * You can combine `.` separated values with sections to create more deeply
46: * nested structures.
47: *
48: * IniReader also manipulates how the special ini values of
49: * 'yes', 'no', 'on', 'off', 'null' are handled. These values will be
50: * converted to their boolean equivalents.
51: *
52: * @package Cake.Configure
53: * @see http://php.net/parse_ini_file
54: */
55: class IniReader implements ConfigReaderInterface {
56:
57: /**
58: * The path to read ini files from.
59: *
60: * @var array
61: */
62: protected $_path;
63:
64: /**
65: * The section to read, if null all sections will be read.
66: *
67: * @var string
68: */
69: protected $_section;
70:
71: /**
72: * Build and construct a new ini file parser. The parser can be used to read
73: * ini files that are on the filesystem.
74: *
75: * @param string $path Path to load ini config files from. Defaults to APP . 'Config' . DS
76: * @param string $section Only get one section, leave null to parse and fetch
77: * all sections in the ini file.
78: */
79: public function __construct($path = null, $section = null) {
80: if (!$path) {
81: $path = APP . 'Config' . DS;
82: }
83: $this->_path = $path;
84: $this->_section = $section;
85: }
86:
87: /**
88: * Read an ini file and return the results as an array.
89: *
90: * For backwards compatibility, acl.ini.php will be treated specially until 3.0.
91: *
92: * @param string $key The identifier to read from. If the key has a . it will be treated
93: * as a plugin prefix. The chosen file must be on the reader's path.
94: * @return array Parsed configuration values.
95: * @throws ConfigureException when files don't exist.
96: * Or when files contain '..' as this could lead to abusive reads.
97: * @throws ConfigureException
98: */
99: public function read($key) {
100: if (strpos($key, '..') !== false) {
101: throw new ConfigureException(__d('cake_dev', 'Cannot load configuration files with ../ in them.'));
102: }
103: if (substr($key, -8) === '.ini.php') {
104: $key = substr($key, 0, -8);
105: list($plugin, $key) = pluginSplit($key);
106: $key .= '.ini.php';
107: } else {
108: if (substr($key, -4) === '.ini') {
109: $key = substr($key, 0, -4);
110: }
111: list($plugin, $key) = pluginSplit($key);
112: $key .= '.ini';
113: }
114:
115: if ($plugin) {
116: $file = App::pluginPath($plugin) . 'Config' . DS . $key;
117: } else {
118: $file = $this->_path . $key;
119: }
120: if (!is_file($file)) {
121: throw new ConfigureException(__d('cake_dev', 'Could not load configuration file: %s', $file));
122: }
123:
124: $contents = parse_ini_file($file, true);
125: if (!empty($this->_section) && isset($contents[$this->_section])) {
126: $values = $this->_parseNestedValues($contents[$this->_section]);
127: } else {
128: $values = array();
129: foreach ($contents as $section => $attribs) {
130: if (is_array($attribs)) {
131: $values[$section] = $this->_parseNestedValues($attribs);
132: } else {
133: $parse = $this->_parseNestedValues(array($attribs));
134: $values[$section] = array_shift($parse);
135: }
136: }
137: }
138: return $values;
139: }
140:
141: /**
142: * parses nested values out of keys.
143: *
144: * @param array $values Values to be exploded.
145: * @return array Array of values exploded
146: */
147: protected function _parseNestedValues($values) {
148: foreach ($values as $key => $value) {
149: if ($value === '1') {
150: $value = true;
151: }
152: if ($value === '') {
153: $value = false;
154: }
155: unset($values[$key]);
156: if (strpos($key, '.') !== false) {
157: $values = Hash::insert($values, $key, $value);
158: } else {
159: $values[$key] = $value;
160: }
161: }
162: return $values;
163: }
164:
165: /**
166: * Dumps the state of Configure data into an ini formatted string.
167: *
168: * @param string $filename The filename on $this->_path to save into.
169: * Extension ".ini" will be automatically appended if not included in filename.
170: * @param array $data The data to convert to ini file.
171: * @return int Bytes saved.
172: */
173: public function dump($filename, $data) {
174: $result = array();
175: foreach ($data as $key => $value) {
176: if ($key[0] != '[') {
177: $result[] = "[$key]";
178: }
179: if (is_array($value)) {
180: $keyValues = Hash::flatten($value, '.');
181: foreach ($keyValues as $k => $v) {
182: $result[] = "$k = " . $this->_value($v);
183: }
184: }
185: }
186: $contents = join("\n", $result);
187:
188: if (substr($filename, -4) !== '.ini') {
189: $filename .= '.ini';
190: }
191: return file_put_contents($this->_path . $filename, $contents);
192: }
193:
194: /**
195: * Converts a value into the ini equivalent
196: *
197: * @param mixed $value to export.
198: * @return string String value for ini file.
199: */
200: protected function _value($val) {
201: if ($val === null) {
202: return 'null';
203: }
204: if ($val === true) {
205: return 'true';
206: }
207: if ($val === false) {
208: return 'false';
209: }
210: return (string)$val;
211: }
212:
213: }
214: