1: <?php
2: /**
3: * IniReader
4: *
5: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7: *
8: * Licensed under The MIT License
9: * For full copyright and license information, please see the LICENSE.txt
10: * Redistributions of files must retain the above copyright notice.
11: *
12: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13: * @link http://cakephp.org CakePHP(tm) Project
14: * @package Cake.Configure
15: * @since CakePHP(tm) v 2.0
16: * @license http://www.opensource.org/licenses/mit-license.php MIT License
17: */
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: */
98: public function read($key) {
99: if (strpos($key, '..') !== false) {
100: throw new ConfigureException(__d('cake_dev', 'Cannot load configuration files with ../ in them.'));
101: }
102:
103: $file = $this->_getFilePath($key);
104: if (!is_file($file)) {
105: throw new ConfigureException(__d('cake_dev', 'Could not load configuration file: %s', $file));
106: }
107:
108: $contents = parse_ini_file($file, true);
109: if (!empty($this->_section) && isset($contents[$this->_section])) {
110: $values = $this->_parseNestedValues($contents[$this->_section]);
111: } else {
112: $values = array();
113: foreach ($contents as $section => $attribs) {
114: if (is_array($attribs)) {
115: $values[$section] = $this->_parseNestedValues($attribs);
116: } else {
117: $parse = $this->_parseNestedValues(array($attribs));
118: $values[$section] = array_shift($parse);
119: }
120: }
121: }
122: return $values;
123: }
124:
125: /**
126: * parses nested values out of keys.
127: *
128: * @param array $values Values to be exploded.
129: * @return array Array of values exploded
130: */
131: protected function _parseNestedValues($values) {
132: foreach ($values as $key => $value) {
133: if ($value === '1') {
134: $value = true;
135: }
136: if ($value === '') {
137: $value = false;
138: }
139: unset($values[$key]);
140: if (strpos($key, '.') !== false) {
141: $values = Hash::insert($values, $key, $value);
142: } else {
143: $values[$key] = $value;
144: }
145: }
146: return $values;
147: }
148:
149: /**
150: * Dumps the state of Configure data into an ini formatted string.
151: *
152: * @param string $key The identifier to write to. If the key has a . it will be treated
153: * as a plugin prefix.
154: * @param array $data The data to convert to ini file.
155: * @return integer Bytes saved.
156: */
157: public function dump($key, $data) {
158: $result = array();
159: foreach ($data as $k => $value) {
160: $isSection = false;
161: if ($k[0] !== '[') {
162: $result[] = "[$k]";
163: $isSection = true;
164: }
165: if (is_array($value)) {
166: $kValues = Hash::flatten($value, '.');
167: foreach ($kValues as $k2 => $v) {
168: $result[] = "$k2 = " . $this->_value($v);
169: }
170: }
171: if ($isSection) {
172: $result[] = '';
173: }
174: }
175: $contents = trim(implode("\n", $result));
176:
177: $filename = $this->_getFilePath($key);
178: return file_put_contents($filename, $contents);
179: }
180:
181: /**
182: * Converts a value into the ini equivalent
183: *
184: * @param mixed $value to export.
185: * @return string String value for ini file.
186: */
187: protected function _value($val) {
188: if ($val === null) {
189: return 'null';
190: }
191: if ($val === true) {
192: return 'true';
193: }
194: if ($val === false) {
195: return 'false';
196: }
197: return (string)$val;
198: }
199:
200: /**
201: * Get file path
202: *
203: * @param string $key The identifier to write to. If the key has a . it will be treated
204: * as a plugin prefix.
205: * @return string Full file path
206: */
207: protected function _getFilePath($key) {
208: if (substr($key, -8) === '.ini.php') {
209: $key = substr($key, 0, -8);
210: list($plugin, $key) = pluginSplit($key);
211: $key .= '.ini.php';
212: } else {
213: if (substr($key, -4) === '.ini') {
214: $key = substr($key, 0, -4);
215: }
216: list($plugin, $key) = pluginSplit($key);
217: $key .= '.ini';
218: }
219:
220: if ($plugin) {
221: $file = App::pluginPath($plugin) . 'Config' . DS . $key;
222: } else {
223: $file = $this->_path . $key;
224: }
225:
226: return $file;
227: }
228:
229: }
230: