The following solution of adding ReCaptcha to a JQuery Dialog (Lightbox) makes use of the sfFormExtraPlugin, not the sfReCaptchaPlugin. The reason is because the following solution was developed in Symfony 1.2+, and we had access to the Symfony form architecture.

Out of the box, the sfFormExtraPlugin sfWidgetFormReCaptcha::render() will not work in lightboxes because it results in a document.write(). Consider the following:

// sfWidgetFormReCaptcha::render() produces the following javascript
<script type="text/javascript" src=""></script>

// Which produces the following javascript
var RecaptchaState = {
    site : '6Let7r0SAAAAAJFp77tW4gRze7r5y89Cql_msPcm',
    challenge : '03AHJ_VusnO7jeI3VF-cHgOIv9RY9RIpYl2QWea_LULIdL8xrM9PZdcZZ7I9gFf4gSbr1fxGCSQjQZPJQ1sa6p1oEI9U_nkU8f2SjczxjH6nzmy43Q-m_8rnxWhhRUIDa7iTPEwo4-dwi-FipyMHsSAz-nE5yfFQfZog',
    is_incorrect : false,
    programming_error : '',
    error_message : '',
    server : '',
    timeout : 18000

document.write('<scr'+'ipt type="text/javascript" s'+'rc="' + RecaptchaState.server + 'js/recaptcha.js"></scr'+'ipt>');</pre>

Note the resulting document.write() code produced by recaptcha.

Recaptha offers multiple ways to access their api, so instead of the solution implemented by the devs of the sfWidgetFormReCaptcha plugin, I opted to introduce custom code which will produce the same result, except it will be lightbox friendly.  As with any Symfony plugin, I made sure not to overwrite core logic so it doesn’t increase technical debt with respect to future plugin upgrades etc. (unless the core logic changes such that the custom code must follow suit).

First, we need to override the default plugin code so we can add our custom code, and thanks to Symfony autoloading, the following changes should work out of the box. Also note, the myWidgetFormCaptcha class name follows symfony naming conventions to indicate Symfony core code vs custom code.

We need to create a new file which will extend sfWidgetFormReCaptcha;

mkdir -p /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget
touch /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget/myWidgetFormReCaptcha.class.php #lol @ touch myWidget

You can use whichever path you wish, just make sure it’s in the /path/to/symfony_code/lib dir so it will be included when Symfony autoloads files. I chose the above path to maximize flexibility if I ever need to override any other sfFormExtraPlugin widgets.

Now that we have our override class file in place, lets add the code:

// /path/to/symfony_code/lib/form/myExtraFormPlugin/lib/widget/myWidgetFormReCaptcha.class.php

 * Extending plugin functionality, see parent class for full docs
class myWidgetFormReCaptcha extends sfWidgetFormReCaptcha
 * @see sfWidgetForm
public function render($name, $value = null, $attributes = array(), $errors = array())
    $server    = $this->getServerUrl();
    $key    = $this->getOption('public_key');

    if ((array_key_exists('context', $attributes)) &&
        ($attributes['context'] == 'lightbox')) {

        // Arbitrary flag, unset it

        return javascript_tag("
            Recaptcha.create('".$key."', 'captchaWrap', {custom_translations: {instructions_visual:'" . __('recaptcha_instructions_visual') . "',instructions_audio:'" . __('recaptcha_instructions_audio') . "',play_again:'" . __('recaptcha_play_again') . "', cant_hear_this:'" . __('recaptcha_cant_hear_this') . "', visual_challenge:'" . __('recaptcha_visual_challenge') . "', audio_challenge:'" . __('recaptcha_audio_challenge') . "', refresh_btn:'" . __('recaptcha_refresh_btn') . "', help_btn:'" . __('recaptcha_help_btn') . "', incorrect_try_again:'" . __('recaptcha_incorrect_try_again') . "'}, theme:'". sfConfig::get('recaptchaTheme', sfConfig::get('clientid'), 'text', 'clean') ."', lang:'en'});
        ") . '<div id="captchaWrap"></div>';

    parent::render($name, $value, $attributes, $errors);

Ya I know, a lot of javascript, but nothing overly complicated just a bunch of flags dictating to recaptcha how the element should be rendered. I added an arbitary flag to the attributes array, so I could still call the same render() methods.  This allows the calling code to tell the widget how it should be rendered. If the context flag is not set to ‘lightbox’ then it simply calls on parent::render() which will default to normal behavior for the class.

Now we just need to add the following code to the lightbox template (the template that your form data is located in):

<?php echo $form['captcha']->renderRow(array('context' => 'lightbox')); ?>

And of course we need to bring in the ReCaptcha js library (view.yml):

javascripts: []

Now, clear symfony cache and click the link to activate your lightbox, it should render your form AND the recaptcha form element. Yay!

In a future post I will write-up how to check lightbox ajax responses using JSON.