diff --git a/src/Pluf/Form/Field/ReCaptcha.php b/src/Pluf/Form/Field/ReCaptcha.php new file mode 100644 index 0000000..4724e57 --- /dev/null +++ b/src/Pluf/Form/Field/ReCaptcha.php @@ -0,0 +1,170 @@ + + * $ssl = (!empty($extra['request']->SERVER['HTTPS']) + * and $extra['request']->SERVER['HTTPS'] != 'off'); + * + * $this->fields['recaptcha'] = new Pluf_Form_Field_ReCaptcha( + * array('required' => true, + * 'label' => __('Please solve this challenge'), + * 'privkey' => 'PRIVATE_RECAPTCHA_KEY_HERE', + * 'remoteip' => $extra['request']->remote_addr, + * 'widget_attrs' => array( + * 'pubkey' => 'PUBLIC_RECAPTCHA_KEY_HERE', + * ), + * )); + * + * + * Then in your template, you simply need to add the ReCaptcha field: + * + *
+ * {if $form.f.recaptcha.errors}{$form.f.recaptcha.fieldErrors}{/if} + * {$form.f.recaptcha|safe} + *+ * + * Based on http://recaptcha.googlecode.com/files/recaptcha-php-1.10.zip + * + * Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net + * AUTHORS: + * Mike Crawford + * Ben Maurer + */ +class Pluf_Form_Field_ReCaptcha extends Pluf_Form_Field +{ + public $widget = 'Pluf_Form_Widget_ReCaptcha'; + public $privkey = ''; + public $remoteip = ''; + public $extra_params = array(); + + public function clean($value) + { + // will throw the Pluf_Form_Invalid exception in case of + // error. + self::checkAnswer($this->privkey, $this->remoteip, + $value[0], $value[1], $this->extra_params); + return $value; + } + + /** + * Submits an HTTP POST to a reCAPTCHA server + * + * @param string Host + * @param string Path + * @param array Data + * @param int port (80 + * @return array response + */ + public static function httpPost($host, $path, $data, $port=80) + { + + $req = self::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; + + if (false === ($fs=@fsockopen($host, $port, $errno, $errstr, 10))) { + throw new Pluf_Form_Invalid(__('Cannot connect to the reCaptcha server for validation.')); + } + fwrite($fs, $http_request); + $response = ''; + while (!feof($fs)) { + $response .= fgets($fs, 1160); // One TCP-IP packet + } + fclose($fs); + return explode("\r\n\r\n", $response, 2); + } + + /** + * Encodes the given data into a query string format + * + * @param array Array of string elements to be encoded + * @return string Encoded request + */ + public static function qsencode($data) + { + $d = array(); + foreach ($data as $key => $value) { + $d[] = $key.'='.urlencode(stripslashes($value)); + } + return implode('&', $d); + } + + /** + * 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 + */ + public static function checkAnswer($privkey, $remoteip, $challenge, $response, $extra_params=array()) + { + if ($privkey == '') { + throw new Pluf_Form_Invalid(__('To use reCAPTCHA you must set your API key.')); + } + if ($remoteip == '') { + throw new Pluf_Form_Invalid(__('For security reasons, you must pass the remote ip to reCAPTCHA.')); + } + //discard spam submissions + if (strlen($challenge) == 0 || strlen($response) == 0) { + return false; + } + + $response = self::httpPost('api-verify.recaptcha.net', '/verify', + array( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode("\n", $response[1]); + if (trim($answers[0]) == 'true') { + return true; + } else { + throw new Pluf_Form_Invalid($answers[1]); + } + } +} diff --git a/src/Pluf/Form/Widget/ReCaptcha.php b/src/Pluf/Form/Widget/ReCaptcha.php new file mode 100644 index 0000000..5d1e0c0 --- /dev/null +++ b/src/Pluf/Form/Widget/ReCaptcha.php @@ -0,0 +1,106 @@ +attrs['pubkey'])); + } + + /** + * 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 A public key for reCAPTCHA + * @param string The error given by reCAPTCHA (null) + * @param boolean Should the request be made over ssl? (false) + * @return string The HTML to be embedded in the user's form. + */ + public static function getHtml($pubkey, $error=null, $use_ssl=false) + { + $server = ($use_ssl) ? 'https://api-secure.recaptcha.net' + : 'http://api.recaptcha.net'; + $errorpart = ($error) ? '&error='.$error : ''; + + return ' + '; + } + + /** + * Get the form data from the reCaptcha fields. + * + * We need to get back two fields from the POST request + * 'recaptcha_challenge_field' and 'recaptcha_response_field'. + * + * They are hardcoded, so we do not even bother checking something + * else. + * + * @param string Name of the form + * @param array Submitted form data + * @return array Challenge and answer + */ + public function valueFromFormData($name, $data) + { + $res = array('', ''); + $res[0] = isset($data['recaptcha_challenge_field']) + ? $data['recaptcha_challenge_field'] : ''; + $res[1] = isset($data['recaptcha_response_field']) + ? $data['recaptcha_response_field'] : ''; + return $res; + } +}