reCAPTCHA Component & Helper for CakePHP
an easy-to-use component and helper set to use reCAPTCHA on your website.
This is an easy to use component and helper set for you to get reCAPTCHA(including mailhide) running on your website now. If you don't know what reCAPTCHA is please head to http://recaptcha.net/ now to find out! Please note this code is largely derived off of the library provided for reCAPTCHA - it's just repurposed for ease of use in CakePHP.
Create the following files:
code for app/controllers/components/recaptcha.php
code for app/views/helpers/recaptcha.php
To use the reCAPTCHA system load the component
After you save the component and helper and initiate them, set your public & private keys in "beforeFilter" of your controller to the ones you received when you signed up on the reCAPTCHA website.
in the view, the helper can be used to verify form submissions or hide your e-mail addresses (NOTE: mcrypt is required for this.)
in the controller to verify a form submission using reCAPTCHA do the following.
I hope you enjoy it, this is the first component / helper (let alone helper) pair I've written.
Create the following files:
app/controllers/components/recaptcha.php
app/views/helpers/recaptcha.php
code for app/controllers/components/recaptcha.php
Component Class:
<?php
class RecaptchaComponent extends Object {
var $publickey = "";
var $privatekey= "";
var $is_valid = false;
var $error = "";
function startup(&$controller){
Configure::write("Recaptcha.apiServer","http://api.recaptcha.net");
Configure::write("Recaptcha.apiSecureServer","https://api-secure.recaptcha.net");
Configure::write("Recaptcha.verifyServer","api-verify.recaptcha.net");
Configure::write("Recaptcha.pubKey", $this->publickey);
Configure::write("Recaptcha.privateKey", $this->privatekey);
$this->controller =& $controller;
$this->controller->helpers[] = "Recaptcha";
}
function valid($form){
if (isset($form['recaptcha_challenge_field']) && isset($form['recaptcha_response_field'])){
if($this->recaptcha_check_answer(
$this->privatekey,
$_SERVER["REMOTE_ADDR"],
$form['recaptcha_challenge_field'],
$form['recaptcha_response_field']
) == 0)
return false;
if ($this->is_valid)
return true;
}
return false;
}
/**
* Calls an HTTP POST function to verify if the user's guess was correct
* @param string $privkey
* @param string $remoteip
* @param string $challenge
* @param string $response
* @param array $extra_params an array of extra variables to post to the server
* @return ReCaptchaResponse
*/
function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()){
if ($privkey == null || $privkey == ''){
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($remoteip == null || $remoteip == ''){
die ("For security reasons, you must pass the remote ip to reCAPTCHA");
}
//discard spam submissions
if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
$this->is_valid = false;
$this->error = 'incorrect-captcha-sol';
return 0;
}
$response = $this->_recaptcha_http_post(Configure::read('Recaptcha.verifyServer'), "/verify",
array (
'privatekey' => $privkey,
'remoteip' => $remoteip,
'challenge' => $challenge,
'response' => $response
) + $extra_params
);
$answers = explode ("\n", $response [1]);
if (trim ($answers [0]) == 'true') {
$this->is_valid = true;
return 1;
}else{
$this->is_valid = false;
$this->error = $answers [1];
return 0;
}
}
/**
* Submits an HTTP POST to a reCAPTCHA server
* @param string $host
* @param string $path
* @param array $data
* @param int port
* @return array response
*/
function _recaptcha_http_post($host, $path, $data, $port = 80) {
$req = $this->_recaptcha_qsencode ($data);
$http_request = "POST $path HTTP/1.0\r\n";
$http_request .= "Host: $host\r\n";
$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
$http_request .= "Content-Length: " . strlen($req) . "\r\n";
$http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
$http_request .= "\r\n";
$http_request .= $req;
$response = '';
if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) {
die ('Could not open socket');
}
fwrite($fs, $http_request);
while ( !feof($fs) )
$response .= fgets($fs, 1160); // One TCP-IP packet
fclose($fs);
$response = explode("\r\n\r\n", $response, 2);
return $response;
}
/**
* Encodes the given data into a query string format
* @param $data - array of string elements to be encoded
* @return string - encoded request
*/
function _recaptcha_qsencode ($data) {
$req = "";
foreach ( $data as $key => $value )
$req .= $key . '=' . urlencode( stripslashes($value) ) . '&';
// Cut the last '&'
$req=substr($req,0,strlen($req)-1);
return $req;
}
}
?>
code for app/views/helpers/recaptcha.php
Helper Class:
<?php
class RecaptchaHelper extends AppHelper {
var $helpers = array('form');
function display_form($output_method = 'return', $error = null, $use_ssl = false){
$data = $this->__form(Configure::read("Recaptcha.pubKey"),$error,$use_ssl);
if($output_method == "echo")
echo $data;
else
return $data;
}
function hide_mail($email = '',$output_method = 'return'){
$data = $this->recaptcha_mailhide_html(Configure::read('Recaptcha.pubKey'), Configure::read('Recaptcha.privateKey'), $email);
if($output_method == "echo")
echo $data;
else
return $data;
}
/**
* Gets the challenge HTML (javascript and non-javascript version).
* This is called from the browser, and the resulting reCAPTCHA HTML widget
* is embedded within the HTML form it was called from.
* @param string $pubkey A public key for reCAPTCHA
* @param string $error The error given by reCAPTCHA (optional, default is null)
* @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
* @return string - The HTML to be embedded in the user's form.
*/
function __form($pubkey, $error = null, $use_ssl = false){
if ($pubkey == null || $pubkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($use_ssl) {
$server = Configure::read('Recaptcha.apiSecureServer');
} else {
$server = Configure::read('Recaptcha.apiServer');
}
$errorpart = "";
if ($error) {
$errorpart = "&error=" . $error;
}
return '<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
<noscript>
<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
</noscript>';
}
/* Mailhide related code */
function _recaptcha_aes_encrypt($val,$ky) {
if (! function_exists ("mcrypt_encrypt")) {
die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
}
$mode=MCRYPT_MODE_CBC;
$enc=MCRYPT_RIJNDAEL_128;
$val=$this->_recaptcha_aes_pad($val);
return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
function _recaptcha_mailhide_urlbase64 ($x) {
return strtr(base64_encode ($x), '+/', '-_');
}
/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
function recaptcha_mailhide_url($pubkey, $privkey, $email) {
if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
"you can do so at <a href='http://mailhide.recaptcha.net/apikey'>http://mailhide.recaptcha.net/apikey</a>");
}
$ky = pack('H*', $privkey);
$cryptmail = $this->_recaptcha_aes_encrypt ($email, $ky);
return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . $this->_recaptcha_mailhide_urlbase64 ($cryptmail);
}
/**
* gets the parts of the email to expose to the user.
* eg, given johndoe@example,com return ["john", "example.com"].
* the email is then displayed as john...@example.com
*/
function _recaptcha_mailhide_email_parts ($email) {
$arr = preg_split("/@/", $email );
if (strlen ($arr[0]) <= 4) {
$arr[0] = substr ($arr[0], 0, 1);
} else if (strlen ($arr[0]) <= 6) {
$arr[0] = substr ($arr[0], 0, 3);
} else {
$arr[0] = substr ($arr[0], 0, 4);
}
return $arr;
}
/**
* Gets html to display an email address given a public an private key.
* to get a key, go to:
*
* http://mailhide.recaptcha.net/apikey
*/
function recaptcha_mailhide_html($pubkey, $privkey, $email) {
$emailparts = $this->_recaptcha_mailhide_email_parts ($email);
$url = $this->recaptcha_mailhide_url ($pubkey, $privkey, $email);
return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
}
}
?>
To use the reCAPTCHA system load the component
Controller Class:
<?php
var $components = array('Recaptcha');
?>
After you save the component and helper and initiate them, set your public & private keys in "beforeFilter" of your controller to the ones you received when you signed up on the reCAPTCHA website.
Controller Class:
<?php
function beforeFilter(){
$this->Recaptcha->publickey = "";
$this->Recaptcha->privatekey = "";
}
?>
in the view, the helper can be used to verify form submissions or hide your e-mail addresses (NOTE: mcrypt is required for this.)
Controller Class:
<?php
//create the reCAPTCHA form.
$recaptcha->display_form('echo')
//hide an e-mail address
$recaptcha->hide_mail("someuser@somdomain.tld",'echo');
?>
in the controller to verify a form submission using reCAPTCHA do the following.
Controller Class:
<?php
if($this->Recaptcha->valid($this->params['form']))
//submission is valid!
else
//invalid reCAPTCHA entry.
?>
I hope you enjoy it, this is the first component / helper (let alone helper) pair I've written.








