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