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