Zend Framework

ReCAPTCHA does work in forms using Array Notation

Details

  • Type: Bug Bug
  • Status: Resolved Resolved
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 1.11.2
  • Fix Version/s: 1.11.9
  • Component/s: Zend_Form
  • Labels:
    None

Description

If you create a form containing a ReCaptcha captcha element it will not validate if that form is using array notation.

This form will validate correctly

class Contact_Form_Test extends Zend_Form
{
    private static $_privKey = '';
    private static $_pubKey = '';


    public function init()
    {
        $this->setMethod('post');

        $element = new Zend_Form_Element_Captcha('recaptcha', array(
            'label' => "Please verify you're a human",
            'captcha' => 'ReCaptcha',
            'captchaOptions' => array(
                'privKey' => self::$_privKey,
                'pubKey' => self::$_pubKey,
            ),
        ));
        $this->addElement($element);
        $this->addElement('Submit', 'submit');
    }

}

This form will not validate and dies with a 'missingValue' error. Note the addition of $this->setElementsBelongTo('contact');

class Contact_Form_Test extends Zend_Form
{
    private static $_privKey = '';
    private static $_pubKey = '';


    public function init()
    {
        $this->setElementsBelongTo('contact');
        $this->setMethod('post');

        $element = new Zend_Form_Element_Captcha('recaptcha', array(
            'label' => "Please verify you're a human",
            'captcha' => 'ReCaptcha',
            'captchaOptions' => array(
                'privKey' => self::$_privKey,
                'pubKey' => self::$_pubKey,
            ),
        ));
        $this->addElement($element);
        $this->addElement('Submit', 'submit');
    }

}

Activity

Hide
Kai Uwe added a comment -

Please use always the code tags!

Show
Kai Uwe added a comment - Please use always the code tags!
Hide
Guy Halford-Thompson added a comment - - edited

Edit: Added code tags

Show
Guy Halford-Thompson added a comment - - edited Edit: Added code tags
Hide
Matthew Weier O'Phinney added a comment - - edited

On analysis, this appears to be due to a change in the ReCaptcha JavaScript API. Previously, it would use the element name in order to find and populate the challenge and response values, but now appears to be hard-coded to use only the keys "recaptcha_challenge_field" and "recaptcha_response_field" – which makes it (a) impossible to namespace these in a form, and (b) have more than one ReCaptcha on a single page (since whichever ReCaptcha operates last populates those keys in the POST).

What's more frustrating is that if you disable JS, it's actually possible to use namespaced values via the <noscript> section, as it uses the existing elements you create via Zend_Form.

As such, I'm unsure how to resolve this issue. We could potentially generate some JS that wraps the ReCaptcha API, but this could prove to be a moving target and maintenance nightmare. Another option is to support namespaces via <noscript> only, and document a methodology for pre-seeding the values yourself:

$formValues         = $this->_request->getPost('contact', array());
if (!isset($formValues['recaptcha_challenge_field'])) {
    $formValues['recaptcha_challenge_field'] = $this->_request->getPost('recaptcha_challenge_field', '');
}
if (!isset($formValues['recaptcha_response_field'])) {
    $formValues['recaptcha_response_field'] = $this->_request->getPost('recaptcha_response_field', '');
}
if (!$form->isValid($formValues)) { ... }

Finally, we could raise an exception if we determine that the element is part of a form array.

Guy, Leigh, which approach would you like to see?

Show
Matthew Weier O'Phinney added a comment - - edited On analysis, this appears to be due to a change in the ReCaptcha JavaScript API. Previously, it would use the element name in order to find and populate the challenge and response values, but now appears to be hard-coded to use only the keys "recaptcha_challenge_field" and "recaptcha_response_field" – which makes it (a) impossible to namespace these in a form, and (b) have more than one ReCaptcha on a single page (since whichever ReCaptcha operates last populates those keys in the POST). What's more frustrating is that if you disable JS, it's actually possible to use namespaced values via the <noscript> section, as it uses the existing elements you create via Zend_Form. As such, I'm unsure how to resolve this issue. We could potentially generate some JS that wraps the ReCaptcha API, but this could prove to be a moving target and maintenance nightmare. Another option is to support namespaces via <noscript> only, and document a methodology for pre-seeding the values yourself:
$formValues         = $this->_request->getPost('contact', array());
if (!isset($formValues['recaptcha_challenge_field'])) {
    $formValues['recaptcha_challenge_field'] = $this->_request->getPost('recaptcha_challenge_field', '');
}
if (!isset($formValues['recaptcha_response_field'])) {
    $formValues['recaptcha_response_field'] = $this->_request->getPost('recaptcha_response_field', '');
}
if (!$form->isValid($formValues)) { ... }
Finally, we could raise an exception if we determine that the element is part of a form array. Guy, Leigh, which approach would you like to see?
Hide
Matthew Weier O'Phinney added a comment -

