diff --git a/Controller/CaptchaController.php b/Controller/CaptchaController.php new file mode 100644 index 0000000..0a1e35e --- /dev/null +++ b/Controller/CaptchaController.php @@ -0,0 +1,37 @@ + + */ +class CaptchaController extends Controller +{ + /** + * Action that is used to generate the captcha, save its code, and stream the image + * + * @param \Symfony\Component\HttpFoundation\Request $request + * @param string $key + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function generateCaptchaAction(Request $request, $key) + { + $options = $this->container->getParameter('gregwar_captcha.config'); + if (!$options['as_url'] || !in_array($key, $options['valid_keys'])) { + return $this->createNotFoundException('Unable to generate a captcha via a URL without the proper configuration.'); + } + + /* @var \Gregwar\CaptchaBundle\Generator\CaptchaGenerator $generator */ + $generator = $this->container->get('gregwar_captcha.generator'); + + return new Response($generator->generate($key, $options)); + } +} + diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 3b0da0d..4cc0b36 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -9,12 +9,13 @@ class Configuration implements ConfigurationInterface { /** * Generates the configuration tree. + * * @return TreeBuilder */ public function getConfigTreeBuilder() { $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('gregwar_captcha', 'array'); + $rootNode = $treeBuilder->root('gregwar_captcha'); $rootNode ->addDefaultsIfNotSet() @@ -26,6 +27,7 @@ class Configuration implements ConfigurationInterface ->scalarNode('keep_value')->defaultValue(true)->end() ->scalarNode('charset')->defaultValue('abcdefhjkmnprstuvwxyz23456789')->end() ->scalarNode('as_file')->defaultValue(false)->end() + ->scalarNode('as_url')->defaultValue(false)->end() ->scalarNode('image_folder')->defaultValue('captcha')->end() ->scalarNode('web_path')->defaultValue('%kernel.root_dir%/../web')->end() ->scalarNode('gc_freq')->defaultValue(100)->end() @@ -33,8 +35,10 @@ class Configuration implements ConfigurationInterface ->scalarNode('quality')->defaultValue(15)->end() ->scalarNode('invalid_message')->defaultValue('Bad code value')->end() ->scalarNode('bypass_code')->defaultValue(null)->end() + ->arrayNode('valid_keys')->defaultValue(array('captcha'))->prototype('scalar')->end() ->end() ; + return $treeBuilder; } } diff --git a/DependencyInjection/GregwarCaptchaExtension.php b/DependencyInjection/GregwarCaptchaExtension.php index cd4d0d3..7c49990 100755 --- a/DependencyInjection/GregwarCaptchaExtension.php +++ b/DependencyInjection/GregwarCaptchaExtension.php @@ -4,12 +4,20 @@ namespace Gregwar\CaptchaBundle\DependencyInjection; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\Config\FileLocator; +/** + * Extension used to load the configuration, set parameters, and initialize the captcha view + * + * @author Gregwar + */ class GregwarCaptchaExtension extends Extension { + /** + * @param array $configs + * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + */ public function load(array $configs, ContainerBuilder $container) { $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); @@ -19,6 +27,10 @@ class GregwarCaptchaExtension extends Extension $config = $this->processConfiguration($configuration, $configs); $container->setParameter('gregwar_captcha.config', $config); + $container->setParameter('gregwar_captcha.config.image_folder', $config['image_folder']); + $container->setParameter('gregwar_captcha.config.web_path', $config['web_path']); + $container->setParameter('gregwar_captcha.config.gc_freq', $config['gc_freq']); + $container->setParameter('gregwar_captcha.config.expiration', $config['expiration']); $resources = $container->getParameter('twig.form.resources'); $container->setParameter('twig.form.resources', array_merge(array('GregwarCaptchaBundle::captcha.html.twig'), $resources)); diff --git a/Generator/CaptchaGenerator.php b/Generator/CaptchaGenerator.php index 24e6c60..d009b17 100644 --- a/Generator/CaptchaGenerator.php +++ b/Generator/CaptchaGenerator.php @@ -3,159 +3,124 @@ namespace Gregwar\CaptchaBundle\Generator; use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Routing\RouterInterface; /** * Generates a CAPTCHA image */ -class CaptchaGenerator { +class CaptchaGenerator +{ + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var \Symfony\Component\Routing\RouterInterface + */ + protected $router; /** * Name of folder for captcha images * @var string */ - public $imageFolder; + protected $imageFolder; /** * Absolute path to public web folder * @var string */ - public $webPath; + protected $webPath; /** - * Frequence of garbage collection in fractions of 1 + * Frequency of garbage collection in fractions of 1 * @var int */ - public $gcFreq; - - /** - * Captcha Font - * @var string - */ - public $font; + protected $gcFreq; /** * Maximum age of images in minutes * @var int */ - public $expiration; + protected $expiration; /** - * Random fingerprint - * Useful to be able to regenerate exactly the same image - * @var array + * The fingerprint used to generate the image details across requests + * @var array|null */ - public $fingerprint; + protected $fingerprint; /** - * Should fingerprint be used ? - * @var boolean + * Whether this instance should use the fingerprint + * @var bool */ - public $use_fingerprint; + protected $useFingerprint; /** - * The captcha code - * @var string + * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session + * @param \Symfony\Component\Routing\RouterInterface $router + * @param string $imageFolder + * @param string $webPath + * @param int $gcFreq + * @param int $expiration */ - public $value; - - /** - * Captcha quality - * @var int - */ - public $quality; - - public function __construct($value, $imageFolder, $webPath, $gcFreq, $expiration, $font, $fingerprint, $quality) + public function __construct(SessionInterface $session, RouterInterface $router, $imageFolder, $webPath, $gcFreq, $expiration) { - $this->value = $value; - $this->imageFolder = $imageFolder; - $this->webPath = $webPath; - $this->gcFreq = intval($gcFreq); - $this->expiration = intval($expiration); - $this->font = $font; - $this->fingerprint = $fingerprint; - $this->use_fingerprint = (bool)$fingerprint; - $this->quality = intval($quality); + $this->session = $session; + $this->router = $router; + $this->imageFolder = $imageFolder; + $this->webPath = $webPath; + $this->gcFreq = $gcFreq; + $this->expiration = $expiration; } /** - * Get the captcha embeded code - */ - public function getCode($width = 120, $height = 40) - { - return 'data:image/jpeg;base64,'.base64_encode($this->generate($width, $height)); - } - - /** - * Creates a captcha image with provided dimensions - * and randomly executes a garbage collection + * Get the captcha URL, stream, or filename that will go in the image's src attribute * - * @param int $width - * @param int $height - * @return string Web path to the created image - */ - public function getFile($width = 120, $height = 40) - { - if (mt_rand(1, $this->gcFreq) == 1) { - $this->garbageCollection(); - } - - return $this->generate($width, $height, true); - } - - /** - * Returns a random number or the next number in the - * fingerprint - */ - public function rand($min, $max) - { - if (!is_array($this->fingerprint)) { - $this->fingerprint = array(); - } - - if ($this->use_fingerprint) { - $value = current($this->fingerprint); - next($this->fingerprint); - } else { - $value = mt_rand($min, $max); - $this->fingerprint[] = $value; - } - return $value; - } - - /** - * Get the CAPTCHA fingerprint - */ - public function getFingerprint() - { - return $this->fingerprint; - } - - /** - * Deletes all images in the configured folder - * that are older than 10 minutes + * @param $key + * @param array $options * - * @return void + * @return array */ - public function garbageCollection() + public function getCaptchaCode($key, array $options) { - $finder = new Finder(); - $criteria = sprintf('<= now - %s minutes', $this->expiration); - $finder->in($this->webPath . '/' . $this->imageFolder) - ->date($criteria); + // Randomly execute garbage collection and returns the image filename + if ($options['as_file']) { + if (mt_rand(1, $this->gcFreq) == 1) { + $this->garbageCollection(); + } - foreach($finder->files() as $file) - { - unlink($file->getPathname()); + return $this->generate($key, $options); } + + // Returns the configured URL for image generation + if ($options['as_url']) { + return $this->router->generate('gregwar_captcha.generate_captcha', array('key' => $key)); + } + + return 'data:image/jpeg;base64,' . base64_encode($this->generate($key, $options)); } /** * Generate the image */ - public function generate($width, $height, $createFile = false) + public function generate($key, array $options) { - $i = imagecreatetruecolor($width,$height); + $width = $options['width']; + $height = $options['height']; + if ($options['keep_value'] && $this->session->has($key . '_fingerprint')) { + $this->fingerprint = $this->session->get($key . '_fingerprint'); + $this->useFingerprint = true; + } else { + $this->fingerprint = null; + $this->useFingerprint = false; + } + + $captchaValue = $this->getCaptchaValue($key, $options['keep_value'], $options['charset'], $options['length']); + + $i = imagecreatetruecolor($width,$height); $col = imagecolorallocate($i, $this->rand(0,110), $this->rand(0,110), $this->rand(0,110)); imagefill($i, 0, 0, 0xFFFFFF); @@ -171,86 +136,143 @@ class CaptchaGenerator { } // Write CAPTCHA text - $size = $width/strlen($this->value); - $font = $this->font; - $box = imagettfbbox($size, 0, $font, $this->value); - $txt_width = $box[2] - $box[0]; - $txt_height = $box[1] - $box[7]; + $size = $width / strlen($captchaValue); + $font = $options['font']; + $box = imagettfbbox($size, 0, $font, $captchaValue); + $textWidth = $box[2] - $box[0]; + $textHeight = $box[1] - $box[7]; - imagettftext($i, $size, 0, ($width-$txt_width)/2, ($height-$txt_height)/2+$size, $col, $font, $this->value); + imagettftext($i, $size, 0, ($width - $textWidth) / 2, ($height - $textHeight) / 2 + $size, $col, $font, $captchaValue); // Distort the image - $X = $this->rand(0, $width); - $Y = $this->rand(0, $height); - $Phase=$this->rand(0,10); - $Scale = 1.3 + $this->rand(0,10000)/30000; - $Amp=1+$this->rand(0,1000)/1000; - $out = imagecreatetruecolor($width, $height); + $X = $this->rand(0, $width); + $Y = $this->rand(0, $height); + $phase = $this->rand(0, 10); + $scale = 1.3 + $this->rand(0, 10000) / 30000; + $out = imagecreatetruecolor($width, $height); - for ($x=0; $x<$width; $x++) - for ($y=0; $y<$height; $y++) { - $Vx=$x-$X; - $Vy=$y-$Y; - $Vn=sqrt($Vx*$Vx+$Vy*$Vy); + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $Vx = $x - $X; + $Vy = $y - $Y; + $Vn = sqrt($Vx * $Vx + $Vy * $Vy); - if ($Vn!=0) { - $Vn2=$Vn+4*sin($Vn/8); - $nX=$X+($Vx*$Vn2/$Vn); - $nY=$Y+($Vy*$Vn2/$Vn); + if ($Vn != 0) { + $Vn2 = $Vn + 4 * sin($Vn / 8); + $nX = $X + ($Vx * $Vn2 / $Vn); + $nY = $Y + ($Vy * $Vn2 / $Vn); } else { - $nX=$X; - $nY=$Y; + $nX = $X; + $nY = $Y; } - $nY = $nY+$Scale*sin($Phase + $nX*0.2); + $nY = $nY + $scale * sin($phase + $nX * 0.2); - $p = $this->bilinearInterpolate($nX-floor($nX), $nY-floor($nY), - $this->getCol($i,floor($nX),floor($nY)), - $this->getCol($i,ceil($nX),floor($nY)), - $this->getCol($i,floor($nX),ceil($nY)), - $this->getCol($i,ceil($nX),ceil($nY))); + $p = $this->bilinearInterpolate($nX - floor($nX), $nY - floor($nY), + $this->getCol($i, floor($nX), floor($nY)), + $this->getCol($i, ceil($nX), floor($nY)), + $this->getCol($i, floor($nX), ceil($nY)), + $this->getCol($i, ceil($nX), ceil($nY))); - if ($p==0) { - $p=0xFFFFFF; + if ($p == 0) { + $p = 0xFFFFFF; } imagesetpixel($out, $x, $y, $p); } + } + + if ($options['keep_value']) { + $this->session->set($key . '_fingerprint', $this->fingerprint); + } // Renders it - if (!$createFile) { + if (!$options['as_file']) { ob_start(); - imagejpeg($out, null, $this->quality); + imagejpeg($out, null, $options['quality']); + return ob_get_clean(); - } else { - // Check if folder exists and create it if not - if (!file_exists($this->webPath . '/' . $this->imageFolder)) { - mkdir($this->webPath . '/' . $this->imageFolder, 0755); - } - $filename = md5(uniqid()) . '.jpg'; - $filepath = $this->webPath . '/' . $this->imageFolder . '/' . $filename; - imagejpeg($out, $filepath, 15); - return '/' . $this->imageFolder . '/' . $filename; + } + + // Check if folder exists and create it if not + if (!file_exists($this->webPath . '/' . $this->imageFolder)) { + mkdir($this->webPath . '/' . $this->imageFolder, 0755); + } + + $filename = md5(uniqid()) . '.jpg'; + $filepath = $this->webPath . '/' . $this->imageFolder . '/' . $filename; + imagejpeg($out, $filepath, 15); + + return '/' . $this->imageFolder . '/' . $filename; + } + + /** + * Generate a new captcha value or pull the existing one from the session + * + * @param string $key + * @param bool $keepValue + * @param string $charset + * @param int $length + * + * @return mixed|string + */ + protected function getCaptchaValue($key, $keepValue, $charset, $length) + { + if ($keepValue && $this->session->has($key)) { + return $this->session->get($key); + } + + $value = ''; + $chars = str_split($charset); + + for ($i=0; $i < $length; $i++) { + $value .= $chars[array_rand($chars)]; + } + + $this->session->set($key, $value); + + return $value; + } + + /** + * Deletes all images in the configured folder + * that are older than the configured number of minutes + * + * @return void + */ + protected function garbageCollection() + { + $finder = new Finder(); + $criteria = sprintf('<= now - %s minutes', $this->expiration); + $finder->in($this->webPath . '/' . $this->imageFolder) + ->date($criteria); + + foreach($finder->files() as $file) { + unlink($file->getPathname()); } } - protected function getCol($image, $x, $y) + /** + * Returns a random number or the next number in the + * fingerprint + */ + protected function rand($min, $max) { - $L = imagesx($image); - $H = imagesy($image); - if ($x<0 || $x>=$L || $y<0 || $y>=$H) - return 0xFFFFFF; - else return imagecolorat($image, $x, $y); + if (!is_array($this->fingerprint)) { + $this->fingerprint = array(); + } + + if ($this->useFingerprint) { + $value = current($this->fingerprint); + next($this->fingerprint); + } else { + $value = mt_rand($min, $max); + $this->fingerprint[] = $value; + } + + return $value; } - protected function getRGB($col) { - return array( - (int)($col >> 16) & 0xff, - (int)($col >> 8) & 0xff, - (int)($col) & 0xff, - ); - } - - function bilinearInterpolate($x, $y, $nw, $ne, $sw, $se) + protected function bilinearInterpolate($x, $y, $nw, $ne, $sw, $se) { list($r0, $g0, $b0) = $this->getRGB($nw); list($r1, $g1, $b1) = $this->getRGB($ne); @@ -274,5 +296,24 @@ class CaptchaGenerator { return ($r << 16) | ($g << 8) | $b; } + + protected function getCol($image, $x, $y) + { + $L = imagesx($image); + $H = imagesy($image); + if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) { + return 0xFFFFFF; + } + + return imagecolorat($image, $x, $y); + } + + protected function getRGB($col) { + return array( + (int)($col >> 16) & 0xff, + (int)($col >> 8) & 0xff, + (int)($col) & 0xff, + ); + } } diff --git a/README.md b/README.md index bdbee91..01059bf 100644 --- a/README.md +++ b/README.md @@ -106,13 +106,20 @@ You can use the "captcha" type in your forms this way: // ... ``` -Note that the generated image will be embeded in the HTML document, to avoid dealing -with route and subrequests. +Note that the generated image will, by default, be embedded in the HTML document +to avoid dealing with route and subrequests. Options ======= -You can define the following type option : +You can define the following configuration options globally: + +* **image_folder**: name of folder for captcha images relative to public web folder in case **as_file** is set to true (default="captcha") +* **web_path**: absolute path to public web folder (default="%kernel.root_dir%/../web") +* **gc_freq**: frequency of garbage collection in fractions of 1 (default=100) +* **expiration**: maximum lifetime of captcha image files in minutes (default=60) + +You can define the following configuration options globally or on the CaptchaType itself: * **width**: the width of the captcha image (default=120) * **height**: the height of the captcha image (default=40) @@ -122,12 +129,10 @@ You can define the following type option : * **font**: the font to use (default=Generator/Font/captcha.ttf) * **keep_value**: the value will be the same until the form is posted, even if the page is refreshed (default=true) * **as_file**: if set to true an image file will be created instead of embedding to please IE6/7 (default=false) -* **image_folder**: name of folder for captcha images relative to public web folder in case **as_file** ist set to true (default="captcha") -* **web_path**: absolute path to public web folder (default="%kernel.root_dir%/../web") -* **gc_freq**: frequency of garbage collection in fractions of 1 (default=100) -* **expiration**: maximum lifetime of captcha image files in minutes (default=60) +* **as_url**: if set to true, a URL will be used in the image tag and will handle captcha generation. This can be used in a multiple-server environment and support IE6/7 (default=false) * **invalid_message**: error message displayed when an non-matching code is submitted (default="Bad code value") * **bypass_code**: code that will always validate the captcha (default=null) +* **valid_keys**: names that are able to be used for a captcha form type (default=[captcha]) Example : @@ -149,10 +154,28 @@ configuration entry in your `config.yml` file: height: 50 length: 6 -Form theming +As URL +============ +To use a URL to generate a captcha image, you must add the bundle's routing configuration to your app/routing.yml file: + + gregwar_captcha_routing: + resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml" + +This will use the bundle's route of "/generate-captcha/{key}" to handle the generation. If this route conflicts with an application route, you can prefix the bundle's routes when you import: + + gregwar_captcha_routing: + resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml" + prefix: /_gcb + +If you are using multiple captchas or assigning names other than the default "captcha", you will need to whitelist your captcha names in the "valid_keys" configuration: + + gregwar_captcha: + valid_keys: [registration_captcha, confirmation_captcha] + +Form Theming ============ -The widget support the standard symfony theming, see the [documentation](http://symfony.com/doc/current/book/forms.html#form-theming) for details on how to accomplish this. +The widget support the standard Symfony theming, see the [documentation](http://symfony.com/doc/current/book/forms.html#form-theming) for details on how to accomplish this. The default rendering is: diff --git a/Resources/config/routing/routing.yml b/Resources/config/routing/routing.yml new file mode 100644 index 0000000..e074029 --- /dev/null +++ b/Resources/config/routing/routing.yml @@ -0,0 +1,3 @@ +gregwar_captcha.generate_captcha: + pattern: /generate-captcha/{key} + defaults: { _controller: GregwarCaptchaBundle:Captcha:generateCaptcha } \ No newline at end of file diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 9f91571..dfb9fc0 100755 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -3,6 +3,19 @@ services: # captcha type captcha.type: class: Gregwar\CaptchaBundle\Type\CaptchaType - arguments: [ "@session", %gregwar_captcha.config% ] + arguments: + - @session + - @gregwar_captcha.generator + - %gregwar_captcha.config% tags: - { name: form.type, alias: captcha } + + gregwar_captcha.generator: + class: Gregwar\CaptchaBundle\Generator\CaptchaGenerator + arguments: + - @session + - @router + - %gregwar_captcha.config.image_folder% + - %gregwar_captcha.config.web_path% + - %gregwar_captcha.config.gc_freq% + - %gregwar_captcha.config.expiration% \ No newline at end of file diff --git a/Type/CaptchaType.php b/Type/CaptchaType.php index 92f7824..9c91951 100644 --- a/Type/CaptchaType.php +++ b/Type/CaptchaType.php @@ -2,20 +2,16 @@ namespace Gregwar\CaptchaBundle\Type; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\DependencyInjection\ContainerInterface; - +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\AbstractType; -use Symfony\Component\Form\Exception\FormException; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; -use Symfony\Component\Form\FormViewInterface; +use Symfony\Component\Form\FormEvents; use Gregwar\CaptchaBundle\Validator\CaptchaValidator; use Gregwar\CaptchaBundle\Generator\CaptchaGenerator; -use Gregwar\CaptchaBundle\DataTransformer\EmptyTransformer; /** * Captcha type @@ -24,6 +20,16 @@ use Gregwar\CaptchaBundle\DataTransformer\EmptyTransformer; */ class CaptchaType extends AbstractType { + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var \Gregwar\CaptchaBundle\Generator\CaptchaGenerator + */ + protected $generator; + /** * Options * @var array @@ -31,99 +37,70 @@ class CaptchaType extends AbstractType private $options = array(); /** - * Session key - * @var string + * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session + * @param \Gregwar\CaptchaBundle\Generator\CaptchaGenerator $generator + * @param array $options */ - private $key = 'captcha'; - - public function __construct(Session $session, $config) + public function __construct(SessionInterface $session, CaptchaGenerator $generator, $options) { - $this->session = $session; - $this->options = $config; + $this->session = $session; + $this->generator = $generator; + $this->options = $options; } + /** + * @param \Symfony\Component\Form\FormBuilderInterface $builder + * @param array $options + */ public function buildForm(FormBuilderInterface $builder, array $options) { - $this->key = $builder->getForm()->getName(); - - $builder->addValidator( - new CaptchaValidator($this->session, - $this->key, - $options['invalid_message'], - $options['bypass_code']) + $validator = new CaptchaValidator( + $this->session, + $builder->getForm()->getName(), + $options['invalid_message'], + $options['bypass_code'] ); + + $builder->addEventListener(FormEvents::POST_BIND, array($validator, 'validate')); } + /** + * @param \Symfony\Component\Form\FormView $view + * @param \Symfony\Component\Form\FormInterface $form + * @param array $options + */ public function buildView(FormView $view, FormInterface $form, array $options) { - $fingerprint = null; - - if ($options['keep_value'] && $this->session->has($this->key.'_fingerprint')) { - $fingerprint = $this->session->get($this->key.'_fingerprint'); - } - - $generator = new CaptchaGenerator($this->generateCaptchaValue(), - $options['image_folder'], - $options['web_path'], - $options['gc_freq'], - $options['expiration'], - $options['font'], - $fingerprint, - $options['quality']); - - if ($options['as_file']) { - $captchaCode = $generator->getFile($options['width'], $options['height']); - } else { - $captchaCode = $generator->getCode($options['width'], $options['height']); - } - - if ($options['keep_value']) { - $this->session->set($this->key.'_fingerprint', $generator->getFingerprint()); - } - - $fieldVars = array( + $view->vars = array_merge($view->vars, array( 'captcha_width' => $options['width'], 'captcha_height' => $options['height'], - 'captcha_code' => $captchaCode, + 'captcha_code' => $this->generator->getCaptchaCode($form->getName(), $options), 'value' => '', - ); - - foreach($fieldVars as $name => $value){ - $view->set($name,$value); - } + )); } + /** + * @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver + */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $this->options['property_path'] = false; $resolver->setDefaults($this->options); } + /** + * @return string + */ public function getParent() { return 'text'; } + /** + * @return string + */ public function getName() { return 'captcha'; } - - private function generateCaptchaValue() - { - if (!$this->options['keep_value'] || !$this->session->has($this->key)) { - $value = ''; - $chars = str_split($this->options['charset']); - - for ($i=0; $i<$this->options['length']; $i++) { - $value.= $chars[array_rand($chars)]; - } - - $this->session->set($this->key, $value); - } else { - $value = $this->session->get($this->key); - } - - return $value; - } } \ No newline at end of file diff --git a/Validator/CaptchaValidator.php b/Validator/CaptchaValidator.php index d9a0fb1..dedaf33 100644 --- a/Validator/CaptchaValidator.php +++ b/Validator/CaptchaValidator.php @@ -2,20 +2,19 @@ namespace Gregwar\CaptchaBundle\Validator; -use Symfony\Component\Form\FormValidatorInterface; -use Symfony\Component\HttpFoundation\Session\Session; -use Symfony\Component\Form\FormInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; /** * Captcha validator * * @author Gregwar */ -class CaptchaValidator implements FormValidatorInterface +class CaptchaValidator { /** - * Session + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface */ private $session; @@ -34,54 +33,76 @@ class CaptchaValidator implements FormValidatorInterface */ private $bypassCode; - public function __construct(Session $session, $key, $invalidMessage, $bypassCode) + /** + * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session + * @param string $key + * @param string $invalidMessage + * @param string|null $bypassCode + */ + public function __construct(SessionInterface $session, $key, $invalidMessage, $bypassCode) { - $this->session = $session; - $this->key = $key; - $this->invalidMessage = $invalidMessage; - $this->bypassCode = $bypassCode; + $this->session = $session; + $this->key = $key; + $this->invalidMessage = $invalidMessage; + $this->bypassCode = $bypassCode; } - public function validate(FormInterface $form) + /** + * @param FormEvent $event + */ + public function validate(FormEvent $event) { + $form = $form = $event->getForm(); + $code = $form->getData(); $expectedCode = $this->getExpectedCode(); - if (!($code && is_string($code) - && ($this->compare($code, $expectedCode) || $this->compare($code, $this->bypassCode)))) { + if (!($code && is_string($code) && ($this->compare($code, $expectedCode) || $this->compare($code, $this->bypassCode)))) { $form->addError(new FormError($this->invalidMessage)); } $this->session->remove($this->key); - if ($this->session->has($this->key.'_fingerprint')) { - $this->session->remove($this->key.'_fingerprint'); + if ($this->session->has($this->key . '_fingerprint')) { + $this->session->remove($this->key . '_fingerprint'); } } /** * Retrieve the expected CAPTCHA code + * + * @return mixed|null */ - private function getExpectedCode() + protected function getExpectedCode() { if ($this->session->has($this->key)) { return $this->session->get($this->key); } + return null; } /** * Process the codes + * + * @param $code + * + * @return string */ - private function niceize($code) + protected function niceize($code) { return strtr(strtolower($code), 'oil', '01l'); } /** * Run a match comparison on the provided code and the expected code + * + * @param $code + * @param $expectedCode + * + * @return bool */ - private function compare($code, $expectedCode) + protected function compare($code, $expectedCode) { return ($expectedCode && is_string($expectedCode) && $this->niceize($code) == $this->niceize($expectedCode)); }