1

First, forgive me if this is an overly pedantic question. I have searched trying to find answers but perhaps I'm using the wrong search terms.

Trying to use an INI file for a simple PHP application, where there is an admin page to allow application options to be easily changed. I'm able to read in the ini file with no issue, problem I'm coming across is on the write - if any boolean values are false, they won't get put into the _POST and as such don't get written back into the ini file. Here's my sample:

settings.ini file:

[Site options]
bRequireLegal['Require NDA before badge print'] = true ;
bCollectVehicleInfo['Collect vehicle information'] = false; 
bShowAdditionalMessageBeforeBadgePrint['Show badge printing message'] = true; 

[Company info]
companyname['Company Name'] = 'The Company, Inc.' ;

Code to read in the ini file (settings.php):

$filepath = 'settings.ini'; //location of settings file

$settings = parse_ini_file($filepath, true, $scanner_mode = INI_SCANNER_TYPED);

//pull everything in ini file in as variable
foreach($settings as $section=>$options){
  foreach($options as $option=>$values){
    foreach($values as $descriptor=>$value){
      if(is_bool($value) === true) {
        ${htmlspecialchars($option)} = +$value;
      }
      else ${htmlspecialchars($option)} = $value;
    }
  }
}

And finally, the options setting page:

<?php

 include 'settings.php';

//after the form submit
if($_POST){
    $data = $_POST;
    update_ini_file($data, $filepath);
}

    function update_ini_file($data, $filepath) { 
        $content = ""; 
        
        //parse the ini file to get the sections
        foreach($data as $section=>$options){
            //append the section 
            $content .= "[".$section."]\r\n"; 
            //append the values
      foreach($options as $option=>$values){
               $content .= $option;
        foreach($values as $descriptor=>$value){                
                $content .= "['".$descriptor."'] = '".$value."';\r\n"; 
            }
        }
        $content .= "\r\n";
     }
        
       if (!$handle = fopen($filepath, 'w')) { 
           return false; 
       }
       $success = fwrite($handle, $content);
       fclose($handle); 
       return $success; 

    }
?>
<html>
<body>
<?php 

?>
<div class="container-fluid">
<form action="" method="post">
    <?php 
        
    foreach($settings as $section=>$options){
        echo "<h3>$section</h3>";
        //keep the section as hidden text so we can update once the form submitted
        echo "<input type='hidden' value='$section' name='$section' />";
        //print all other values as input fields, so can edit. 
      foreach($options as $option=>$values){
        foreach($values as $descriptor=>$value){
            if(is_bool($value) === true) {
               echo "<p>".$descriptor.": <input type='checkbox' name='{$section}[$option][$descriptor]' ".(($value===true)?" checked":"")." /></p>";
            } else
              echo "<p>".$descriptor.": <input type='text' name='{$section}[$option][$descriptor]' value='$value' />"."</p>";
        }
    }
            echo "<br>";
 }
    ?>

<input type="submit" value="Update INI" /> 
</form>
</div>
</body>
</html>

Any help would be greatly appreciated!

mgarvey
  • 11
  • 1
  • generally ini files are used to initialise your code, not to save config, i would use a fixed ini file to set your defaults then save changes and overrides in a json file. actually unless you have a desperate need for the ini file i would just provide do the config in json. the main advantage of the ini structure is that is a little more human readable – MikeT Sep 06 '22 at 16:32
  • `${htmlspecialchars($option)} = $value` is a conceptual mistake. Not everything is HTML on this world. Even more, HTML is just a small part of what one can do using PHP. Do not encode anything (as HTML or as something else) until the very moment when you use that value to produce content (HTML or something else). – axiac Sep 06 '22 at 17:19
  • @MikeT understood, probably a better idea to use JSON, was looking to keep it human readable. Not clear what converting to JSON does to solve this problem though, maybe I'm missing something obvious? – mgarvey Sep 06 '22 at 17:24
  • @axiac Agreed, that was a bit of kludge I put in to get it to write out the variable name as a text. Open to options to better handle that. – mgarvey Sep 06 '22 at 17:25
  • 1
    the problem you are having is that because INI format is designed to be read only php comes with a deserialiser but nor a serialiser, you've had to write your own serialiser, json is a recognised exchange format though so includes both a serialiser and deserialiser this means you just have to put the `json_encode($yourObject)` into a file and `$yourObject = json_decode(file_get_contents)` to get it back – MikeT Sep 07 '22 at 13:07

1 Answers1

0

In your update_ini_file() function, replace this:

$content .= "['".$descriptor."'] = '".$value."';\r\n";

with

$content .= "['".$descriptor."'] = '".($value ? 'true' : 'false')."';\r\n";

This will cause it to write the strings 'true' and 'false' instead of literal Boolean values. See How to Convert Boolean to String

Edit to add:

I think you're generating your checkboxes incorrectly:

<input type='checkbox' name='{$section}[$option][$descriptor]' ".(($value===true)?" checked":"")." />

This will cause the 'true' boxes to be checked, but they will still lack a value (and thus will not be transmitted to the server when the form is submitted). You should change that code to:

<input type='checkbox' name='{$section}[$option][$descriptor]' value='1'".(($value===true)?" checked":"")." />

In other words, all checkboxes should have a value of '1', but the way the browser works, only those which are checked will be submitted.

Edit to add:

Checkboxes that are not checked will not get submitted. That explains why you are not seeing any output for values that are 'false': they simply don't get submitted. When you loop through $data (which comes from $_POST), it is missing those unchecked (and thus 'false') checkboxes.

Using a solution found here: POST unchecked HTML checkboxes

Change this:

echo "<p>".$descriptor.": <input type='checkbox' name='{$section}[$option][$descriptor]' ".(($value===true)?" checked":"")." /></p>";

to this, which includes a hidden field that has the value '0', which will get submitted even if the corresponding checkbox is unchecked:

echo "<p>".$descriptor.": <input type='hidden' name='{$section}[$option][$descriptor]' value='0'><input type='checkbox' name='{$section}[$option][$descriptor]' ".(($value===true)?" checked":"")." /></p>";

However, this has its own set of potential problems, specifically when a checkbox is checked, you will send two identically named fields: one with a '0' value and the other with a '1' value. This is explained in the link above, and is left as an exercise for you to solve (or ask for further details on) if my answer doesn't work.

kmoser
  • 8,780
  • 3
  • 24
  • 40
  • Thank you for the quick response. Unfortunately that doesn't appear to work, the result is that it just writes every variable out as "true", and still doesn't write anything out for the values that are false. – mgarvey Sep 06 '22 at 15:18
  • Tried that, same problem - values that are "true" are written out correctly, "false" values do not get stored at all, any string values just get written out as "true". I feel like there's something dumb I'm missing... – mgarvey Sep 06 '22 at 16:00
  • 1
    @mgarvey Checkboxes that are not checked will not get submitted. That explains why you are not seeing any output for values that are 'false': they simply don't get submitted. When you loop through `$data` (which comes from `$_POST`), it is missing those unchecked (and thus 'false') checkboxes. To fix this, you can loop through a canonical list of settings (which you can store as a hard-coded array) and compare with `$data` to determine whether a value is checked (because it is in `$data` and should be 'true') or unchecked (because it is not in `$data`, and should be 'false'). – kmoser Sep 06 '22 at 16:15
  • @mgarvey Also see https://stackoverflow.com/questions/1809494/post-unchecked-html-checkboxes – kmoser Sep 06 '22 at 16:17