From 96fd37de2ef1107fb4a2f000244d43c45c67de7e Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 8 May 2013 15:27:06 -0400 Subject: [PATCH] Fixes issue #49: finished CAPTCHA feature. --- .../protected/controllers/SiteController.php | 9 + apps/bootstrap/protected/models/ContactForm.php | 2 +- apps/bootstrap/protected/views/site/contact.php | 10 + framework/assets.php | 7 + framework/assets/yii.activeForm.js | 6 +- framework/assets/yii.captcha.js | 72 +++++ framework/validators/CaptchaValidator.php | 3 +- framework/web/CaptchaAction.php | 338 +++++++++++++++++++++ framework/web/SpicyRice.md | 11 + framework/web/SpicyRice.ttf | Bin 0 -> 67244 bytes framework/widgets/ActiveField.php | 2 +- framework/widgets/Captcha.php | 102 +++++++ 12 files changed, 556 insertions(+), 6 deletions(-) create mode 100644 framework/assets/yii.captcha.js create mode 100644 framework/web/CaptchaAction.php create mode 100644 framework/web/SpicyRice.md create mode 100644 framework/web/SpicyRice.ttf create mode 100644 framework/widgets/Captcha.php diff --git a/apps/bootstrap/protected/controllers/SiteController.php b/apps/bootstrap/protected/controllers/SiteController.php index d1186f6..b06ed06 100644 --- a/apps/bootstrap/protected/controllers/SiteController.php +++ b/apps/bootstrap/protected/controllers/SiteController.php @@ -6,6 +6,15 @@ use app\models\ContactForm; class SiteController extends Controller { + public function actions() + { + return array( + 'captcha' => array( + 'class' => 'yii\web\CaptchaAction', + ), + ); + } + public function actionIndex() { echo $this->render('index'); diff --git a/apps/bootstrap/protected/models/ContactForm.php b/apps/bootstrap/protected/models/ContactForm.php index 5124b2c..7b713a1 100644 --- a/apps/bootstrap/protected/models/ContactForm.php +++ b/apps/bootstrap/protected/models/ContactForm.php @@ -26,7 +26,7 @@ class ContactForm extends Model // email has to be a valid email address array('email', 'email'), // verifyCode needs to be entered correctly - //array('verifyCode', 'captcha', 'allowEmpty' => !Captcha::checkRequirements()), + array('verifyCode', 'captcha'), ); } diff --git a/apps/bootstrap/protected/views/site/contact.php b/apps/bootstrap/protected/views/site/contact.php index 4115b53..bee1ede 100644 --- a/apps/bootstrap/protected/views/site/contact.php +++ b/apps/bootstrap/protected/views/site/contact.php @@ -1,6 +1,7 @@ params['breadcrumbs'][] = $this->title; field($model, 'email')->textInput(); ?> field($model, 'subject')->textInput(); ?> field($model, 'body')->textArea(array('rows' => 6)); ?> + field($model, 'verifyCode'); + echo $field->begin(); + echo $field->label(); + $this->widget(Captcha::className()); + echo Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')); + echo $field->error(); + echo $field->end(); + ?>
'btn btn-primary')); ?>
diff --git a/framework/assets.php b/framework/assets.php index 919011b..10a450a 100644 --- a/framework/assets.php +++ b/framework/assets.php @@ -28,4 +28,11 @@ return array( ), 'depends' => array('yii', 'yii/validation'), ), + 'yii/captcha' => array( + 'sourcePath' => __DIR__ . '/assets', + 'js' => array( + 'yii.captcha.js', + ), + 'depends' => array('yii'), + ), ); diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index 158ea74..d987879 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -116,8 +116,8 @@ }); }, - options: function() { - return this.data('yiiActiveForm').settings; + data: function() { + return this.data('yiiActiveForm'); }, submitForm: function () { @@ -384,4 +384,4 @@ } }; -})(window.jQuery); \ No newline at end of file +})(window.jQuery); diff --git a/framework/assets/yii.captcha.js b/framework/assets/yii.captcha.js new file mode 100644 index 0000000..9211edb --- /dev/null +++ b/framework/assets/yii.captcha.js @@ -0,0 +1,72 @@ +/** + * Yii Captcha widget. + * + * This is the JavaScript widget used by the yii\widgets\Captcha widget. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ +(function ($) { + $.fn.yiiCaptcha = function (method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.yiiCaptcha'); + return false; + } + }; + + var defaults = { + refreshUrl: undefined, + hashKey: undefined + }; + + var methods = { + init: function (options) { + return this.each(function () { + var $e = $(this); + var settings = $.extend({}, defaults, options || {}); + $e.data('yiiCaptcha', { + settings: settings + }); + + $e.on('click.yiiCaptcha', function() { + methods.refresh.apply($e); + return false; + }); + + }); + }, + + refresh: function () { + var $e = this, + settings = this.data('yiiCaptcha').settings; + $.ajax({ + url: $e.data('yiiCaptcha').settings.refreshUrl, + dataType: 'json', + cache: false, + success: function(data) { + $e.attr('src', data['url']); + $('body').data(settings.hashKey, [data['hash1'], data['hash2']]); + } + }); + }, + + destroy: function () { + return this.each(function () { + $(window).unbind('.yiiCaptcha'); + $(this).removeData('yiiCaptcha'); + }); + }, + + data: function() { + return this.data('yiiCaptcha'); + } + }; +})(window.jQuery); + diff --git a/framework/validators/CaptchaValidator.php b/framework/validators/CaptchaValidator.php index 4eba9df..2e58cf2 100644 --- a/framework/validators/CaptchaValidator.php +++ b/framework/validators/CaptchaValidator.php @@ -21,6 +21,7 @@ use yii\helpers\Html; */ class CaptchaValidator extends Validator { + public $skipOnEmpty = false; /** * @var boolean whether the comparison is case sensitive. Defaults to false. */ @@ -70,7 +71,7 @@ class CaptchaValidator extends Validator /** * Returns the CAPTCHA action object. * @throws InvalidConfigException - * @return CaptchaAction the action object + * @return \yii\web\CaptchaAction the action object */ public function getCaptchaAction() { diff --git a/framework/web/CaptchaAction.php b/framework/web/CaptchaAction.php new file mode 100644 index 0000000..e3d6eaa --- /dev/null +++ b/framework/web/CaptchaAction.php @@ -0,0 +1,338 @@ + + * @since 2.0 + */ +class CaptchaAction extends Action +{ + /** + * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated. + */ + const REFRESH_GET_VAR = 'refresh'; + /** + * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3. + * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2). + */ + public $testLimit = 3; + /** + * @var integer the width of the generated CAPTCHA image. Defaults to 120. + */ + public $width = 120; + /** + * @var integer the height of the generated CAPTCHA image. Defaults to 50. + */ + public $height = 50; + /** + * @var integer padding around the text. Defaults to 2. + */ + public $padding = 2; + /** + * @var integer the background color. For example, 0x55FF00. + * Defaults to 0xFFFFFF, meaning white color. + */ + public $backColor = 0xFFFFFF; + /** + * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color). + */ + public $foreColor = 0x2040A0; + /** + * @var boolean whether to use transparent background. Defaults to false. + */ + public $transparent = false; + /** + * @var integer the minimum length for randomly generated word. Defaults to 6. + */ + public $minLength = 6; + /** + * @var integer the maximum length for randomly generated word. Defaults to 7. + */ + public $maxLength = 7; + /** + * @var integer the offset between characters. Defaults to -2. You can adjust this property + * in order to decrease or increase the readability of the captcha. + **/ + public $offset = -2; + /** + * @var string the TrueType font file. This can be either a file path or path alias. + */ + public $fontFile = '@yii/web/SpicyRice.ttf'; + /** + * @var string the fixed verification code. When this is property is set, + * [[getVerifyCode()]] will always return the value of this property. + * This is mainly used in automated tests where we want to be able to reproduce + * the same verification code each time we run the tests. + * If not set, it means the verification code will be randomly generated. + */ + public $fixedVerifyCode; + + + /** + * Initializes the action. + * @throws InvalidConfigException if the font file does not exist. + */ + public function init() + { + $this->fontFile = Yii::getAlias($this->fontFile); + if (!is_file($this->fontFile)) { + throw new InvalidConfigException("The font file does not exist: {$this->fontFile}"); + } + } + + /** + * Runs the action. + */ + public function run() + { + if (isset($_GET[self::REFRESH_GET_VAR])) { + // AJAX request for regenerating code + $code = $this->getVerifyCode(true); + echo json_encode(array( + 'hash1' => $this->generateValidationHash($code), + 'hash2' => $this->generateValidationHash(strtolower($code)), + // we add a random 'v' parameter so that FireFox can refresh the image + // when src attribute of image tag is changed + 'url' => $this->controller->createUrl($this->id, array('v' => uniqid())), + )); + } else { + $this->renderImage($this->getVerifyCode()); + } + Yii::$app->end(); + } + + /** + * Generates a hash code that can be used for client side validation. + * @param string $code the CAPTCHA code + * @return string a hash code generated from the CAPTCHA code + */ + public function generateValidationHash($code) + { + for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) { + $h += ord($code[$i]); + } + return $h; + } + + /** + * Gets the verification code. + * @param boolean $regenerate whether the verification code should be regenerated. + * @return string the verification code. + */ + public function getVerifyCode($regenerate = false) + { + if ($this->fixedVerifyCode !== null) { + return $this->fixedVerifyCode; + } + + $session = Yii::$app->session; + $session->open(); + $name = $this->getSessionKey(); + if ($session[$name] === null || $regenerate) { + $session[$name] = $this->generateVerifyCode(); + $session[$name . 'count'] = 1; + } + return $session[$name]; + } + + /** + * Validates the input to see if it matches the generated code. + * @param string $input user input + * @param boolean $caseSensitive whether the comparison should be case-sensitive + * @return boolean whether the input is valid + */ + public function validate($input, $caseSensitive) + { + $code = $this->getVerifyCode(); + $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0; + $session = Yii::$app->session; + $session->open(); + $name = $this->getSessionKey() . 'count'; + $session[$name] = $session[$name] + 1; + if ($session[$name] > $this->testLimit && $this->testLimit > 0) { + $this->getVerifyCode(true); + } + return $valid; + } + + /** + * Generates a new verification code. + * @return string the generated verification code + */ + protected function generateVerifyCode() + { + if ($this->minLength < 3) { + $this->minLength = 3; + } + if ($this->maxLength > 20) { + $this->maxLength = 20; + } + if ($this->minLength > $this->maxLength) { + $this->maxLength = $this->minLength; + } + $length = mt_rand($this->minLength, $this->maxLength); + + $letters = 'bcdfghjklmnpqrstvwxyz'; + $vowels = 'aeiou'; + $code = ''; + for ($i = 0; $i < $length; ++$i) { + if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) { + $code .= $vowels[mt_rand(0, 4)]; + } else { + $code .= $letters[mt_rand(0, 20)]; + } + } + + return $code; + } + + /** + * Returns the session variable name used to store verification code. + * @return string the session variable name + */ + protected function getSessionKey() + { + return '__captcha/' . $this->getUniqueId(); + } + + /** + * Renders the CAPTCHA image. + * @param string $code the verification code + */ + protected function renderImage($code) + { + if (Captcha::checkRequirements() === 'gd') { + $this->renderImageByGD($code); + } else { + $this->renderImageByImagick($code); + } + } + + /** + * Renders the CAPTCHA image based on the code using GD library. + * @param string $code the verification code + */ + protected function renderImageByGD($code) + { + $image = imagecreatetruecolor($this->width, $this->height); + + $backColor = imagecolorallocate($image, + (int)($this->backColor % 0x1000000 / 0x10000), + (int)($this->backColor % 0x10000 / 0x100), + $this->backColor % 0x100); + imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor); + imagecolordeallocate($image, $backColor); + + if ($this->transparent) { + imagecolortransparent($image, $backColor); + } + + $foreColor = imagecolorallocate($image, + (int)($this->foreColor % 0x1000000 / 0x10000), + (int)($this->foreColor % 0x10000 / 0x100), + $this->foreColor % 0x100); + + if ($this->fontFile === null) { + $this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf'; + } + + $length = strlen($code); + $box = imagettfbbox(30, 0, $this->fontFile, $code); + $w = $box[4] - $box[0] + $this->offset * ($length - 1); + $h = $box[1] - $box[5]; + $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h); + $x = 10; + $y = round($this->height * 27 / 40); + for ($i = 0; $i < $length; ++$i) { + $fontSize = (int)(rand(26, 32) * $scale * 0.8); + $angle = rand(-10, 10); + $letter = $code[$i]; + $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter); + $x = $box[2] + $this->offset; + } + + imagecolordeallocate($image, $foreColor); + + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Transfer-Encoding: binary'); + header("Content-type: image/png"); + imagepng($image); + imagedestroy($image); + } + + /** + * Renders the CAPTCHA image based on the code using ImageMagick library. + * @param string $code the verification code + */ + protected function renderImageByImagick($code) + { + $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor)); + $foreColor = new \ImagickPixel('#' . dechex($this->foreColor)); + + $image = new \Imagick(); + $image->newImage($this->width, $this->height, $backColor); + + if ($this->fontFile === null) { + $this->fontFile = dirname(__FILE__) . '/SpicyRice.ttf'; + } + + $draw = new \ImagickDraw(); + $draw->setFont($this->fontFile); + $draw->setFontSize(30); + $fontMetrics = $image->queryFontMetrics($draw, $code); + + $length = strlen($code); + $w = (int)($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1); + $h = (int)($fontMetrics['textHeight']) - 8; + $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h); + $x = 10; + $y = round($this->height * 27 / 40); + for ($i = 0; $i < $length; ++$i) { + $draw = new \ImagickDraw(); + $draw->setFont($this->fontFile); + $draw->setFontSize((int)(rand(26, 32) * $scale * 0.8)); + $draw->setFillColor($foreColor); + $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]); + $fontMetrics = $image->queryFontMetrics($draw, $code[$i]); + $x += (int)($fontMetrics['textWidth']) + $this->offset; + } + + header('Pragma: public'); + header('Expires: 0'); + header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); + header('Content-Transfer-Encoding: binary'); + header("Content-type: image/png"); + $image->setImageFormat('png'); + echo $image; + } +} diff --git a/framework/web/SpicyRice.md b/framework/web/SpicyRice.md new file mode 100644 index 0000000..d99f3dc --- /dev/null +++ b/framework/web/SpicyRice.md @@ -0,0 +1,11 @@ +## Spicy Rice font + +* **Author:** Brian J. Bonislawsky, Astigmatic (AOETI, Astigmatic One Eye Typographic Institute) +* **License:** SIL Open Font License (OFL), version 1.1, [notes and FAQ](http://scripts.sil.org/OFL) + +## Links + +* [Astigmatic](http://www.astigmatic.com/) +* [Google WebFonts](http://www.google.com/webfonts/specimen/Spicy+Rice) +* [fontsquirrel.com](http://www.fontsquirrel.com/fonts/spicy-rice) +* [fontspace.com](http://www.fontspace.com/astigmatic-one-eye-typographic-institute/spicy-rice) diff --git a/framework/web/SpicyRice.ttf b/framework/web/SpicyRice.ttf new file mode 100644 index 0000000000000000000000000000000000000000..638436cd38eab22a9e9d1a3d160a4542ec73f955 GIT binary patch literal 67244 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 W)Br(I?aL1Nmodel, $this->attribute); $options['name'] = $inputID; $names = array( diff --git a/framework/widgets/Captcha.php b/framework/widgets/Captcha.php new file mode 100644 index 0000000..918e30c --- /dev/null +++ b/framework/widgets/Captcha.php @@ -0,0 +1,102 @@ + + * @since 2.0 + */ +class Captcha extends Widget +{ + /** + * @var string the route of the action that generates the CAPTCHA images. + * The action represented by this route must be an action of [[CaptchaAction]]. + */ + public $captchaAction = 'site/captcha'; + /** + * @var array HTML attributes to be applied to the rendered image element. + */ + public $options = array(); + + + /** + * Renders the widget. + */ + public function run() + { + $this->checkRequirements(); + + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + $id = $this->options['id']; + $options = Json::encode($this->getClientOptions()); + $this->view->registerAssetBundle('yii/captcha'); + $this->view->registerJs("jQuery('#$id').yiiCaptcha($options);"); + $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid())); + echo Html::img($url, $this->options); + } + + /** + * Returns the options for the captcha JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $options = array( + 'refreshUrl' => Html::url(array($this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1)), + 'hashKey' => "yiiCaptcha/{$this->captchaAction}", + ); + return $options; + } + + /** + * Checks if there is graphic extension available to generate CAPTCHA images. + * This method will check the existence of ImageMagick and GD extensions. + * @return string the name of the graphic extension, either "imagick" or "gd". + * @throws InvalidConfigException if neither ImageMagick nor GD is installed. + */ + public static function checkRequirements() + { + if (extension_loaded('imagick')) { + $imagick = new \Imagick(); + $imagickFormats = $imagick->queryFormats('PNG'); + if (in_array('PNG', $imagickFormats)) { + return 'imagick'; + } + } + if (extension_loaded('gd')) { + $gdInfo = gd_info(); + if (!empty($gdInfo['FreeType Support'])) { + return 'gd'; + } + } + throw new InvalidConfigException('GD with FreeType or ImageMagick PHP extensions are required.'); + } +}