HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

Gdn_Form - Associative Arrays?

PHP makes it easy to access associative arrays send via HTTP POST.

Example:
Ask the user to input some poll choices with each choice having a unique ID and a text:

<?php 
if (is_array($_POST['Choice'])) {
    foreach ($_POST['Choice'] as $ChoiceID => $Choice) {
        echo "<p>Got {$ChoiceID} => {$Choice['text']}</p>";
    }
}
?>
<form method="POST">
    <input type="text" name="Choice[100][text]">
    <input type="text" name="Choice[110][text]">
    <input type="text" name="Choice[120][text]">
    <input type="submit" name="send">
</form>

Now, I want to accomplish the same via Gdn_Form. Ideally, I also want to be able to prepopulate these form fields with some values.

Is that possible? Or would I have to modify Gdn_Form for that?

Tagged:

Comments

  • mtschirsmtschirs ✭✭✭

    So far, I found the following:

    Gdn_Form->TextBox("Choice[]") produces <input ... name="Choice[]">

    Gdn_Form->TextBox("Choice[Text]") produces <input ... name="Choices%5B%5D%5BText%5D">

    ...horrible :eh:

  • I wouldn't say you have to modify the form class. If you look at checkboxes in that class you find the functions Checkbox, CheckboxList, CheckboxGrid, etc. Each function has a different use case. You seem to look for something like a "TexboxList" which doesn't exist. So you would either have to create that for yourself or use something like the code below, which is "just" a workaround.

    if (Gdn::request()->isAuthenticatedPostBack()) {
        $post = Gdn::request()->post();
        $choice = array();
        foreach ($post as $key => $value) {
            if (substr($key, 0, 7) === 'choice_') {
                $choice[substr($key, 7)] = $value;
            }
        }
        decho($choice);
    } else {
        $choice[1] = 'one';
        $choice[2] = 'two';
        $choice[3] = 'three';
        $form = new Gdn_Form();
        echo $form->open();
        echo $form->errors();
        echo '<p>';
        foreach($choice as $key => $value) {
            echo $form->label('Your opinion', 'choice_'.$key);
            echo $form->textBox('choice_'.$key, array('value' => $value));
        }
        echo '</p>';
        echo $form->button(t('Save'));
        echo $form->close();
    }
    

    But I really will not stop you from adding a TextBoxList to the form class - that would be a nice extension!

  • mtschirsmtschirs ✭✭✭
    edited August 2015

    Thank you for your code, I might implement your work-around!

    The whole concept of TextBox / TextBoxList etc. is a bridge abstraction too far. PHP natively handles forms whose values correspond to associative arrays of any depth. Gdn_Form should be an enhancement, not a limitation. Therefore, the only way to clean up this mess is to make Gdn_Form accept associative arrays in its $_DataArray instead of just flat name -> value pairs.

    The current workarounds within

    Gdn_Form::escapeString($String) {
      $Array = false;
      if (substr($String, -2) == '[]') {
      ...
    

    don't help either...

  • Gdn_Forms main purpose is building forms and validating the inputs (through Gdn_Validation) based on a table schema, which is always 1-dimensional.

  • mtschirsmtschirs ✭✭✭

    @Bleistivt: You will have to search hard for an application that relies on the form <-> table schema correspondence. Nearly every use case requires you to ditch the given Gdn_Form->save() or rather Gdn_Model->save() and overwrite it with custom code.

    But perhaps I am stuck thinking inside the box. How would you handle displaying a form "Poll" that allows a variable number of poll choices to be added? Surely, ditching Gdn_Form is not the answer...

  • The DiscussionPolls plugin does this using a non-associative array:
    https://github.com/hgtonight/Plugin-DiscussionPolls/blob/master/views/questions.php#L39
    https://github.com/hgtonight/Plugin-DiscussionPolls/blob/master/class.discussionpollsmodel.php#L205

    I agree that Gdn_Form::escapeString looks like it should be removed for something less limiting.

  • mtschirsmtschirs ✭✭✭

    @Bleistivt: The DiscussionPolls plugin makes use of the special workaround for '[]' provided by Gdn_Form. This workaround does no allow to prepopulate the form fields with values via Gdn_Form::setData(). That is why DiscussionPolls then works around this limitation of that workaround by manually setting the form field's value. If I were the dev of DiscussionPolls, I would rather write native HTML instead of abusing the failed abstraction of Gdn_Form any further...

  • BleistivtBleistivt Moderator
    edited August 2015

    I would rather write native HTML

    To my understanding, Garden is not a general purpose framework. Features get added when they are needed. I don't see a problem in using the standard PHP toolset when you can't do something through the framework.

    The convention in garden for deeply nested structures is a flattened representation using the -dot- syntax. See the checkboxes on the roles and permissions screen.

    Like I said before, I agree with you that arbitrary limitations should be removed.
    I'm just trying to explain why nested forms are a niche use case here.

  • mtschirsmtschirs ✭✭✭

    @Bleistift: I like your explanations :). Since I am relatively new to Garden & Vanilla, my understanding of how things came to be as they are is limited.

    I will have a look at the dot syntax found in e.g. the ConfigurationModel.

  • mtschirsmtschirs ✭✭✭
    edited August 2015

    So the current Gdn_Form fails to handle models with columns like 'abc[]', but it has special code allowing it to handle models with columns like 'abc[x]'...

    public function escapeString($String) {
        $Array = false;
        if (substr($String, -2) == '[]') {
            $String = substr($String, 0, -2);
            $Array = true;
        }
        $Return = urlencode(str_replace(' ', '_', $String));
        if ($Array === true) {
            $Return .= '[]';
        }
        return str_replace('.', '-dot-', $Return);
    }
    
    /**
     * Decodes the encoded string from a php-form safe-encoded format to the
     * format it was in when presented to the form.
     */
    protected function _unescapeString($EscapedString) {
        $Return = str_replace('-dot-', '.', $EscapedString);
        return urldecode($Return);
    }
    

    Also, the _unescapeString function clearly fails s to perform its duty...

    Is there a reason for that or can it be fixed in 2.2?

  • mtschirsmtschirs ✭✭✭
    edited August 2015

    I tested Gdn_Form with a table having the following columns:

    • "x_y", "xy[]", "x[y]", "x.y", "x-dot-y" and "x y".

    With 'escaping', Gdn_Form fails to handle:

    • "xy[]", "x-dot-y", "x y"

    Without 'escaping', Gdn_Form fails to handle:

    • "xy[]", "x[y]", "x.y", "x y"

    I therefore propose removing form name escaping (Gdn_Form::escapeString() and Gdn_Form::_unescapeString()) and replace escaping with a simple htmlspecialchars.

    Further arguments for removing escaping:

    • Without escaping, it is pretty clear for the programmer which column names can and cannot be put into a form.
    • There is no way to 'fix' escaping: MySQL and PHP/HTML have very different rules as to what column names and what HTML input names are valid.
  • edited August 2015

    If it may help you, I already covered this feature in my Aelia Foundation Classes plugin, which is the framework on which almost all my plugins are based. I needed a hierarchy of fields for my Awards plugin, so I extended the form class to handle them (see file class.aeliaform.php).

    Note
    The plugin and the class were written years ago for Vanilla 2.0, but they should work with 2.1 as well. You might eventually have to tweak the class a bit.

  • mtschirsmtschirs ✭✭✭

    @businessdad I had a look at your Foundation Classes; seems you put a 'correction & enhancement layer' above the Garden framework. While that might have been exactly what you needed to support your plugins on existing and older Vanilla installs, I think addressing issues in the core directly is the better solution (if one has the luxury of just having to support one site running on the most recent master branch :p)

  • That is correct. I needed several functions available immediately for my projects, and I didn't have time to wait for the pull request to be approved. That's why the AFC was born, and it has the advantage of offering full backward compatibility, which Vanilla updates don't always guarantee. :)

Sign In or Register to comment.