From dd02492f08248bf9b53a20b54a3d49a9f78fc0ad Mon Sep 17 00:00:00 2001 From: xue <> Date: Wed, 29 Aug 2007 19:57:50 +0000 Subject: finished TCaptcha. --- .gitattributes | 3 + .../protected/pages/Controls/Captcha.page | 39 +++++ .../pages/Controls/Samples/TCaptcha/Home.page | 32 ++++ .../pages/Controls/Samples/TCaptcha/Home.php | 27 ++++ .../protected/pages/Controls/Standard.page | 4 + .../pages/GettingStarted/NewFeatures.page | 2 +- framework/Exceptions/messages/messages.txt | 12 +- framework/Web/UI/WebControls/TCaptcha.php | 81 +++++++++- framework/Web/UI/WebControls/assets/captcha.php | 169 +++++++++++++++++---- 9 files changed, 332 insertions(+), 37 deletions(-) create mode 100644 demos/quickstart/protected/pages/Controls/Captcha.page create mode 100644 demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.page create mode 100644 demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.php diff --git a/.gitattributes b/.gitattributes index d605adf9..ccaebf65 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1271,6 +1271,7 @@ demos/quickstart/protected/pages/Configurations/Templates3.page -text demos/quickstart/protected/pages/Configurations/UrlMapping.page -text demos/quickstart/protected/pages/Construction.page -text demos/quickstart/protected/pages/Controls/Button.page -text +demos/quickstart/protected/pages/Controls/Captcha.page -text demos/quickstart/protected/pages/Controls/CheckBox.page -text demos/quickstart/protected/pages/Controls/ClientScript.page -text demos/quickstart/protected/pages/Controls/ClientScriptLoader.page -text @@ -1318,6 +1319,8 @@ demos/quickstart/protected/pages/Controls/Samples/TBulletedList/Home.php -text demos/quickstart/protected/pages/Controls/Samples/TBulletedList/bullet.gif -text demos/quickstart/protected/pages/Controls/Samples/TButton/Home.page -text demos/quickstart/protected/pages/Controls/Samples/TButton/Home.php -text +demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.page -text +demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.php -text demos/quickstart/protected/pages/Controls/Samples/TCheckBox/Home.page -text demos/quickstart/protected/pages/Controls/Samples/TCheckBox/Home.php -text demos/quickstart/protected/pages/Controls/Samples/TCheckBoxList/Home.page -text diff --git a/demos/quickstart/protected/pages/Controls/Captcha.page b/demos/quickstart/protected/pages/Controls/Captcha.page new file mode 100644 index 00000000..244a3adc --- /dev/null +++ b/demos/quickstart/protected/pages/Controls/Captcha.page @@ -0,0 +1,39 @@ + + +

TCaptcha

+ + +

+TCaptcha displays a CAPTCHA that can be used to determine if the input is entered by a real user instead of some program. TCaptcha displays a token (a string consisting of alphanumeric characters) as an image and the user is expected to repeat the token in a text box. The token image is generated in a way such that it can be recognized by a human being, but not a program. +

+ +

+To use TCaptcha, you must enable the PHP GD2 extension with TrueType font support. Unlike other CAPTCHA scripts, TCaptcha does not need session or cookie. +

+ +

+The token generated by TCaptcha can be configured in several ways. To specify the length of characters in the token, set MinTokenLength and MaxTokenLength. To use case-insensitive token comparison and generate upper-case-only token, set CaseSensitive to false. More advanced users can try to set TokenAlphabet to specify which characters may appear in the generated tokens. +

+ +

+To specify the appearance of the generated token image, set TokenImageTheme to be an integer between 0 and 31. You may try the following example to see how this value affects the generated token image. The size of the generated token image is determined by TokenFontSize. In particular, the image width is proportional to the font size. You may also set Width to scale the generated image to your desired size, but the scaled image may not look good. +

+ +

+Upon postback, user input can be validated by calling the method TCaptcha.validate(). More easily, you can use a TCaptchaValidator to automate the validation work for you. The TCaptchaValidator has the additional benefit of being able to validate the user input on the client-side. By default, a generated token will remain unchanged during postbacks. A new token can be generated by calling TCaptcha.regenerateToken() manually. +