Thanks for the component and helper code... this works pretty well. Though, recently I ran into an issue with the URLs. It appears the old recaptcha API URLs aren't working anymore. To get this to work, again, I changed the following (both changes are in the controller):
in startup()...
Configure::write("Recaptcha.apiServer","http://www.google.com/recaptcha/api");
Configure::write("Recaptcha.apiSecureServer","https://www.google.com/recaptcha/api");
Configure::write("Recaptcha.verifyServer","www.google.com");
in recaptcha_check_answer()...
$response = $this->_recaptcha_http_post(Configure::read('Recaptcha.verifyServer'), "/recaptcha/api/verify", [...]
I hope this helps!
Thank you.
public function register( $data = array(), $recaptcha = false ) {
$this->set( $data );
if ( $this->validates() ) {
if ( $recaptcha ) {...}
}
return false;
}
Very simple!
What I need to do to put this working fine?
Although, I am having issue when validating the form. When I click submit on an empty form. It first validates the captcha and does not display the error on each forms. What I wanted is, validate the form first, then recaptcha.
I am using this code.
if($this->Recaptcha->valid($this->params['form'])) {
if (!empty($this->data)) {
$this->Feedback->create();
if ($this->Feedback->save($this->data)) {
//... save here
} else {
$this->Session->setFlash(__('could not be saved.', true));
}
}
}
}
else {
//invalid reCAPTCHA entry.
$this->Session->setFlash(__('Invalid captcha. Please, try again.', true));
}
What might be possibly wrong?
Thanks
use
$this->Recaptcha->valid($this->params['form']))
but Undefined property: ContactsController::$Recaptcha,
Fatal error: Call to a member function valid() on a non-object
the helpers it works great.
cakephp v1.3.2
Thanks.
www.the-di-lab.com
Worked perfectly - well done!
If you update to 1.3 and you want to use the new Component settings feature, you can change the startup function to initialize, and then just use the settings array.
Component Class:
<?php
function initialize(&$controller, $settings = array()){
$this->publickey = $settings['publickey'];
$this->privatekey = $settings['privatekey'];
Configure::write("Recaptcha.apiServer","http://api.recaptcha.net");
Configure::write("Recaptcha.apiSecureServer","https://api-secure.recaptcha.net");
Configure::write("Recaptcha.verifyServer","api-verify.recaptcha.net");
Configure::write("Recaptcha.pubKey", $this->publickey);
Configure::write("Recaptcha.privateKey", $this->privatekey);
$this->controller =& $controller;
$this->controller->helpers[] = "Recaptcha";
}
?>
And you would set your variables either in app_controller or where ever.
Controller Class:
<?phpIt's a bit of a frivolous change, but it keeps your beforeFilter clean.var $components = array(
'Recaptcha' => array(
'publickey' => 'key',
'privatekey' => 'other_key'
),
'Auth' => array(
'userModel' => 'MyUser',
'loginAction' => array('controller' => 'users', 'action' => 'login')
)
);
?>
Here is the modified Helper code (app/views/helpers/recaptcha.php)
Helper Class:
<?php
<?php
class RecaptchaHelper extends AppHelper {
var $helpers = array('form');
function display_form($output_method = 'return', $error = null, $use_ssl = false){
$data = $this->__form(Configure::read("Recaptcha.pubKey"),$error,$use_ssl);
if($output_method == "echo")
echo $data;
else
return $data;
}
function hide_mail($email = '',$output_method = 'return'){
$data = $this->recaptcha_mailhide_html(Configure::read('Recaptcha.pubKey'), Configure::read('Recaptcha.privateKey'), $email);
if($output_method == "echo")
echo $data;
else
return $data;
}
/**
* Gets the challenge HTML (javascript and non-javascript version).
* This is called from the browser, and the resulting reCAPTCHA HTML widget
* is embedded within the HTML form it was called from.
* @param string $pubkey A public key for reCAPTCHA
* @param string $error The error given by reCAPTCHA (optional, default is null)
* @param boolean $use_ssl Should the request be made over ssl? (optional, default is false)
* @return string - The HTML to be embedded in the user's form.
*/
function __form($pubkey, $error = null, $use_ssl = false){
if ($pubkey == null || $pubkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($use_ssl) {
$server = Configure::read('Recaptcha.apiSecureServer');
} else {
$server = Configure::read('Recaptcha.apiServer');
}
$errorpart = "";
if ($error) {
$errorpart = "&error=" . $error;
}
return '
<script type="text/javascript" src="' . $server .'/js/recaptcha_ajax.js">
</script>
<div id="recaptcha_div"></div>
<script type="text/javascript">
Recaptcha.create("' . $pubkey . '",
"recaptcha_div", {
theme: "clean",
});
</script>
';
}
/* Mailhide related code */
function _recaptcha_aes_encrypt($val,$ky) {
if (! function_exists ("mcrypt_encrypt")) {
die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
}
$mode=MCRYPT_MODE_CBC;
$enc=MCRYPT_RIJNDAEL_128;
$val=$this->_recaptcha_aes_pad($val);
return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
function _recaptcha_mailhide_urlbase64 ($x) {
return strtr(base64_encode ($x), '+/', '-_');
}
/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
function recaptcha_mailhide_url($pubkey, $privkey, $email) {
if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) {
die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
"you can do so at <a href='http://mailhide.recaptcha.net/apikey'>http://mailhide.recaptcha.net/apikey</a>");
}
$ky = pack('H*', $privkey);
$cryptmail = $this->_recaptcha_aes_encrypt ($email, $ky);
return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . $this->_recaptcha_mailhide_urlbase64 ($cryptmail);
}
/**
* gets the parts of the email to expose to the user.
* eg, given johndoe@example,com return ["john", "example.com"].
* the email is then displayed as john...@example.com
*/
function _recaptcha_mailhide_email_parts ($email) {
$arr = preg_split("/@/", $email );
if (strlen ($arr[0]) <= 4) {
$arr[0] = substr ($arr[0], 0, 1);
} else if (strlen ($arr[0]) <= 6) {
$arr[0] = substr ($arr[0], 0, 3);
} else {
$arr[0] = substr ($arr[0], 0, 4);
}
return $arr;
}
/**
* Gets html to display an email address given a public an private key.
* to get a key, go to:
*
* http://mailhide.recaptcha.net/apikey
*/
function recaptcha_mailhide_html($pubkey, $privkey, $email) {
$emailparts = $this->_recaptcha_mailhide_email_parts ($email);
$url = $this->recaptcha_mailhide_url ($pubkey, $privkey, $email);
return htmlentities($emailparts[0]) . "<a href='" . htmlentities ($url) .
"' onclick=\"window.open('" . htmlentities ($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>@" . htmlentities ($emailparts [1]);
}
}
?>
?>
This is using the clean theme, check the reCAPTCHA site for other available themes. I hope this helps!
Sure thing, it's really only two lines added to the component, and one line to the __form() function of the helper, but I'll paste the beginning of the component and the whole __form() function so you can see the changes in some sort of context.
Component:
<?php
class RecaptchaComponent extends Object {
var $publickey = "";
var $privatekey= "";
var $theme= "clean";
var $is_valid = false;
var $error = "";
function startup(&$controller){
Configure::write("Recaptcha.apiServer","http://api.recaptcha.net");
Configure::write("Recaptcha.apiSecureServer","https://api-secure.recaptcha.net");
Configure::write("Recaptcha.verifyServer","api-verify.recaptcha.net");
Configure::write("Recaptcha.pubKey", $this->publickey);
Configure::write("Recaptcha.privateKey", $this->privatekey);
Configure::write("Recaptcha.theme", $this->theme);
$this->controller =& $controller;
$this->controller->helpers[] = "Recaptcha";
}
...
helper->__form():
function __form($pubkey, $error = null, $use_ssl = false){
if ($pubkey == null || $pubkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='http://recaptcha.net/api/getkey'>http://recaptcha.net/api/getkey</a>");
}
if ($use_ssl) {
$server = Configure::read('Recaptcha.apiSecureServer');
} else {
$server = Configure::read('Recaptcha.apiServer');
}
$errorpart = "";
if ($error) {
$errorpart = "&error=" . $error;
}
return '<script>var RecaptchaOptions = {theme:\''.Configure::read('Recaptcha.theme').'\'}; </script>
<script type="text/javascript" src="'. $server . '/challenge?k=' . $pubkey . $errorpart . '"></script>
<noscript>
<iframe src="'. $server . '/noscript?k=' . $pubkey . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
</noscript>';
}
Then, to change the theme you simply set $this->Recaptcha->theme = ''; in your controller as you do with the public and private key component variables.
Hope it's of help!
Paul.
I have added a theme variable so I can change the default skin on the captcha which required passing through the variable from the controller, through the component to the helper which echoes "var RecaptchaOptions = {theme:\''.Configure::read('Recaptcha.theme').'\'}; " before the main javascript.
Your first component/helper was a good one, keep up the good work!
Paul.
Here
http://www.linux.uz/dsearch/signup
Thanks again!
$this->Security->disabledFields = array('recaptcha_challenge_field', 'recaptcha_response_field');
I've used the block
<?php
if($this->Recaptcha->valid($this->params['form']))
//submission is valid!
else
//invalid reCAPTCHA entry.
?>
Any workarounds yet?
Hi here's a solution you can try, worked for me!
Check your corresponding view, app/views/users/register.ctp (in my case)
And make sure the code that displays the reCaptcha form:
$recaptcha->display_form('echo')is within your form,NOT outside example:
<?php
Hope this helps! :)echo $form->create('User', array('url' => '/users/register'));
$recaptcha->display_form('echo');
echo $form->input('username');
echo $form->input('password');
echo $form->input('email');
echo $form->input('first_name');
echo $form->input('last_name');
echo $form->end('Register');
?>
Warning (2): pack() [function.pack]: Type H: illegal hex digit L [APP/views/helpers/recaptcha.php, line 79]
I must be missing a step but I can't figure it out. Can anyone help?
Hi try this!
It seems one of the functions in your "/helpers/recaptcha.php" is missing, so add this function in there!
function _recaptcha_aes_pad($val)
{
$block_size = 16;
$numpad = $block_size - (strlen ($val) % $block_size);
return str_pad($val, strlen ($val) + $numpad, chr($numpad));
}
Then... find the
function hide_mailin the same page and add in the Public and Private Keys given for MailHide as variables, see below..function hide_mail($email = '',$output_method = 'return'){
$pubkey = "01URu9mx0VAUTVkg3Eb6Bn1Q==";
$privkey = "8AE48922E14FB61F90BDDBE4C61D70E5";
$data = $this->recaptcha_mailhide_html($pubkey, $privkey, $email);
if($output_method == "echo")
echo $data;
else
return $data;
}
That solved the problem for me! Cheers! :)
Any ideas where I might be going wrong or why $this->params['form'] refuses to be populated with the captcha response? Any help would be greatly appreciated.
*** update ***
It seems like this may be a problem with browsers? Everything works beautifully in IE 8. $this->params[form] is populated appropriately and I can validate the captcha response.
Unfortunately it fails in Firefox 3.0.4 (safe mode), Safari 3.2, and Opera 9.62. At least it does for me. Anyone else validate or have a solution?
I have exactly the same problem, though it has worked for some time, and I don't know why now it doesn't work anymore (and I really don't understand why in the ___form() function there are two inputs with the same name?)
**EDIT**
you need valid XHTML pages for this to work. I've modified my page according to the W3C validator, and now it works well!
Comments are closed for articles over a year old