ZF-10991: ReCAPTCHA does work in forms using Array Notation

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');
    }

}

Comments

Please use always the code tags!

Edit: Added code tags

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

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

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?

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. :) )

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).

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

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/… 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 ?

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.