+ +

+The following template shows a typical use of the TCaptcha control: +

+ +<com:TCaptcha ID="Captcha" /> +<com:TTextBox ID="Input" /> +<com:TCaptchaValidator CaptchaControl="Captcha" + ControlToValidate="Input" + ErrorMessage="You are challenged!" /> + + + + +
$Id$
\ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.page b/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.page new file mode 100644 index 00000000..f47583e9 --- /dev/null +++ b/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.page @@ -0,0 +1,32 @@ + +

TCaptcha Samples

+ +

Validating CAPTCHA

+ + + + + + +

Different Themes of TCaptcha

+ + + + + + + + + + + + + +
ThemeImage
+ <%# $this->Data %> + + +
+ +
$Id$
+
\ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.php b/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.php new file mode 100644 index 00000000..4d54ce87 --- /dev/null +++ b/demos/quickstart/protected/pages/Controls/Samples/TCaptcha/Home.php @@ -0,0 +1,27 @@ +IsPostBack) + { + $this->CaptchaList->DataSource=range(0,31); + $this->CaptchaList->dataBind(); + } + } + + public function regenerateToken($sender,$param) + { + $this->Captcha->regenerateToken(); + $this->SubmitButton->Text="Submit"; + } + + public function buttonClicked($sender,$param) + { + if($this->IsValid) + $sender->Text="You passed!"; + } +} + +?> \ No newline at end of file diff --git a/demos/quickstart/protected/pages/Controls/Standard.page b/demos/quickstart/protected/pages/Controls/Standard.page index d481525c..7d961a84 100644 --- a/demos/quickstart/protected/pages/Controls/Standard.page +++ b/demos/quickstart/protected/pages/Controls/Standard.page @@ -7,6 +7,10 @@ TButton represents a click button on a Web page. It is mainly used to trigger page postback. +
  • + TCaptcha displays a CAPTCHA to keep spammers from signing up for certain accounts online. +
  • +
  • TCheckBox represents a checkbox on a Web page. It can be used to collect two-state user input.
  • diff --git a/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page b/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page index a5136d72..53b516cc 100644 --- a/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page +++ b/demos/quickstart/protected/pages/GettingStarted/NewFeatures.page @@ -10,7 +10,7 @@ This page summarizes the main new features that are introduced in each PRADO rel diff --git a/framework/Exceptions/messages/messages.txt b/framework/Exceptions/messages/messages.txt index a60b8226..33a6f752 100644 --- a/framework/Exceptions/messages/messages.txt +++ b/framework/Exceptions/messages/messages.txt @@ -432,4 +432,14 @@ urlmapping_configfile_invalid = TUrlMapping.ConfigFile '{0}' must point to an urlmappingpattern_serviceparameter_required = TUrlMappingPattern.ServiceParameter is required for pattern '{0}'. keyboard_forcontrol_required = TKeyboard.ForControl cannot be empty. -keyboard_forcontrol_invalid = TKeyboard.ForControl '{0}' is invalid. \ No newline at end of file +keyboard_forcontrol_invalid = TKeyboard.ForControl '{0}' is invalid. + +captcha_tokenimagetheme_invalid = TCaptcha.TokenImageTheme must be an integer between {0} and {1}. +captcha_tokenfontsize_invalid = TCaptcha.TokenFontSize must be an integer between {0} and {1}. +captcha_mintokenlength_invalid = TCaptcha.MinTokenLength must be an integer between {0} and {1}. +captcha_maxtokenlength_invalid = TCaptcha.MaxTokenLength must be an integer between {0} and {1}. +captcha_tokenalphabet_invalid = TCaptcha.TokenAlphabet must be a string consisting of at least 2 characters. +captcha_privatekey_unknown = TCaptcha.PrivateKey is unknown. Please make sure that your assets directory is writable by the Web server process. +captcha_gd2_required = TCaptcha requires PHP GD2 extension. +captcha_imagettftext_required = TCaptcha requires PHP GD2 extension with TrueType font support. +captcha_imagepng_required = TCaptcha requires PHP GD2 extension with PNG image format support. \ No newline at end of file diff --git a/framework/Web/UI/WebControls/TCaptcha.php b/framework/Web/UI/WebControls/TCaptcha.php index 17e9ad34..bff04236 100644 --- a/framework/Web/UI/WebControls/TCaptcha.php +++ b/framework/Web/UI/WebControls/TCaptcha.php @@ -18,11 +18,18 @@ Prado::using('System.Web.UI.WebControls.TImage'); * TCaptcha displays a CAPTCHA (a token displayed as an image) that can be used * to determine if the input is entered by a real user instead of some program. * + * Unlike other CAPTCHA scripts, TCaptcha does not need session or cookie. + * * The token (a string consisting of alphanumeric characters) displayed is automatically * generated and can be configured in several ways. To specify the length of characters * in the token, set {@link setMinTokenLength MinTokenLength} and {@link setMaxTokenLength MaxTokenLength}. * To use case-insensitive comparison and generate upper-case-only token, set {@link setCaseSensitive CaseSensitive} - * to false. + * to false. More advanced users can try to set {@link setTokenAlphabet TokenAlphabet}, which + * specifies what characters can appear in tokens. + * + * To specify the appearance of the generated token image, set {@link setTokenImageTheme TokenImageTheme} + * to be an integer between 0 and 31. And to adjust the generated image size, set {@link setTokenFontSize TokenFontSize} + * (you may also set {@link TWebControl::setWidth Width}, but the scaled image may not look good.) * * Upon postback, user input can be validated by calling {@link validate()}. * The {@link TCaptchaValidator} control can also be used to do validation, which provides @@ -30,6 +37,15 @@ Prado::using('System.Web.UI.WebControls.TImage'); * remain the same during multiple postbacks. A new one can be generated by calling * {@link regenerateToken()} manually. * + * The following template shows a typical use of TCaptcha control: + * + * + * + * + * + * * @author Qiang Xue * @version $Id$ * @package System.Web.UI.WebControls @@ -41,12 +57,67 @@ class TCaptcha extends TImage const MAX_TOKEN_LENGTH=40; private $_privateKey; + /** + * Checks the requirements needed for using TCaptcha. + */ public function onInit($param) { parent::onInit($param); $this->checkRequirements(); } + /** + * @return integer the theme of the token image. Defaults to 0. + */ + public function getTokenImageTheme() + { + return $this->getViewState('TokenImageTheme',0); + } + + /** + * Sets the theme of the token image. + * You may test each theme to find out the one you like the most. + * Below is the explanation of the theme value: + * It is treated as a 5-bit integer. Each bit toggles a specific feature of the image. + * Bit 0 (the least significant): whether the image is opaque (1) or transparent (0). + * Bit 1: whether we should add white noise to the image (1) or not (0). + * Bit 2: whether we should add a grid to the image (1) or not (0). + * Bit 3: whether we should add some scribbles to the image (1) or not (0). + * Bit 4: whether the image background should be morphed (1) or not (0). + * @param integer the theme of the token image. It must be an integer between 0 and 31. + */ + public function setTokenImageTheme($value) + { + $value=TPropertyValue::ensureInteger($value); + if($value>=0 && $value<=31) + $this->setViewState('TokenImageTheme',$value,0); + else + throw new TConfigurationException('captcha_tokenimagetheme_invalid',0,31); + } + + /** + * @return integer the font size used for displaying the token in an image. Defaults to 30. + */ + public function getTokenFontSize() + { + return $this->getViewState('TokenFontSize',30); + } + + /** + * Sets the font size used for displaying the token in an image. + * This property affects the generated token image size. + * The image width is proportional to this font size. + * @param integer the font size used for displaying the token in an image. It must be an integer between 20 and 100. + */ + public function setTokenFontSize($value) + { + $value=TPropertyValue::ensureInteger($value); + if($value>=20 && $value<=100) + $this->setViewState('TokenFontSize',$value,30); + else + throw new TConfigurationException('captcha_tokenfontsize_invalid',20,100); + } + /** * @return integer the minimum length of the token. Defaults to 5. */ @@ -104,11 +175,11 @@ class TCaptcha extends TImage } /** - * @return string the characters that may appear in the token. Defaults to '234578adefhijmnrtABDEFGHJLMNQRT'. + * @return string the characters that may appear in the token. Defaults to '234578adefhijmnrtABDEFGHJLMNRT'. */ public function getTokenAlphabet() { - return $this->getViewState('TokenAlphabet','234578adefhijmnrtABDEFGHJLMNQRT'); + return $this->getViewState('TokenAlphabet','234578adefhijmnrtABDEFGHJLMNRT'); } /** @@ -118,7 +189,7 @@ class TCaptcha extends TImage { if(strlen($value)<2) throw new TConfigurationException('captcha_tokenalphabet_invalid'); - $this->setViewState('TokenAlphabet',$value,'234578adefhijmnrtABDEFGHJLMNQRT'); + $this->setViewState('TokenAlphabet',$value,'234578adefhijmnrtABDEFGHJLMNRT'); } /** @@ -241,6 +312,8 @@ class TCaptcha extends TImage $options['tokenLength']=strlen($token); $options['caseSensitive']=$this->getCaseSensitive(); $options['alphabet']=$this->getTokenAlphabet(); + $options['fontSize']=$this->getTokenFontSize(); + $options['theme']=$this->getTokenImageTheme(); $str=serialize($options); return base64_encode(md5($privateKey.$str).$str); } diff --git a/framework/Web/UI/WebControls/assets/captcha.php b/framework/Web/UI/WebControls/assets/captcha.php index 3941eb44..2a4952a9 100644 --- a/framework/Web/UI/WebControls/assets/captcha.php +++ b/framework/Web/UI/WebControls/assets/captcha.php @@ -10,9 +10,17 @@ * @package System.Web.UI.WebControls.assets */ +define('THEME_OPAQUE_BACKGROUND',0x0001); +define('THEME_NOISY_BACKGROUND',0x0002); +define('THEME_HAS_GRID',0x0004); +define('THEME_HAS_SCRIBBLE',0x0008); +define('THEME_MORPH_BACKGROUND',0x0010); + require_once(dirname(__FILE__).'/captcha_key.php'); $token='error'; +$theme=0; + if(isset($_GET['options'])) { $str=base64_decode($_GET['options']); @@ -27,12 +35,14 @@ if(isset($_GET['options'])) $tokenLength=$options['tokenLength']; $caseSensitive=$options['caseSensitive']; $alphabet=$options['alphabet']; + $fontSize=$options['fontSize']; + $theme=$options['theme']; $token=generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensitive); } } } -displayToken($token); +displayToken($token,$fontSize,$theme); function generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensitive) { @@ -40,10 +50,10 @@ function generateToken($publicKey,$privateKey,$alphabet,$tokenLength,$caseSensit return $caseSensitive?$token:strtoupper($token); } -function hash2string($hex,$alphabet='') +function hash2string($hex,$alphabet) { if(strlen($alphabet)<2) - $alphabet='234578adefhijmnrtABDEFGHJLMNQRT'; + $alphabet='234578adefhijmnrtABDEFGHJLMNRT'; $hexLength=strlen($hex); $base=strlen($alphabet); $result=''; @@ -59,47 +69,144 @@ function hash2string($hex,$alphabet='') return $result; } -function displayToken($token) +function displayToken($token,$fontSize,$theme) { + if(($fontSize=(int)$fontSize)<22) + $fontSize=22; + if($fontSize>100) + $fontSize=100; $length=strlen($token); - $width=45*$length; - $height=70; + $padding=10; + $fontWidth=$fontSize; + $fontHeight=floor($fontWidth*1.5); + $width=$fontWidth*$length+$padding*2; + $height=$fontHeight; $image=imagecreatetruecolor($width,$height); + + addBackground + ( + $image, $width, $height, + $theme&THEME_OPAQUE_BACKGROUND, + $theme&THEME_NOISY_BACKGROUND, + $theme&THEME_HAS_GRID, + $theme&THEME_HAS_SCRIBBLE, + $theme&THEME_MORPH_BACKGROUND + ); + $font=dirname(__FILE__).DIRECTORY_SEPARATOR.'verase.ttf'; - $vred=rand(0,100); - $vgreen=rand(0,100); - $vblue=rand(0,100); + + if(function_exists('imagefilter')) + imagefilter($image,IMG_FILTER_GAUSSIAN_BLUR); + + for($i=0;$i<$length;$i++) + { + $color=imagecolorallocate($image,rand(150,220),rand(150,220),rand(150,220)); + imagettftext($image,rand($fontWidth-10,$fontWidth),rand(-30, 30),$padding+$i*$fontWidth,rand($fontHeight-15,$fontHeight-10),$color,$font,$token[$i]); + imagecolordeallocate($image,$color); + } + + imagepng($image); + imagedestroy($image); +} + +function addBackground($image,$width,$height,$opaque,$noisy,$hasGrid,$hasScribble,$morph) +{ + $background=imagecreatetruecolor($width*2,$height*2); + $white=imagecolorallocate($background,255,255,255); + imagefill($background,0,0,$white); + + if($opaque) + imagefill($background,0,0,imagecolorallocate($background,100,100,100)); + + if($noisy) + addNoise($background,$width*2,$height*2); + + if($hasGrid) + addGrid($background,$width*2,$height*2); + + if($hasScribble) + addScribble($background,$width*2,$height*2); + + if($morph) + morphImage($background,$width*2,$height*2); + + imagecopy($image,$background,0,0,30,30,$width,$height); + + if(!$opaque) + imagecolortransparent($image,$white); +} + +function addNoise($image,$width,$height) +{ for($x=0;$x<$width;++$x) { for($y=0;$y<$height;++$y) { - $vred+=rand(-2,2); - $vgreen+=rand(-2,2); - $vblue+=rand(-2,2); - if($vred<0) $vred=0; if($vred>150) $vred=75; - if($vgreen<0) $vgreen=0; if($vgreen>150) $vgreen=75; - if($vblue<0) $vblue=0; if($vblue>150) $vblue=75; - $col = imagecolorallocate($image, $vred, $vgreen, $vblue); - imagesetpixel($image, $x, $y, $col); - imagecolordeallocate($image, $col); + if(rand(0,100)<25) + { + $color=imagecolorallocate($image,rand(150,220),rand(150,220),rand(150,220)); + imagesetpixel($image,$x,$y,$color); + imagecolordeallocate($image,$color); + } } } +} - imagefilter($image,IMG_FILTER_GAUSSIAN_BLUR); - for($i=0;$i<$length;$i++) +function addGrid($image,$width,$height) +{ + for($i=0;$i<$width;$i+=rand(15,25)) { - $vred = rand(150, 240); - $vgreen = rand(150, 240); - $vblue = rand(150, 240); - $col = imagecolorallocate($image, $vred, $vgreen, $vblue); - $char = $token[$i]; - imagettftext($image, rand(40, 50), rand(-10, 20), 13 + (40 * $i), rand(50, imagesy($image) - 10), $col, $font, $char); - imagecolordeallocate($image, $col); - } - imagefilter($image,IMG_FILTER_GAUSSIAN_BLUR); + imagesetthickness($image,rand(2,6)); + $color=imagecolorallocate($image,rand(100,180),rand(100,180),rand(100,180)); + imageline($image,$i+rand(-10,20),0,$i+rand(-10,20),$height,$color); + imagecolordeallocate($image,$color); + } + for($i=0;$i<$height;$i+=rand(15,25)) + { + imagesetthickness($image,rand(2,6)); + $color=imagecolorallocate($image,rand(100,180),rand(100,180),rand(100,180)); + imageline($image,0,$i+rand(-10,20),$width,$i+rand(-10,20),$color); + imagecolordeallocate($image,$color); + } +} - imagepng($image); - imagedestroy($image); +function addScribble($image,$width,$height) +{ + for($i=0;$i<8;$i++) + { + $color=imagecolorallocate($image,rand(100,180),rand(100,180),rand(100,180)); + $points=array(); + for($j=1;$j=$height) $y=$height-5; + if($y<0) $y=5; + imagecopy($tempImage,$image,$x,0,$x,$y,$chunk,$height); + } + for($x=$y=0;$y<$height;$y+=$chunk) + { + $chunk=rand(1,5); + $x+=rand(-1,1); + if($x>=$width) $x=$width-5; + if($x<0) $x=5; + imagecopy($image,$tempImage,$x,$y,0,$y,$width,$chunk); + } } ?> \ No newline at end of file -- cgit v1.2.3