parse_ini_file can't deal with const which cancate a string. For example, if test.ini file is
classPath = ROOT/lib
If you:
<?php
define('ROOT', dirname(__FILE__));
$buf = parse_ini_file('test.ini');
?>
const ROOT would't be parsed.
But my version could work find.
<?php
// array parse_ini_file ( string $filename [, bool $process_sections] )
function parse_ini($filename, $process_sections = false)
{
function replace_process(& $item, $key, $consts)
{
$item = str_replace(array_keys($consts), array_values($consts), $item);
}
$buf = get_defined_constants(true); // PHP version > 5.0
$consts = $buf['user'];
$ini = parse_ini_file($filename, $process_sections);
array_walk_recursive($ini, 'replace_process', $consts);
return $ini;
}
define('ROOT', '/test');
print_r(parse_ini(dirname(__FILE__).'/test.ini'));
?>
parse_ini_file
(PHP 4, PHP 5)
parse_ini_file — Analysiert eine Konfigurationsdatei
Beschreibung
$filename
[, bool $process_sections
] )
parse_ini_file() lädt die in
filename angegebene Datei, und gibt
die darin enthaltenen Einstellungen in einem assoziativen
Array zurück. Setzen Sie den letzten Parameter
process_sections auf TRUE, erhalten
Sie ein mehrdimensionales Array mit den Gruppennamen und
Einstellungen. Ist process_sections
nicht angegeben, wird FALSE angenommen.
Hinweis:
Diese Funktion hat nichts mit der php.ini zu tun, denn diese ist bereits abgearbeitet, wenn Sie Ihr Skript ausführen. Diese Funktion ist vorgesehen, um Konfigurationsdateien für Ihre eigenen Applikationen einzulesen.
Hinweis:
Enthält ein Wert in der ini Datei nicht alphanumerische Zeichen, so muss dieser von doppelten Anführungszeichen (") eingeschlossen sein.
Hinweis: Seit PHP 4.2.1 wird diese Funktion auch von Safe Mode und open_basedir beeinflusst.
Die Struktur der ini Datei ist der von php.ini ähnlich.
Konstanten können in der ini Datei ebenfalls geparsed werden. Wenn Sie eine Konstante als einen ini Wert definieren bevor Sie parse_ini_file() aufrufen, wird diese in den Ergebnissen enthalten sein. Es werden nur ini Werte ausgewertet. Zum Beispiel:
Beispiel #1 Inhalt der sample.ini
; Dies ist ein Beispiel für eine Konfigurationsdatei ; Kommentare beginnen wie in der php.ini mit ';' [erste_gruppe] eins = 1 fünf = 5 tier = VOGEL [zweite_gruppe] pfad = /usr/local/bin URL = "http://www.example.com/~username" [dritte_gruppe] phpversion[] = "5.0" phpversion[] = "5.1" phpversion[] = "5.2" phpversion[] = "5.3"
Beispiel #2 parse_ini_file()
<?php
define ('VOGEL', 'Amsel');
// Ohne Gruppen analysieren
$ini_array = parse_ini_file("sample.ini");
print_r($ini_array);
// Mit Gruppen analysieren
$ini_array = parse_ini_file("sample.ini", TRUE);
print_r($ini_array);
?>
Würde wie folgt ausgeben:
Array
(
[eins] => 1
[fünf] => 5
[tier] => Amsel
[pfad] => /usr/local/bin
[URL] => http://www.example.com/~username
[phpversion] => Array
(
[0] => 5.0
[1] => 5.1
[2] => 5.2
[3] => 5.3
)
)
Array
(
[erste_gruppe] => Array
(
[eins] => 1
[fünf] => 5
[tier] => Amsel
)
[zweite_gruppe] => Array
(
[pfad] => /usr/local/bin
[URL] => http://www.example.com/~username
)
[dritte_gruppe] => Array
(
[phpversion] => Array
(
[0] => 5.0
[1] => 5.1
[2] => 5.2
[3] => 5.3
)
)
)
parse_ini_file
29-Jun-2007 10:46
25-Jun-2007 07:45
Arrays can be defined in the ini file by adding '[]' at the end of a key name. For example:
value1 = 17
value2 = 13
value3[] = a
value3[] = b
value3[] = c
Will return:
Array
(
[value1] => 17
[value2] => 13
[value3] => Array
(
[0] => a
[1] => b
[2] => c
)
)
25-May-2007 09:42
I wrote few functions to work with ini files.
The function make_ini_file($array, &$errors)
The function read_ini($file)
The function prepare_ini($array, $maxdepth=NULL)
The function prepare_ini($array, $maxdepth=NULL)
This function will take an array as returned by the function read_ini() and will return an array as needed by the function make_ini_file() so that you can write extanded ini files easily.
If maxdepth is not given (or if maxdepth is NULL), this function will try to create sections so the keys in the sections do not have dots. if maxdepth is given, it will create sections with $maxdepth members in them (or less if it is not possible). It won't use the special key name "."
<?php
function prepare_ini($arr, $maxdepth=NULL){
$res = array();
prepare_ini__1($res, $arr, $maxdepth);
return $res;
}
function prepare_ini__1(
&$res, $arr, $maxdepth,
$prefix1="", $prefix2="", $depth=0,
$self='prepare_ini__1')
{
foreach($arr as $key=>$val){
if(is_array($val)){
if(is_null($maxdepth) or $depth < $maxdepth){
$newprefix = $prefix1 ? "$prefix1.$key" : $key;
$self($res, $val, $maxdepth, $newprefix, $prefix2, $depth+1);
}else{
$newprefix = $prefix2 ? "$prefix1.$key" : $key;
$self($res, $val, $maxdepth, $prefix1, $newprefix, $depth+1);
}
}else{
$newprefix = $prefix2 ? "$prefix2.$key" : $key;
if(!isset($res[$prefix1])) $res[$prefix1] = array();
$res[$prefix1][$newprefix] = $val;
}
}
}
// kate: indent-width 4; tab-width 8; space-indent on;
// kate: replace-tabs off; remove-trailing-space on;
?>
27-Mar-2007 10:39
or to prevent the file being viewed you can just use a .htaccess file and add this line
<files *.ini>
order deny,allow
deny from all
</files>
i use a similar thing to prevent my config files being accessed
02-Feb-2007 07:22
I modified phpcoder's readINIFile function to allow multi-lined values. Adding a backslash (\) to the end of a line indicates that the whole of the next line should be appended to the value. Leading whitespace is ignored on continues lines, whitespace before the backslash is preserved. This is the same as the Java Properties spec: http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html
<?php
function readINIfile ($filename, $commentchar) {
$array1 = file($filename);
$section = '';
for ($line_num = 0; $line_num <= sizeof($array1); $line_num++) {
$filedata = $array1[$line_num];
$dataline = trim($filedata);
$firstchar = substr($dataline, 0, 1);
if ($firstchar!=$commentchar && $dataline!='') {
//It's an entry (not a comment and not a blank line)
if ($firstchar == '[' && substr($dataline, -1, 1) == ']') {
//It's a section
$section = strtolower(substr($dataline, 1, -1));
}else{
//It's a key...
$delimiter = strpos($dataline, '=');
if ($delimiter > 0) {
//...with a value
$key = strtolower(trim(substr($dataline, 0, $delimiter)));
$array2[$section][$key] = '';
$value = trim(substr($dataline, $delimiter + 1));
while (substr($value, -1, 1) == '\\') {
//...value continues on the next line
$value = substr($value, 0, strlen($value)-1);
$array2[$section][$key] .= stripcslashes($value);
$line_num++;
$value = trim($array1[$line_num]);
}
$array2[$section][$key] .= stripcslashes($value);
$array2[$section][$key] = trim($array2[$section][$key]);
if (substr($array2[$section][$key], 0, 1) == '"' && substr($array2[$section][$key], -1, 1) == '"') {
$array2[$section][$key] = substr($array2[$section][$key], 1, -1);
}
}else{
//...without a value
$array2[$section][strtolower(trim($dataline))]='';
}
}
}else{
//It's a comment or blank line. Ignore.
}
}
return $array2;
}
?>
03-Jan-2007 04:54
The ahull version of the parse_ini_file_quotes_safe can not handle unicode... the original version from Julio L Garbayo can.
15-Nov-2006 06:09
A number of posts mention using pear::Config as a replacement for this function. Note however that internally it uses parse_ini_file to read the ini file, so it suffers from the same limitations.
31-Oct-2006 08:46
This is a simple (but slightly hackish) way of avoiding the character limitations (in values):
<?php
define('QUOTE', '"');
$test = parse_ini_file('test.ini');
echo "<pre>";
print_r($test);
?>
contents of test.ini:
park yesterday = "I (walked) | {to} " QUOTE"the"QUOTE " park yesterday & saw ~three~ dogs!"
output:
<?php
Array
(
[park yesterday] => I (walked) | {to} "the" park yesterday & saw ~three~ dogs!
)
?>
this function won't parse a remote INI file, even with allow_url_fopen turned on.
01-Oct-2006 09:26
If you are looking for an OOP way to parse ini files, take a look at Marcus Boerger's IniGroups class available here :
http://www.php.net/~helly/php/ext/spl/classIniGroups.html
18-Sep-2006 02:46
or better
on first line :
;<?php exit(' you won\'t see my ini file'); ?>
upgrade of "mauder[remove] at [remove]gmail[remove] dot com" idea of hiding ini content from being seen.
file.ini.php
first line:
;<?/*
last line:
;*/?>
will result ";" in browser, not "pharse error: (...)".
24-Mar-2006 05:27
In addition to the note that "Parsing an ini file stops at a key named 'none'".
Values of 'none' do not return as the string 'none'. They return nothing at all, however this does not halt the processing of the ini file.
22-Feb-2006 02:12
I ran into a snag where I wanted to have an INI file for a library. All attempts to parse the file from the library, apart from hardcoded path qualification, failed because it couldn't find the INI file. Some of the php functions will optionally use the include path. Adding this to the parse_ini_file() function would permit its use in this way and would encourage not putting INI files in document root.
16-Feb-2006 01:29
Beside the mentioned reserved words 'null', 'yes', 'no', 'true', and 'false', also 'none' seems to be a reserved word. Parsing an ini file stops at a key named 'none'.
14-Feb-2006 01:31
Be careful if you put any .ini file in your readable directories, if somebody would know the name (e.g. if your application is widely used), the webserver might return it as plain text.
For example : your database username and password could be exposed, if it is stored in that file !
To prevent this from happening :
- give the file .php extension : "my.ini.php"
- put ';<?php' (without quotes and without X between X and php) on first line
- put ';?>' on last line
The server would run the ini file as being PHP-code, but will do nothing due to bad syntax, preventing the content from being exosed.
On the other hand, it is still a valid .ini file...
HTH !
26-Jan-2006 10:33
I had a look at the code for function parse_ini_file_quotes_safe(
and added in the ability to preserve comments.
<?php
// Parse a file into an array following the rules for ini files as follows
//
// Looks for [] characters to mark section headings and = chars to mark the break between the key and its values.
// Also keeps comments delimited by any of the characters in $comments_chars in the array numbered as they are found.
//
// Note writing back the array will necessarily move the comments to the beginning of the section,
// even if they are found within
// a section simply because there is no exact place-holder information stored in the array.
// This could of course be a problem.
// Also the Write array routine will have to be modified
// to correctly write back comments otherwise they will appear as blank sections called [comment{x}]
function parse_ini_file_quotes_safe($f)
{
$newline = "<br>";
$null = "";
$r=$null;
$first_char = "";
$sec=$null;
$comment_chars="/*<;#?>";
$num_comments = "0";
$header_section = "";
//Read to end of file with the newlines still attached into $f
$f=@file($f);
// Process all lines from 0 to count($f)
for ($i=0;$i<@count($f);$i++)
{
$newsec=0;
$w=@trim($f[$i]);
$first_char = @substr($w,0,1);
if ($w)
{
if ((!$r) or ($sec))
{
// Look for [] chars round section headings
if ((@substr($w,0,1)=="[") and (@substr($w,-1,1))=="]") {$sec=@substr($w,1,@strlen($w)-2);$newsec=1;}
// Look for comments and number into array
if ((stristr($comment_chars, $first_char) === FALSE)) {} else {$sec=$w;$k="Comment".$num_comments;$num_comments = $num_comments +1;$v=$w;$newsec=1;$r[$k]=$v;echo "comment".$w.$newline;}
//
}
if (!$newsec)
{
//
// Look for the = char to allow us to split the section into key and value
$w=@explode("=",$w);$k=@trim($w[0]);unset($w[0]); $v=@trim(@implode("=",$w));
// look for the new lines
if ((@substr($v,0,1)=="\"") and (@substr($v,-1,1)=="\"")) {$v=@substr($v,1,@strlen($v)-2);}
if ($sec) {$r[$sec][$k]=$v;} else {$r[$k]=$v;}
}
}
}
return $r;
}
?>
21-Oct-2005 08:45
[A feature request for a third parameter, to turn off the following insecure behaviour has been submitted: http://bugs.php.net/bug.php?id=34949 - I'm just documenting it here so that people are aware that they need to take the insecurity of the current behaviour into consideration when programming.]
Be warned that, in its current (2-argument) form, this function should be avoided when processing user-provided ini files, as data leakage may occur if the user provides an ini file with unquoted string values.
To avoid this problem, it's vital that if your program stores any sensitive data in constants, that you either pre-scan the ini file for unquoted strings, or that you do not use this function.
A suitable pre-parse parser might be as follows.
This assumes that there are no non-word (a-zA-Z0-9_) characters in your keys, and minimises whitespace.
It tries to convert intelligently, like so:
value1 = value ; this is a comment
value2 = value; with semicolon in
to
value1 = "value" ; this is a comment
value2 = "value; with semicolon in"
<?php
$file = file_get_contents('user_provided.ini');
$file2 = preg_replace('/^
(\s*\w+\s*=\s*) # Part \1 - the key and initial whitespace.
( # Part \2 - the value to be quoted
(?:(?!\s;)[^"\r\n]) # Anything but \r, ", \s;, \n
*? # As little as possible of that, minimise whitespace.
)
( # Part \3 - everything after the value
\s* # Optional whitespace.
(?:\s;.*)? # Optional comment preceded by a space
)
$/mx', '\1"\2"\3', $file);
file_put_contents('user_provided.ini2', $file2);
?>
22-Sep-2005 10:53
I wrote a replacement function with following changes:
-It allows quotes and double quotes.
-It detects wether your .ini file has sections or not.
-It will read until eof in any case, even if a line contains errors.
I know it can be improved a lot, so feel free to work on it and, please, notify me if you do.
<?php
function parse_ini_file_quotes_safe($f)
{
$r=$null;
$sec=$null;
$f=@file($f);
for ($i=0;$i<@count($f);$i++)
{
$newsec=0;
$w=@trim($f[$i]);
if ($w)
{
if ((!$r) or ($sec))
{
if ((@substr($w,0,1)=="[") and (@substr($w,-1,1))=="]") {$sec=@substr($w,1,@strlen($w)-2);$newsec=1;}
}
if (!$newsec)
{
$w=@explode("=",$w);$k=@trim($w[0]);unset($w[0]); $v=@trim(@implode("=",$w));
if ((@substr($v,0,1)=="\"") and (@substr($v,-1,1)=="\"")) {$v=@substr($v,1,@strlen($v)-2);}
if ($sec) {$r[$sec][$k]=$v;} else {$r[$k]=$v;}
}
}
}
return $r;
}
?>
13-Sep-2005 05:42
kieran dot huggins at rogers dot com:
You can just use their HEX equivalent, like so:
0x20
13-Sep-2005 09:19
Slight modification of write_ini_file that will keep values global in an array if they appear after an array
<?php function write_ini_file($path, $assoc_array)
{
$content = '';
$sections = '';
foreach ($assoc_array as $key => $item)
{
if (is_array($item))
{
$sections .= "\n[{$key}]\n";
foreach ($item as $key2 => $item2)
{
if (is_numeric($item2) || is_bool($item2))
$sections .= "{$key2} = {$item2}\n";
else
$sections .= "{$key2} = \"{$item2}\"\n";
}
}
else
{
if(is_numeric($item) || is_bool($item))
$content .= "{$key} = {$item}\n";
else
$content .= "{$key} = \"{$item}\"\n";
}
}
$content .= $sections;
if (!$handle = fopen($path, 'w'))
{
return false;
}
if (!fwrite($handle, $content))
{
return false;
}
fclose($handle);
return true;
} ?>
21-Jul-2005 11:19
I have modified the code to delete double quote from values.
if (substr($value, 0, 1) == '"' && substr($value, -1) == '"') { $value = substr($value, 1, -1); }
14-Jul-2005 07:12
@phpcoder:
there is another small bug in your code. in readINIfile the line
if (substr($value, 1, 1) == '"' && substr($value, -1, 1) == '"') {$value = substr($value, 1, -1); }
should be
if (substr($value, 0, 1) == '"' && substr($value, -1, 1) == '"') {$value = substr($value, 1, -1); }
(note the 0 in the first substr statement)
otherwise if you read an ini file, save it and read again the items of the array will have " around them.
14-Jul-2005 06:33
Class to access ini values at format "section_name.property", for example $myconf->get("system.name") returns a property "name" in section "system":
class Settings {
var $properties = array();
function Settings() {
$this->properties = parse_ini_file(_SETTINGS_FILE, true);
}
function get($name) {
if(strpos($name, ".")) {
list($section_name, $property) = explode(".", $name);
$section =& $this->properties[$section_name];
$name = $property;
} else {
$section =& $properties;
}
if(is_array($section) && isset($section[$name])) {
return $section[$name];
}
return false;
}
}
24-Jun-2005 06:10
I handy function to allow values with new lines if you are PHP4, is the following:
<?php
function prepareIniNl($string) {
return preg_replace("/(\r\n|\n|\r)/", "\\n", $string);
}
?>
Now, when writing your INI file, parse the value through the function and it will turn for example:
Value line 1
Value line 2
Into literally:
Value line 1\nValue line 2
Which is stored as a single line in the INI file. And when you read the INI file back into PHP, the \n will be parsed and you're value will be back to:
Value line 1
Value line 2
16-Jun-2005 04:55
Oops. There is a small bug in my writeINIfile code example submitted on 13-Jan-2005 11:31. How embarassing. The incorrect statement is:
if (substr($comtext, -1, 1)==$commentchar && substr($comtext, -1, 1)!=$commentchar) {
Note that this is a logic error and the statement will never execute. It should have been written as:
if (substr($comtext, -1, 1)==$commentchar && substr($commenttext, -1, 1)!=$commentchar) {
Notice how in the corrected statement, the string passed to the second substr() function call is $commenttext and not $comtext.
The purpose of this statement was to determine when to strip off the extra comment character that inadvertently gets appended to the comment text block by the previous compounded str_replace code (to prepend comment characters on each line of the comment text block) when the original comment text ends with a new-line sequence.
20-May-2005 05:09
/*
* Search_ini_file refined.
*/
function search_ini_file ( $filename, $search_param, $return_section = false )
{
$search_key = (isset($search_param['key'])?$search_param['key']:false);
$search_value = (isset($search_param['value'])?$search_param['value']:false);
if ( !($search_key !==false || $search_value !==false) ){
return false;
}
$retvalue = false;
$handle = fopen($filename, 'r');
if ( ($search_key !== false) && ($search_value !== false) ){
$key_found = false;
$retvalue['key'] = false;
$retvalue['value'] = false;
while( !feof($handle) ) {
$line = trim(fgets($handle, 4096));
if (preg_match("/^\[$search_key\].*?$/s",$line)){
$key_found = true;
$retvalue['key'] = true;
continue;
}
if ($key_found){
if (preg_match("/^\[.*?$/", trim($line))){
break;
}else{
if ($return_section){
if ($line != '') {
list($k, $v) = split("=", $line);
$retvalue[$search_key][$k] = preg_replace("/;.*$/", "", $v);
} } }
if (preg_match("/^$search_value\s*?=.*$/", $line)){
$retvalue['value'] = true;
break;
} } }
}elseif ($search_key !== false){
$keyfound = false;
while ( !feof($handle) ){
$line = trim(fgets($handle, 4096));
if (preg_match("/^\[$search_key\].*?$/s",$line)){
$retvalue = true;
if (!$return_section){
break;
}else{
$retvalue = Array();
$keyfound = true;
continue;
} }
if ( $keyfound ){
if (preg_match("/^\[.*?$/", trim($line))){
break;
}else{
if ($return_section){
if ($line != ''){
list($k, $v) = split("=", $line);
$retvalue[$search_key][$k] = preg_replace("/;.*$/", "", $v);
} } } } }
}elseif ($search_value !== false){
while ( !feof($handle) ){
$line = trim(fgets($handle, 4096));
if (preg_match("/^$search_value\s*?=.*$/", $line)){
$retvalue = true;
if ($return_section){
$retvalue = array();
if ($line != ''){
list($k, $v) = split("=", $line);
$retvalue[$k] = preg_replace("/;.*$/", "", $v);
} }
break;
} } }
fclose($handle);
return $retvalue;
}
16-Mar-2005 11:07
Note these will be converted to '1' and '0'
[section]
foo = yes
bar = no
Therefore, they need to be put between brackets if you want the value to be 'yes' and 'no'.
08-Mar-2005 05:57
Be careful with the string "none", for example if you want to save a CSS border-style in your config.ini file :
[style]
borderstyle=none
will return:
'style' => array ( 'borderstyle' => '' )
and not
'style' => array ( 'borderstyle' => 'none' )
The solution is to quote the string none :
[style]
borderstyle="none"
31-Jan-2005 02:29
to phpcoder at cyberpimp dot pimpdomain dot com:
thx for the read/write ini functions, they work like a charm ...
except for that one small (easy to find) substr-bug in the readINIfile-function:
counting with substr starts from 0, not 1, so
<?php
if (substr($value, 1, 1) == '"' && ...
?>
should be ...
<?php
if (substr($value, 0, 1) == '"' && ...
?>
18-Jan-2005 01:21
As a Java programmer, I find PHPs lack of handing of multi-line ".properties" files a bit of a pain. I didn't see PEAR::Config handle this, so I hacked together a quick Properties class. This is by no means complete. It works for me, but I'm sure that someone can improve it. I'm also not an expert in PHP, so it may look like a kludge. Anyway, here it is:
<?php
/*
* Properties class. Similar to Java Properties, deals with multi-line
* properties files.
*
* Created on Jan 17, 2005
*
* @author Michael V. Scovetta
* This code is released under the GPL license.
*/
class Properties {
var $properties;
var $keyValueSeparators = "=: \t\r\n";
var $whiteSpaceChars = " \t\r\n";
function Properties($file = null) {
$this->properties = array();
if ($file) {
$this->load($file);
}
}
function set_property( $key, $value ) {
$this->properties[$key] = $value;
}
function get_property( $key ) {
return $this->properties[$key];
}
function load( $file ) {
$lines = file($file);
$lc = 0;
$cont = false;
foreach ($lines as $line) {
if (!$cont) {
$line = ltrim($line, $this->whiteSpaceChars);
$key = $this->findFirstIn($line, $this->keyValueSeparators);
if ($key === false)
continue;
$value = substr($line, $key+2);
$value = trim($value, $this->whiteSpaceChars);
$key = substr($line, 0, $key+1);
$key = trim($key, $this->whiteSpaceChars);
if (substr($value, strlen($value)-1, 1) === '\\') {
$value = substr($value, 0, strlen($value)-1);
$cont = true;
} else {
$this->properties[$key] = $value;
}
} else {
$line = trim($line, $this->whiteSpaceChars);
if (substr($line, strlen($line)-1, 1) === '\\') {
$value .= substr($line, 0, strlen($line)-1);
} else {
$cont = false;
$value .= $line;
$this->properties[$key] = $value;
}
}
}
}
function continueLine($line) {
$slashCount = 0;
$index = strlen($line) - 1;
while (($index >= 0) && (substr($line, $index--, 1) == '\\'))
$slashCount++;
return ($slashCount % 2 == 1);
}
/**
* Finds the first occurance of any character of $choices in $txt
*/
function findFirstIn( $txt, $choices, $start = null)
{
$pos = -1;
$arr = array();
for ($i=0; $i<strlen($choices); $i++) {
array_push($arr, substr($choices, $i, 1));
}
foreach( $arr as $v ) {
$p = strpos( $txt, $v, $start );
if ($p===FALSE)
continue;
if (($p<$pos)||($pos==-1))
$pos = $p;
}
return $pos;
}
function toArray() {
return $this->properties;
}
}
?>
13-Jan-2005 10:31
Here's a much better way of reading and writing INI files. (much fewer character restrictions, automatic comment header, binary safe, etc.)
<?php
/*
Function to replace PHP's parse_ini_file() with much fewer restritions, and
a matching function to write to a .INI file, both of which are binary safe.
Version 1.0
Copyright (C) 2005 Justin Frim <phpcoder@cyberpimp.pimpdomain.com>
Sections can use any character excluding ASCII control characters and ASCII
DEL. (You may even use [ and ] characters as literals!)
Keys can use any character excluding ASCII control characters, ASCII DEL,
ASCII equals sign (=), and not start with the user-defined comment
character.
Values are binary safe (encoded with C-style backslash escape codes) and may
be enclosed by double-quotes (to retain leading & trailing spaces).
User-defined comment character can be any non-white-space ASCII character
excluding ASCII opening bracket ([).
readINIfile() is case-insensitive when reading sections and keys, returning
an array with lower-case keys.
writeINIfile() writes sections and keys with first character capitalization.
Invalid characters are converted to ASCII dash / hyphen (-). Values are
always enclosed by double-quotes.
writeINIfile() also provides a method to automatically prepend a comment
header from ASCII text with line breaks, regardless of whether CRLF, LFCR,
CR, or just LF line break sequences are used! (All line breaks are
translated to CRLF)
*/
function readINIfile ($filename, $commentchar) {
$array1 = file($filename);
$section = '';
foreach ($array1 as $filedata) {
$dataline = trim($filedata);
$firstchar = substr($dataline, 0, 1);
if ($firstchar!=$commentchar && $dataline!='') {
//It's an entry (not a comment and not a blank line)
if ($firstchar == '[' && substr($dataline, -1, 1) == ']') {
//It's a section
$section = strtolower(substr($dataline, 1, -1));
}else{
//It's a key...
$delimiter = strpos($dataline, '=');
if ($delimiter > 0) {
//...with a value
$key = strtolower(trim(substr($dataline, 0, $delimiter)));
$value = trim(substr($dataline, $delimiter + 1));
if (substr($value, 1, 1) == '"' && substr($value, -1, 1) == '"') { $value = substr($value, 1, -1); }
$array2[$section][$key] = stripcslashes($value);
}else{
//...without a value
$array2[$section][strtolower(trim($dataline))]='';
}
}
}else{
//It's a comment or blank line. Ignore.
}
}
return $array2;
}
function writeINIfile ($filename, $array1, $commentchar, $commenttext) {
$handle = fopen($filename, 'wb');
if ($commenttext!='') {
$comtext = $commentchar.
str_replace($commentchar, "\r\n".$commentchar,
str_replace ("\r", $commentchar,
str_replace("\n", $commentchar,
str_replace("\n\r", $commentchar,
str_replace("\r\n", $commentchar, $commenttext)
)
)
)
)
;
if (substr($comtext, -1, 1)==$commentchar && substr($comtext, -1, 1)!=$commentchar) {
$comtext = substr($comtext, 0, -1);
}
fwrite ($handle, $comtext."\r\n");
}
foreach ($array1 as $sections => $items) {
//Write the section
if (isset($section)) { fwrite ($handle, "\r\n"); }
//$section = ucfirst(preg_replace('/[\0-\37]|[\177-\377]/', "-", $sections));
$section = ucfirst(preg_replace('/[\0-\37]|\177/', "-", $sections));
fwrite ($handle, "[".$section."]\r\n");
foreach ($items as $keys => $values) {
//Write the key/value pairs
//$key = ucfirst(preg_replace('/[\0-\37]|=|[\177-\377]/', "-", $keys));
$key = ucfirst(preg_replace('/[\0-\37]|=|\177/', "-", $keys));
if (substr($key, 0, 1)==$commentchar) { $key = '-'.substr($key, 1); }
$value = ucfirst(addcslashes($values,''));
fwrite ($handle, ' '.$key.' = "'.$value."\"\r\n");
}
}
fclose($handle);
}
?>
09-Jan-2005 11:15
<?php
/**
* Function to create lower-case key references to parse_ini_file() result.
* Copyright (C) 2005 Joosep-Georg Järvemaa <georg@linux.ee>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/**
* Creates lower-case references to configuration array loaded from .INI.
*
* Function uses recursion to dive into configuration sub-sections and
* marks already checked sections with additional key '_ns_ini_lcrefs'.
*
* @param arr Configuration array.
*/
function ns_ini_lcrefs(& $arr) {
foreach (array_keys($arr) as $_k) {
if (is_array($arr[$_k]) && !isset($arr[$_k]['_ns_ini_lcrefs']))
ns_ini_lcrefs($arr[$_k]);
if (($_lc_k = strtolower($_k)) != $_k)
$arr[$_lc_k] =& $arr[$_k];
}
$arr['_ns_ini_lcrefs'] = true;
} // function ns_ini_lcrefs()
/* EOF */
?>
20-Oct-2004 04:03
I just noticed that the code I wrote before had an error in it. I have the fix posted here:
That is what happens when you don't error check the code first. Duh.
Here is yet another version of write_ini_file. This version takes data types into account. If the file is numeric or boolean, the value is written in the ini file without quotes. Else it will be written with quotes.
Please note that if a string that CAN be converted into a number WILL be converted into a number because I used the is_numeric function. If you want to make sure that the data type is strictly preserved, use the is_integer and is_double functions in place of the is_numeric function.
<?php
if(!function_exists('write_ini_file')) {
function write_ini_file($path, $assoc_array) {
foreach($assoc_array as $key => $item) {
if(is_array($item)) {
$content .= "\n[{$key}]\n";
foreach ($item as $key2 => $item2) {
if(is_numeric($item2) || is_bool($item2))
$content .= "{$key2} = {$item2}\n";
else
$content .= "{$key2} = \"{$item2}\"\n";
}
} else {
if(is_numeric($item) || is_bool($item))
$content .= "{$key} = {$item}\n";
else
$content .= "{$key} = \"{$item}\"\n";
}
}
if(!$handle = fopen($path, 'w')) {
return false;
}
if(!fwrite($handle, $content)) {
return false;
}
fclose($handle);
return true;
}
}
?>
27-Sep-2004 11:56
Beware that currently you cannot have a closing square bracket (]) in any of the values if you are using sections, no matter how you quote.
See: http://bugs.php.net/bug.php?id=28804
This bug also seems to affect PEAR::Config.
09-Aug-2004 01:49
Here is a better version of write_ini_file() function, which can found below. This version allows you to use sections and still have "global" variables in ini file. The structure of resulting ini file mirrors the structure of the array passed to the function. You can have sections or no, it's up to you.
function write_ini_file($path, $assoc_array) {
foreach ($assoc_array as $key => $item) {
if (is_array($item)) {
$content .= "\n[$key]\n";
foreach ($item as $key2 => $item2) {
$content .= "$key2 = \"$item2\"\n";
}
} else {
$content .= "$key = \"$item\"\n";
}
}
if (!$handle = fopen($path, 'w')) {
return false;
}
if (!fwrite($handle, $content)) {
return false;
}
fclose($handle);
return true;
}
15-Jul-2004 05:20
parse_ini_file seems to have changed it's signature between PHP 4.3.x and PHP 5.0.0 (can't find any relevant changelog / cvs entries referring to this).
In PHP 4.3.x and below return value was a boolean FALSE if the ini file could not be found. With PHP 5.0.0 the return value is an empty array if the file is not found.
22-Jun-2004 08:47
Even better than putting the <?php at the head of the file is to do something like this:
--config.ini.php--
; <?php die( 'Please do not access this page directly.' ); ?>
; This is the settings page, do not modify the above line.
setting = value
...
19-Jun-2004 05:02
based entirely on LIU student's code (thanks), here's a write_ini_file function you can use whether or not the array you are writing is sorted into sections.
It is designed so that $arr1 equals $arr2 in both the cases below, using sections:
<?php
$arr1 = parse_ini_file($filename, true);
write_ini_file(parse_ini_file($filename, true), $filename, true);
$arr2 = parse_ini_file($filename, true);
?>
and without sections:
<?php
$arr1 = parse_ini_file($filename);
write_ini_file(parse_ini_file($filename), $filename);
$arr2 = parse_ini_file($filename);
?>
i.e. files written using write_ini_file will be semantically identical (as far as parse_ini_file can see) to the originals.
Here is the code:
<?php
if (!function_exists('write_ini_file')) {
function write_ini_file($assoc_arr, $path, $has_sections=FALSE) {
$content = "";
if ($has_sections) {
foreach ($assoc_arr as $key=>$elem) {
$content .= "[".$key."]\n";
foreach ($elem as $key2=>$elem2) {
$content .= $key2." = \"".$elem2."\"\n";
}
}
}
else {
foreach ($assoc_arr as $key=>$elem) {
$content .= $key." = \"".$elem."\"\n";
}
}
if (!$handle = fopen($path, 'w')) {
return false;
}
if (!fwrite($handle, $content)) {
return false;
}
fclose($handle);
return true;
}
}
?>
Incidentally I wrapped it inside an if (!function_exists(...)) block so you can just put this wherever it's needed in your code without having to worry about it being declared several times.
Warning: if you read an ini file then write it using <?php write_ini_file(parse_ini_file($fname), $fname); ?>, any sections will obviously be lost.
Note also: unquoted values will be quoted and varname=true will become varname = "1" when writing an ini file back to itself using <?php write_ini_file(parse_ini_file($fname, true), $fname, true); ?> or <?php write_ini_file(parse_ini_file($fname), $fname); ?>. This should make no difference, but it might cause the types of the variables to change in case you plan on using === or !== comparisions.
15-Jun-2004 05:00
A better version of parse_ini_str that takes into account values that are named the same.
<?php
function parse_ini_str($Str,$ProcessSections = TRUE) {
$Section = NULL;
$Data = array();
if ($Temp = strtok($Str,"\r\n")) {
do {
switch ($Temp{0}) {
case ';':
case '#':
break;
case '[':
if (!$ProcessSections) {
break;
}
$Pos = strpos($Temp,'[');
$Section = substr($Temp,$Pos+1,strpos($Temp,']',$Pos)-1);
$Data[$Section] = array();
break;
default:
$Pos = strpos($Temp,'=');
if ($Pos === FALSE) {
break;
}
$Value = array();
$Value["NAME"] = trim(substr($Temp,0,$Pos));
$Value["VALUE"] = trim(substr($Temp,$Pos+1),' "');
if ($ProcessSections) {
$Data[$Section][] = $Value;
}
else {
$Data[] = $Value;
}
break;
}
} while ($Temp = strtok("\r\n"));
}
return $Data;
}
?>
Example:
[Files]
File=File1
File=File2
would return:
array (
'Files' => array (
0 => array (
'NAME' => 'File',
'VALUE' => File1',
),
1 => array (
'NAME' => 'File',
'VALUE' => 'File2',
),
),
)
19-Mar-2004 03:08
[Editor's note: The fwrite()-line should look like: "if (fwrite($handle, $content) === false) {" to avoid returning false when the array is empty --victor@php.net]
function writeIni($assoc_arr, $path){
$content = "";
foreach ( $assoc_arr as $key=>$elem ){
$content .= "[".$key."]\n";
foreach ( $elem as $key2=>$elem2){
$content .= $key2." = \"".$elem2."\"\n";
}
}
if (!$handle = fopen($path, 'w')) {
return false;
}
if (!fwrite($handle, $content)) {
return false;
}
fclose($handle);
return true;
}
09-Nov-2003 03:37
I found that this function will not work on remote files.
I tried
$someArray = parse_ini_file("http://www.someweb.com/setting.ini");
and it reports
Cannot Open 'http://www.someweb.com/setting.ini' for reading ...
11-Oct-2003 03:15
Instead of using parse_ini_file() function I would recommend to use PEAR's Config package which is MUCH more flexible (assuming that you don't mind using PEAR and OOP). Have a closer look at http://pear.php.net/package/Config
20-Jun-2003 05:47
I found another pitfall to watch out for. The key (to the left of the equal sign) can't be the same as one of the predefined values, like yes, no, on, off, etc. I was working on a script that read in an ini file that matched the country codes of top level domains to the full name of the country. I kept getting a parse error everytime it got to the entry for Norway ("no"). I fixed the problem by sticking a dot in front of each of the country codes.
If your configuration file holds any sensitive information (such as database login details), remember NOT to place it within your document root folder! A common mistake is to replace config.inc.php files, which are formatted in PHP:
<?php
$database['host'] = 'localhost';
// etc...
?>
With config.ini files which are written in plain text:
[database]
host = localhost
The file config.ini can be read by anyone who knows where it's located, if it's under your document root folder. Remember to place it above!
07-Jan-2003 07:24
Just a quick note for all those running into trouble escaping double quotes:
I got around this by "base64_encode()"-ing my content on the way in to the ini file, and "base64_decode()"-ing on the way out.
Because base64 uses the "=" sign, you will have to encapsulate the entire value in double quotes so the line looks like this:
varname = "TmlhZ2FyYSBGYWxscywgT04="
When base64'd, your strings will retain all \n, \t...etc... URL's retain everything perfectly :-)
I hope some of you find this useful!
Cheers, Kieran
29-Nov-2002 06:37
Besides the features mentioned above (eg. core constants, booleans), you can also access user-defined constants in ini files! This is handy if you want to create a bit-field, for example:
+++ PHP +++
// Define pizza toppings
define('PIZZA_HAM', 1);
define('PIZZA_PINEAPPLE', 2);
define('PIZZA_ONION', 4);
define('PIZZA_MOZARELLA', 8);
define('PIZZA_GARLIC', 16);
// Read predefined pizzas
$pizzas = parse_ini_file('pizzas.ini');
if ($pizzas[$user_pizza] & PIZZA_ONION) {
// Add onions to the pizza
}
+++ INI +++
[pizzas]
; Define pizzas
hawaii = PIZZA_HAM | PIZZA_PINEAPPLE
stinky = PIZZA_ONION | PIZZA_GARLIC
26-Mar-2002 07:27
And for the extra-paranoid like myself, add a rule into your httpd.conf file so that *.ini (or *.inc) in my case can't be sent to a browser:
<Files *.inc>
Order deny,allow
Deny from all
</Files>
15-Jan-2002 04:41
It should be noted that in all of our attempts you cannot escape a double quote in the value when read with the parse_ini_file() function.
;============================
; Example Configuration File
;============================
[category]
title = "Best Scripting Language"
desc = "See <a href=\"http://www.php.net/\">PHP</a>!"
If this file is read by parse_ini_file() the link value will not be set because of the escaped double quotes.