I've discovered other folks have had similar issues: http://ish.io/embedded/formish/recaptcha.html

I think we may be able to munge this during onSubmit by doing a query on the given form for the appropriate elements, and then creating some new hidden ones that are namespaced but with the same values. I'll try and get something in for this in the next day or two. (We hit this on the ZF site, btw. )

Show
Matthew Weier O'Phinney added a comment - I've discovered other folks have had similar issues: http://ish.io/embedded/formish/recaptcha.html I think we may be able to munge this during onSubmit by doing a query on the given form for the appropriate elements, and then creating some new hidden ones that are namespaced but with the same values. I'll try and get something in for this in the next day or two. (We hit this on the ZF site, btw. )
Hide
Matthew Weier O'Phinney added a comment -

The following dojo code worked for me to make the JS version of ReCaptcha work:

dojo.create("input", {type: "hidden", id: "contact-captcha-challenge", name: "contact[recaptcha_challenge_field]"}, dojo.byId("captcha-element"));
dojo.create("input", {type: "hidden", id: "contact-captcha-response", name: "contact[recaptcha_response_field]"}, dojo.byId("captcha-element"));
dojo.connect(dojo.byId("contact"), "onsubmit", function(e) { dojo.attr("contact-captcha-response", {value: dojo.attr("recaptcha_response_field", "value")}); dojo.attr("contact-captcha-challenge", {value: dojo.attr("recaptcha_challenge_field", "value")});});

Basically, it adds two hidden fields named appropriately into the DOM where the captcha element should live, and then binds to the form's onSubmit event in order to copy the recaptcha information into those hidden fields.

This can probably be written as a custom decorator using generic JS (using document.getElementById() and element.value).

Show
Matthew Weier O'Phinney added a comment - The following dojo code worked for me to make the JS version of ReCaptcha work:
dojo.create("input", {type: "hidden", id: "contact-captcha-challenge", name: "contact[recaptcha_challenge_field]"}, dojo.byId("captcha-element"));
dojo.create("input", {type: "hidden", id: "contact-captcha-response", name: "contact[recaptcha_response_field]"}, dojo.byId("captcha-element"));
dojo.connect(dojo.byId("contact"), "onsubmit", function(e) { dojo.attr("contact-captcha-response", {value: dojo.attr("recaptcha_response_field", "value")}); dojo.attr("contact-captcha-challenge", {value: dojo.attr("recaptcha_challenge_field", "value")});});
Basically, it adds two hidden fields named appropriately into the DOM where the captcha element should live, and then binds to the form's onSubmit event in order to copy the recaptcha information into those hidden fields. This can probably be written as a custom decorator using generic JS (using document.getElementById() and element.value).
Hide
Matthew Weier O'Phinney added a comment -

Fixed in current trunk and 1.11 release branch via a custom decorator for the ReCaptcha captcha element.

Show
Matthew Weier O'Phinney added a comment - Fixed in current trunk and 1.11 release branch via a custom decorator for the ReCaptcha captcha element.
Hide
Thanos Kyritsis added a comment -

Subversion revision #24223 for library/Zend/Form/Element/Captcha.php may have fixed ReCaptcha, but broke at least two other Captchas (I tried Figlet and Image).

The example at http://framework.zend.com/manual/en/learning.quickstart.create-form.html no longer works, because the developer now (since 1.11.9) needs to explicitly add the 'captcha' decorator to the figlet (or image captcha) form element.

Should I open a new issue for this ?

Show
Thanos Kyritsis added a comment - Subversion revision #24223 for library/Zend/Form/Element/Captcha.php may have fixed ReCaptcha, but broke at least two other Captchas (I tried Figlet and Image). The example at http://framework.zend.com/manual/en/learning.quickstart.create-form.html no longer works, because the developer now (since 1.11.9) needs to explicitly add the 'captcha' decorator to the figlet (or image captcha) form element. Should I open a new issue for this ?
Hide
Matthew Weier O'Phinney added a comment -

Please do.

I recall seeing the logic for adding the captcha decorator, as I had to add some conditional logic not to load it if one was already loaded (as, for example, with the Word or ReCaptcha decorators); it may be that this conditional logic may have introduced a new break. I had to add tests to ensure that the ReCaptcha decorator was being invoked; my guess is that we're missing tests for the others.

Show
Matthew Weier O'Phinney added a comment - Please do. I recall seeing the logic for adding the captcha decorator, as I had to add some conditional logic not to load it if one was already loaded (as, for example, with the Word or ReCaptcha decorators); it may be that this conditional logic may have introduced a new break. I had to add tests to ensure that the ReCaptcha decorator was being invoked; my guess is that we're missing tests for the others.

People

Vote (0)
Watch (3)

Dates

  • Created:
    Updated:
    Resolved: