1 <?php
2 /**
3 * Part of the Joomla Framework Registry Package
4 *
5 * @copyright Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
6 * @license GNU General Public License version 2 or later; see LICENSE
7 */
8
9 namespace Joomla\Registry\Format;
10
11 use Joomla\Registry\AbstractRegistryFormat;
12 use Joomla\Utilities\ArrayHelper;
13 use stdClass;
14
15 /**
16 * INI format handler for Registry.
17 *
18 * @since 1.0
19 */
20 class Ini extends AbstractRegistryFormat
21 {
22 /**
23 * Default options array
24 *
25 * @var array
26 * @since 1.3.0
27 */
28 protected static $options = array(
29 'supportArrayValues' => false,
30 'parseBooleanWords' => false,
31 'processSections' => false,
32 );
33
34 /**
35 * A cache used by stringToobject.
36 *
37 * @var array
38 * @since 1.0
39 */
40 protected static $cache = array();
41
42 /**
43 * Converts an object into an INI formatted string
44 * - Unfortunately, there is no way to have ini values nested further than two
45 * levels deep. Therefore we will only go through the first two levels of
46 * the object.
47 *
48 * @param object $object Data source object.
49 * @param array $options Options used by the formatter.
50 *
51 * @return string INI formatted string.
52 *
53 * @since 1.0
54 */
55 public function objectToString($object, $options = array())
56 {
57 $options = array_merge(self::$options, $options);
58
59 $local = array();
60 $global = array();
61
62 $variables = get_object_vars($object);
63
64 $last = count($variables);
65
66 // Assume that the first element is in section
67 $in_section = true;
68
69 // Iterate over the object to set the properties.
70 foreach ($variables as $key => $value)
71 {
72 // If the value is an object then we need to put it in a local section.
73 if (is_object($value))
74 {
75 // Add an empty line if previous string wasn't in a section
76 if (!$in_section)
77 {
78 $local[] = '';
79 }
80
81 // Add the section line.
82 $local[] = '[' . $key . ']';
83
84 // Add the properties for this section.
85 foreach (get_object_vars($value) as $k => $v)
86 {
87 if (is_array($v) && $options['supportArrayValues'])
88 {
89 $assoc = ArrayHelper::isAssociative($v);
90
91 foreach ($v as $array_key => $item)
92 {
93 $array_key = ($assoc) ? $array_key : '';
94 $local[] = $k . '[' . $array_key . ']=' . $this->getValueAsIni($item);
95 }
96 }
97 else
98 {
99 $local[] = $k . '=' . $this->getValueAsIni($v);
100 }
101 }
102
103 // Add empty line after section if it is not the last one
104 if (0 != --$last)
105 {
106 $local[] = '';
107 }
108 }
109 elseif (is_array($value) && $options['supportArrayValues'])
110 {
111 $assoc = ArrayHelper::isAssociative($value);
112
113 foreach ($value as $array_key => $item)
114 {
115 $array_key = ($assoc) ? $array_key : '';
116 $global[] = $key . '[' . $array_key . ']=' . $this->getValueAsIni($item);
117 }
118 }
119 else
120 {
121 // Not in a section so add the property to the global array.
122 $global[] = $key . '=' . $this->getValueAsIni($value);
123 $in_section = false;
124 }
125 }
126
127 return implode("\n", array_merge($global, $local));
128 }
129
130 /**
131 * Parse an INI formatted string and convert it into an object.
132 *
133 * @param string $data INI formatted string to convert.
134 * @param array $options An array of options used by the formatter, or a boolean setting to process sections.
135 *
136 * @return object Data object.
137 *
138 * @since 1.0
139 */
140 public function stringToObject($data, array $options = array())
141 {
142 $options = array_merge(self::$options, $options);
143
144 // Check the memory cache for already processed strings.
145 $hash = md5($data . ':' . (int) $options['processSections']);
146
147 if (isset(self::$cache[$hash]))
148 {
149 return self::$cache[$hash];
150 }
151
152 // If no lines present just return the object.
153 if (empty($data))
154 {
155 return new stdClass;
156 }
157
158 $obj = new stdClass;
159 $section = false;
160 $array = false;
161 $lines = explode("\n", $data);
162
163 // Process the lines.
164 foreach ($lines as $line)
165 {
166 // Trim any unnecessary whitespace.
167 $line = trim($line);
168
169 // Ignore empty lines and comments.
170 if (empty($line) || ($line{0} == ';'))
171 {
172 continue;
173 }
174
175 if ($options['processSections'])
176 {
177 $length = strlen($line);
178
179 // If we are processing sections and the line is a section add the object and continue.
180 if (($line[0] == '[') && ($line[$length - 1] == ']'))
181 {
182 $section = substr($line, 1, $length - 2);
183 $obj->$section = new stdClass;
184 continue;
185 }
186 }
187 elseif ($line{0} == '[')
188 {
189 continue;
190 }
191
192 // Check that an equal sign exists and is not the first character of the line.
193 if (!strpos($line, '='))
194 {
195 // Maybe throw exception?
196 continue;
197 }
198
199 // Get the key and value for the line.
200 list ($key, $value) = explode('=', $line, 2);
201
202 // If we have an array item
203 if (substr($key, -1) == ']' && ($open_brace = strpos($key, '[', 1)) !== false)
204 {
205 if ($options['supportArrayValues'])
206 {
207 $array = true;
208 $array_key = substr($key, $open_brace + 1, -1);
209
210 // If we have a multi-dimensional array or malformed key
211 if (strpos($array_key, '[') !== false || strpos($array_key, ']') !== false)
212 {
213 // Maybe throw exception?
214 continue;
215 }
216
217 $key = substr($key, 0, $open_brace);
218 }
219 else
220 {
221 continue;
222 }
223 }
224
225 // Validate the key.
226 if (preg_match('/[^A-Z0-9_]/i', $key))
227 {
228 // Maybe throw exception?
229 continue;
230 }
231
232 // If the value is quoted then we assume it is a string.
233 $length = strlen($value);
234
235 if ($length && ($value[0] == '"') && ($value[$length - 1] == '"'))
236 {
237 // Strip the quotes and Convert the new line characters.
238 $value = stripcslashes(substr($value, 1, ($length - 2)));
239 $value = str_replace('\n', "\n", $value);
240 }
241 else
242 {
243 // If the value is not quoted, we assume it is not a string.
244
245 // If the value is 'false' assume boolean false.
246 if ($value == 'false')
247 {
248 $value = false;
249 }
250 elseif ($value == 'true')
251 // If the value is 'true' assume boolean true.
252 {
253 $value = true;
254 }
255 elseif ($options['parseBooleanWords'] && in_array(strtolower($value), array('yes', 'no')))
256 // If the value is 'yes' or 'no' and option is enabled assume appropriate boolean
257 {
258 $value = (strtolower($value) == 'yes');
259 }
260 elseif (is_numeric($value))
261 // If the value is numeric than it is either a float or int.
262 {
263 // If there is a period then we assume a float.
264 if (strpos($value, '.') !== false)
265 {
266 $value = (float) $value;
267 }
268 else
269 {
270 $value = (int) $value;
271 }
272 }
273 }
274
275 // If a section is set add the key/value to the section, otherwise top level.
276 if ($section)
277 {
278 if ($array)
279 {
280 if (!isset($obj->$section->$key))
281 {
282 $obj->$section->$key = array();
283 }
284
285 if (!empty($array_key))
286 {
287 $obj->$section->{$key}[$array_key] = $value;
288 }
289 else
290 {
291 $obj->$section->{$key}[] = $value;
292 }
293 }
294 else
295 {
296 $obj->$section->$key = $value;
297 }
298 }
299 else
300 {
301 if ($array)
302 {
303 if (!isset($obj->$key))
304 {
305 $obj->$key = array();
306 }
307
308 if (!empty($array_key))
309 {
310 $obj->{$key}[$array_key] = $value;
311 }
312 else
313 {
314 $obj->{$key}[] = $value;
315 }
316 }
317 else
318 {
319 $obj->$key = $value;
320 }
321 }
322
323 $array = false;
324 }
325
326 // Cache the string to save cpu cycles -- thus the world :)
327 self::$cache[$hash] = clone $obj;
328
329 return $obj;
330 }
331
332 /**
333 * Method to get a value in an INI format.
334 *
335 * @param mixed $value The value to convert to INI format.
336 *
337 * @return string The value in INI format.
338 *
339 * @since 1.0
340 */
341 protected function getValueAsIni($value)
342 {
343 $string = '';
344
345 switch (gettype($value))
346 {
347 case 'integer':
348 case 'double':
349 $string = $value;
350 break;
351
352 case 'boolean':
353 $string = $value ? 'true' : 'false';
354 break;
355
356 case 'string':
357 // Sanitize any CRLF characters..
358 $string = '"' . str_replace(array("\r\n", "\n"), '\\n', $value) . '"';
359 break;
360 }
361
362 return $string;
363 }
364 }
365