Merge pull request #208 from Olaf1989/symfony-5

Fully support Symfony 4 and 5
This commit is contained in:
Grégoire Passault 2020-01-08 16:31:01 +01:00 committed by GitHub
commit a22ba77f52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 359 additions and 341 deletions

View File

@ -1,32 +1,37 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Controller;
use Gregwar\CaptchaBundle\Generator\CaptchaGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Generates a captcha via a URL
* Generates a captcha via a URL.
*
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
*/
class CaptchaController extends AbstractController
{
/**
* Action that is used to generate the captcha, save its code, and stream the image
*
* @param string $key
*
* @return Response
*
* @throws NotFoundHttpException
*/
public function generateCaptchaAction($key)
/** @var CaptchaGenerator */
private $captchaGenerator;
/** @var array */
private $config;
public function __construct(CaptchaGenerator $captchaGenerator, array $config)
{
$options = $this->container->getParameter('gregwar_captcha.config');
$session = $this->get('session');
$whitelistKey = $options['whitelist_key'];
$this->captchaGenerator = $captchaGenerator;
$this->config = $config;
}
public function generateCaptchaAction(Request $request, string $key): Response
{
$session = $request->getSession();
$whitelistKey = $this->config['whitelist_key'];
$isOk = false;
if ($session->has($whitelistKey)) {
@ -37,21 +42,18 @@ class CaptchaController extends AbstractController
}
if (!$isOk) {
return $this->error($options);
return $this->error($this->config);
}
/* @var \Gregwar\CaptchaBundle\Generator\CaptchaGenerator $generator */
$generator = $this->container->get('gregwar_captcha.generator');
$persistedOptions = $session->get($key, array());
$options = array_merge($options, $persistedOptions);
$options = array_merge($this->config, $persistedOptions);
$phrase = $generator->getPhrase($options);
$generator->setPhrase($phrase);
$phrase = $this->captchaGenerator->getPhrase($options);
$this->captchaGenerator->setPhrase($phrase);
$persistedOptions['phrase'] = $phrase;
$session->set($key, $persistedOptions);
$response = new Response($generator->generate($options));
$response = new Response($this->captchaGenerator->generate($options));
$response->headers->set('Content-type', 'image/jpeg');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Cache-Control', 'no-cache');
@ -59,20 +61,11 @@ class CaptchaController extends AbstractController
return $response;
}
/**
* Returns an empty image with status code 428 Precondition Required
*
* @param array $options
*
* @return Response
*/
protected function error($options)
private function error(array $options): Response
{
/* @var \Gregwar\CaptchaBundle\Generator\CaptchaGenerator $generator */
$generator = $this->container->get('gregwar_captcha.generator');
$generator->setPhrase('');
$this->captchaGenerator->setPhrase('');
$response = new Response($generator->generate($options));
$response = new Response($this->captchaGenerator->generate($options));
$response->setStatusCode(428);
$response->headers->set('Content-type', 'image/jpeg');
$response->headers->set('Pragma', 'no-cache');

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
@ -7,21 +9,10 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
/**
* Generates the configuration tree.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('gregwar_captcha');
if (method_exists($treeBuilder, 'getRootNode')) {
$rootNode = $treeBuilder->getRootNode();
} else {
// BC for symfony/config <= 4.1
$rootNode = $treeBuilder->root('gregwar_captcha');
}
$rootNode = $treeBuilder->getRootNode();
$rootNode
->addDefaultsIfNotSet()
@ -36,7 +27,7 @@ class Configuration implements ConfigurationInterface
->scalarNode('as_url')->defaultValue(false)->end()
->scalarNode('reload')->defaultValue(false)->end()
->scalarNode('image_folder')->defaultValue('captcha')->end()
->scalarNode('web_path')->defaultValue('%kernel.root_dir%/../web')->end()
->scalarNode('web_path')->defaultValue('%kernel.project_dir%/public')->end()
->scalarNode('gc_freq')->defaultValue(100)->end()
->scalarNode('expiration')->defaultValue(60)->end()
->scalarNode('quality')->defaultValue(50)->end()

View File

@ -1,24 +1,29 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\DependencyInjection;
use Exception;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator;
/**
* Extension used to load the configuration, set parameters, and initialize the captcha view
* Extension used to load the configuration, set parameters, and initialize the captcha view.
*
* @author Gregwar <g.passault@gmail.com>
*/
class GregwarCaptchaExtension extends Extension
{
/**
* @param array $configs
* @param array $configs
* @param ContainerBuilder $container
*
* @throws Exception
*/
public function load(array $configs, ContainerBuilder $container)
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');

View File

@ -1,48 +1,40 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Generator;
use Gregwar\Captcha\CaptchaBuilder;
use Gregwar\Captcha\PhraseBuilder;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface;
use Gregwar\Captcha\CaptchaBuilderInterface;
use Gregwar\Captcha\PhraseBuilderInterface;
/**
* Uses configuration parameters to call the services that generate captcha images
* Uses configuration parameters to call the services that generate captcha images.
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
*/
class CaptchaGenerator
{
/**
* @var RouterInterface
*/
/** @var RouterInterface */
protected $router;
/**
* @var CaptchaBuilder
*/
/** @var CaptchaBuilder */
protected $builder;
/**
* @var PhraseBuilder
*/
/** @var PhraseBuilder */
protected $phraseBuilder;
/**
* @var ImageFileHandler
*/
/** @var ImageFileHandler */
protected $imageFileHandler;
/**
* @param RouterInterface $router
* @param RouterInterface $router
* @param CaptchaBuilderInterface $builder
* @param PhraseBuilderInterface $phraseBuilder
* @param ImageFileHandler $imageFileHandler
* @param PhraseBuilderInterface $phraseBuilder
* @param ImageFileHandler $imageFileHandler
*/
public function __construct(
RouterInterface $router,
@ -50,20 +42,13 @@ class CaptchaGenerator
PhraseBuilderInterface $phraseBuilder,
ImageFileHandler $imageFileHandler
) {
$this->router = $router;
$this->builder = $builder;
$this->phraseBuilder = $phraseBuilder;
$this->imageFileHandler = $imageFileHandler;
$this->router = $router;
$this->builder = $builder;
$this->phraseBuilder = $phraseBuilder;
$this->imageFileHandler = $imageFileHandler;
}
/**
* Get the captcha URL, stream, or filename that will go in the image's src attribute
*
* @param array $options
*
* @return array
*/
public function getCaptchaCode(array &$options)
public function getCaptchaCode(array &$options): string
{
$this->builder->setPhrase($this->getPhrase($options));
@ -76,27 +61,21 @@ class CaptchaGenerator
// Returns the image generation URL
if ($options['as_url']) {
return $this->router->generate('gregwar_captcha.generate_captcha',
array('key' => $options['session_key'], 'n' => md5(microtime(true).mt_rand())));
return $this->router->generate(
'gregwar_captcha.generate_captcha',
array('key' => $options['session_key'], 'n' => md5(microtime(true).mt_rand()))
);
}
return 'data:image/jpeg;base64,' . base64_encode($this->generate($options));
return 'data:image/jpeg;base64,'.base64_encode($this->generate($options));
}
/**
* Sets the phrase to the builder
*/
public function setPhrase($phrase)
public function setPhrase(string $phrase): void
{
$this->builder->setPhrase($phrase);
}
/**
* @param array $options
*
* @return string
*/
public function generate(array &$options)
public function generate(array &$options): string
{
$this->builder->setDistortion($options['distortion']);
@ -104,7 +83,7 @@ class CaptchaGenerator
$this->builder->setMaxBehindLines($options['max_behind_lines']);
if (isset($options['text_color']) && $options['text_color']) {
if (count($options['text_color']) !== 3) {
if (3 !== count($options['text_color'])) {
throw new \RuntimeException('text_color should be an array of r, g and b');
}
@ -113,7 +92,7 @@ class CaptchaGenerator
}
if (isset($options['background_color']) && $options['background_color']) {
if (count($options['background_color']) !== 3) {
if (3 !== count($options['background_color'])) {
throw new \RuntimeException('background_color should be an array of r, g and b');
}
@ -149,12 +128,7 @@ class CaptchaGenerator
return $this->imageFileHandler->saveAsFile($content);
}
/**
* @param array $options
*
* @return string
*/
public function getPhrase(array &$options)
public function getPhrase(array &$options): string
{
// Get the phrase that we'll use for this image
if ($options['keep_value'] && isset($options['phrase'])) {
@ -163,7 +137,7 @@ class CaptchaGenerator
$phrase = $this->phraseBuilder->build($options['length'], $options['charset']);
$options['phrase'] = $phrase;
}
return $phrase;
}
}

View File

@ -1,11 +1,13 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Generator;
use Symfony\Component\Finder\Finder;
/**
* Handles actions related to captcha image files including saving and garbage collection
* Handles actions related to captcha image files including saving and garbage collection.
*
* @author Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy@quizzle.com>
@ -13,69 +15,61 @@ use Symfony\Component\Finder\Finder;
class ImageFileHandler
{
/**
* Name of folder for captcha images
* Name of folder for captcha images.
*
* @var string
*/
protected $imageFolder;
/**
* Absolute path to public web folder
* Absolute path to public web folder.
*
* @var string
*/
protected $webPath;
/**
* Frequency of garbage collection in fractions of 1
* Frequency of garbage collection in fractions of 1.
*
* @var int
*/
protected $gcFreq;
/**
* Maximum age of images in minutes
* Maximum age of images in minutes.
*
* @var int
*/
protected $expiration;
/**
* @param $imageFolder
* @param $webPath
* @param $gcFreq
* @param $expiration
* @param string $imageFolder
* @param string $webPath
* @param string $gcFreq
* @param string $expiration
*/
public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
public function __construct(string $imageFolder, string $webPath, string $gcFreq, string $expiration)
{
$this->imageFolder = $imageFolder;
$this->webPath = $webPath;
$this->gcFreq = $gcFreq;
$this->expiration = $expiration;
$this->imageFolder = $imageFolder;
$this->webPath = $webPath;
$this->gcFreq = $gcFreq;
$this->expiration = $expiration;
}
/**
* Saves the provided image content as a file
*
* @param string $contents
*
* @return string
*/
public function saveAsFile($contents)
public function saveAsFile($contents): string
{
$this->createFolderIfMissing();
$filename = md5(uniqid()) . '.jpg';
$filePath = $this->webPath . '/' . $this->imageFolder . '/' . $filename;
$filename = md5(uniqid()).'.jpg';
$filePath = $this->webPath.'/'.$this->imageFolder.'/'.$filename;
imagejpeg($contents, $filePath, 15);
return '/' . $this->imageFolder . '/' . $filename;
return '/'.$this->imageFolder.'/'.$filename;
}
/**
* Randomly runs garbage collection on the image directory
*
* @return bool
*/
public function collectGarbage()
public function collectGarbage(): bool
{
if (!mt_rand(1, $this->gcFreq) == 1) {
if (1 == !mt_rand(1, $this->gcFreq)) {
return false;
}
@ -83,23 +77,20 @@ class ImageFileHandler
$finder = new Finder();
$criteria = sprintf('<= now - %s minutes', $this->expiration);
$finder->in($this->webPath . '/' . $this->imageFolder)
$finder->in($this->webPath.'/'.$this->imageFolder)
->date($criteria);
foreach($finder->files() as $file) {
foreach ($finder->files() as $file) {
unlink($file->getPathname());
}
return true;
}
/**
* Creates the folder if it doesn't exist
*/
protected function createFolderIfMissing()
protected function createFolderIfMissing(): void
{
if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
mkdir($this->webPath . '/' . $this->imageFolder, 0755);
if (!file_exists($this->webPath.'/'.$this->imageFolder)) {
mkdir($this->webPath.'/'.$this->imageFolder, 0755);
}
}
}

View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;

100
README.md
View File

@ -11,106 +11,45 @@ It uses [gregwar/captcha](https://github.com/Gregwar/Captcha) as captcha generat
Compatibility with Symfony
==========================
If you are using Symfony `< 2.8`, you should use version `1.*`
| CaptchaBundle | Symfony | PHP |
|:---------------:|:---------:|:-------:|
| 3.* | 4.* - 5.* | 7.1 > |
| 2.* | 2.8 - 3.* | 5.3.9 > |
| 1.* | 2.1 - 2.7 | 5.3.0 > |
If you are using Symfony `>= 2.8`, you should use version `2.*`
Installation
============
### Step 1: Download the GregwarCaptchaBundle
Ultimately, the GregwarCaptchaBundle files should be downloaded to the
'vendor/bundles/Gregwar/CaptchaBundle' directory.
You can accomplish this several ways, depending on your personal preference.
The first method is the standard Symfony method.
***Using Composer***
Use composer require to download and install the package.
Use composer require to download and install the package.
At the end of the installation you can automaticly create the configuration thanks to the Symfony recipe.
``` bash
composer require gregwar/captcha-bundle
```
***Using the vendors script***
Add the following lines to your `deps` file:
```
[GregwarCaptchaBundle]
git=http://github.com/Gregwar/CaptchaBundle.git
target=/bundles/Gregwar/CaptchaBundle
version=origin/2.0 <- add this if you are using Symfony 2.0
```
Now, run the vendors script to download the bundle:
``` bash
$ php bin/vendors install
```
***Using submodules***
If you prefer instead to use git submodules, then run the following:
``` bash
$ git submodule add git://github.com/Gregwar/CaptchaBundle.git vendor/bundles/Gregwar/CaptchaBundle
$ git submodule update --init
```
### Step 2: Configure the Autoloader
If you use composer, you can skip this step.
Now you will need to add the `Gregwar` namespace to your autoloader:
``` php
<?php
// app/autoload.php
$loader->registerNamespaces(array(
// ...
'Gregwar' => __DIR__.'/../vendor/bundles',
));
```
### Step 3: Enable the bundle
Finally, enable the bundle in the kernel:
```php
<?php
// app/appKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new Gregwar\CaptchaBundle\GregwarCaptchaBundle(),
);
}
```
Configuration
=============
Add the following configuration to your `app/config/config.yml`:
If you made the configuration automaticly trough the Symfony recipe, you should have the following config file `/config/packages/gregwar_captcha.yaml`
Otherwise you can create it manually `/config/packages/gregwar_captcha.yaml` with the following default configuration:
``` yaml
gregwar_captcha: ~
```
Usage
=====
You can use the "captcha" type in your forms this way:
```php
``` php
<?php
use Gregwar\CaptchaBundle\Type\CaptchaType;
// ...
$builder->add('captcha', CaptchaType::class); // That's all !
// If you're using php<5.5, you can use instead:
$builder->add('captcha', 'Gregwar\CaptchaBundle\Type\CaptchaType');
// ...
```
@ -123,7 +62,7 @@ Options
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")
* **web_path**: absolute path to public web folder (default='%kernel.project_dir%/public')
* **gc_freq**: frequency of garbage collection in fractions of 1 (default=100)
* **expiration**: maximum lifetime of captcha image files in minutes (default=60)
@ -155,7 +94,7 @@ number of lines depends on the size of the image). (default=null)
Example :
```php
``` php
<?php
use Gregwar\CaptchaBundle\Type\CaptchaType;
// ...
@ -168,11 +107,12 @@ Example :
You can also set these options for your whole application using the `gregwar_captcha`
configuration entry in your `config.yml` file:
``` yaml
gregwar_captcha:
width: 200
height: 50
length: 6
```
Translation
===========
@ -182,15 +122,17 @@ The messages are using the translator, you can either change the `invalid_messag
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:
``` yaml
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:
``` yaml
gregwar_captcha_routing:
resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml"
prefix: /_gcb
```
Since the session key is transported in the URL, it's also added in another session array, under the `whitelist_key` key, for security reasons
@ -201,7 +143,7 @@ The widget support the standard Symfony theming, see the [documentation](http://
The default rendering is:
```html
``` twig
{% block captcha_widget %}
{% spaceless %}
<img src="{{ captcha_code }}" title="captcha" width="{{ captcha_width }}" height="{{ captcha_height }}" />

View File

@ -1,4 +1,5 @@
parameters:
gregwar_captcha.controller.class: Gregwar\CaptchaBundle\Controller\CaptchaController
gregwar_captcha.captcha_type.class: Gregwar\CaptchaBundle\Type\CaptchaType
gregwar_captcha.captcha_generator.class: Gregwar\CaptchaBundle\Generator\CaptchaGenerator
gregwar_captcha.image_file_handler.class: Gregwar\CaptchaBundle\Generator\ImageFileHandler
@ -6,7 +7,15 @@ parameters:
gregwar_captcha.phrase_builder.class: Gregwar\Captcha\PhraseBuilder
services:
captcha.type:
gregwar_captcha.controller:
class: '%gregwar_captcha.controller.class%'
public: true
arguments:
- '@gregwar_captcha.generator'
- '%gregwar_captcha.config%'
# captcha.type:
gregwar_captcha.type:
class: '%gregwar_captcha.captcha_type.class%'
public: true
arguments:

View File

@ -1,22 +1,23 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Type;
use Symfony\Component\Form\Extension\Core\Type\TextType;
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\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Gregwar\CaptchaBundle\Validator\CaptchaValidator;
use Gregwar\CaptchaBundle\Generator\CaptchaGenerator;
/**
* Captcha type
* Captcha type.
*
* @author Gregwar <g.passault@gmail.com>
*/
@ -24,25 +25,16 @@ class CaptchaType extends AbstractType
{
const SESSION_KEY_PREFIX = '_captcha_';
/**
* @var SessionInterface
*/
/** @var SessionInterface */
protected $session;
/**
* @var CaptchaGenerator
*/
/** @var CaptchaGenerator */
protected $generator;
/**
* @var TranslatorInterface
*/
/** @var TranslatorInterface */
protected $translator;
/**
* Options
* @var array
*/
/** @var array */
private $options = array();
/**
@ -53,10 +45,10 @@ class CaptchaType extends AbstractType
*/
public function __construct(SessionInterface $session, CaptchaGenerator $generator, TranslatorInterface $translator, $options)
{
$this->session = $session;
$this->generator = $generator;
$this->translator = $translator;
$this->options = $options;
$this->session = $session;
$this->generator = $generator;
$this->translator = $translator;
$this->options = $options;
}
/**
@ -72,8 +64,8 @@ class CaptchaType extends AbstractType
$options['bypass_code'],
$options['humanity']
);
$event = \Symfony\Component\HttpKernel\Kernel::VERSION >= 2.3 ? FormEvents::POST_SUBMIT : FormEvents::POST_BIND;
$builder->addEventListener($event, array($validator, 'validate'));
$builder->addEventListener(FormEvents::POST_SUBMIT, array($validator, 'validate'));
}
/**
@ -86,7 +78,7 @@ class CaptchaType extends AbstractType
}
$sessionKey = sprintf('%s%s', self::SESSION_KEY_PREFIX, $options['session_key']);
$isHuman = false;
$isHuman = false;
if ($options['humanity'] > 0) {
$humanityKey = sprintf('%s_humanity', $sessionKey);
@ -105,18 +97,18 @@ class CaptchaType extends AbstractType
}
$view->vars = array_merge($view->vars, array(
'captcha_width' => $options['width'],
'captcha_height' => $options['height'],
'reload' => $options['reload'],
'image_id' => uniqid('captcha_'),
'captcha_code' => $this->generator->getCaptchaCode($options),
'value' => '',
'is_human' => $isHuman
'captcha_width' => $options['width'],
'captcha_height' => $options['height'],
'reload' => $options['reload'],
'image_id' => uniqid('captcha_'),
'captcha_code' => $this->generator->getCaptchaCode($options),
'value' => '',
'is_human' => $isHuman,
));
$persistOptions = array();
foreach (array('phrase', 'width', 'height', 'distortion', 'length',
'quality', 'background_color', 'background_images', 'text_color') as $key) {
'quality', 'background_color', 'background_images', 'text_color', ) as $key) {
$persistOptions[$key] = $options[$key];
}
@ -132,36 +124,17 @@ class CaptchaType extends AbstractType
$resolver->setDefaults($this->options);
}
/**
* {@inheritdoc}
* BC for SF < 2.7
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
public function getParent(): string
{
$this->configureOptions($resolver);
return TextType::class;
}
/**
* @return string
*/
public function getParent()
{
// Not using ::class to support Symfony 2.8 w/ php>=5.3.9
return 'Symfony\Component\Form\Extension\Core\Type\TextType';
}
/**
* @return string
*/
public function getName()
public function getName(): string
{
return $this->getBlockPrefix();
}
/**
* @return string
*/
public function getBlockPrefix()
public function getBlockPrefix(): string
{
return 'captcha';
}

View File

@ -1,73 +1,76 @@
<?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Validator;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Captcha validator
* Captcha validator.
*
* @author Gregwar <g.passault@gmail.com>
*/
class CaptchaValidator
{
/**
* @var SessionInterface
*/
/** @var SessionInterface */
private $session;
/**
* Session key to store the code
* Session key to store the code.
*
* @var string
*/
private $key;
/**
* Error message text for non-matching submissions
* Error message text for non-matching submissions.
*
* @var string
*/
private $invalidMessage;
/**
* Configuration parameter used to bypass a required code match
* Configuration parameter used to bypass a required code match.
*
* @var string
*/
private $bypassCode;
/**
* Number of form that the user can submit without captcha
* Number of form that the user can submit without captcha.
*
* @var int
*/
private $humanity;
/**
* Translator
* Translator.
*
* @var TranslatorInterface
*/
private $translator;
/**
* @param TranslatorInterface $translator
* @param SessionInterface $session
* @param string $key
* @param string $invalidMessage
* @param string $bypassCode
* @param int $humanity
*/
public function __construct(TranslatorInterface $translator, SessionInterface $session, $key, $invalidMessage, $bypassCode, $humanity)
{
$this->translator = $translator;
$this->session = $session;
$this->key = $key;
$this->invalidMessage = $invalidMessage;
$this->bypassCode = (string)$bypassCode;
$this->humanity = $humanity;
public function __construct(
TranslatorInterface $translator,
SessionInterface $session,
string $key,
string $invalidMessage,
?string $bypassCode,
int $humanity
) {
$this->translator = $translator;
$this->session = $session;
$this->key = $key;
$this->invalidMessage = $invalidMessage;
$this->bypassCode = $bypassCode;
$this->humanity = $humanity;
}
/**
* @param FormEvent $event
*/
public function validate(FormEvent $event)
public function validate(FormEvent $event): void
{
$form = $event->getForm();
@ -77,12 +80,13 @@ class CaptchaValidator
if ($this->humanity > 0) {
$humanity = $this->getHumanity();
if ($humanity > 0) {
$this->updateHumanity($humanity-1);
$this->updateHumanity($humanity - 1);
return;
}
}
if (!($code !== null && is_string($code) && ($this->compare($code, $expectedCode) || $this->compare($code, $this->bypassCode)))) {
if (!(null !== $code && is_string($code) && ($this->compare($code, $expectedCode) || $this->compare($code, $this->bypassCode)))) {
$form->addError(new FormError($this->translator->trans($this->invalidMessage, array(), 'validators')));
} else {
if ($this->humanity > 0) {
@ -92,13 +96,13 @@ class CaptchaValidator
$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
* Retrieve the expected CAPTCHA code.
*
* @return mixed|null
*/
@ -114,51 +118,39 @@ class CaptchaValidator
}
/**
* Retrieve the humanity
* Retrieve the humanity.
*
* @return mixed|null
*/
protected function getHumanity()
{
return $this->session->get($this->key . '_humanity', 0);
return $this->session->get($this->key.'_humanity', 0);
}
/**
* Updates the humanity
*/
protected function updateHumanity($newValue)
protected function updateHumanity(int $newValue): void
{
if ($newValue > 0) {
$this->session->set($this->key . '_humanity', $newValue);
$this->session->set($this->key.'_humanity', $newValue);
} else {
$this->session->remove($this->key . '_humanity');
$this->session->remove($this->key.'_humanity');
}
return null;
}
/**
* Process the codes
*
* @param $code
*
* @return string
*/
protected function niceize($code)
protected function niceize(string $code): string
{
return strtr(strtolower($code), 'oil', '01l');
}
/**
* Run a match comparison on the provided code and the expected code
* Run a match comparison on the provided code and the expected code.
*
* @param $code
* @param $expectedCode
* @param string $code
* @param string|null $expectedCode
*
* @return bool
*/
protected function compare($code, $expectedCode)
protected function compare($code, $expectedCode): bool
{
return ($expectedCode !== null && is_string($expectedCode) && $this->niceize($code) == $this->niceize($expectedCode));
return null !== $expectedCode && is_string($expectedCode) && $this->niceize($code) == $this->niceize($expectedCode);
}
}

View File

@ -17,15 +17,23 @@
}
],
"require": {
"php": ">=5.3.9",
"php": "^7.1.3",
"ext-gd": "*",
"gregwar/captcha": "~1.1",
"symfony/framework-bundle": "~3.0|~4.0|~5.0",
"symfony/form": "~3.0|~4.0|~5.0",
"twig/twig": "^1.40|^2.9|^3"
"symfony/form": "~4.0|~5.0",
"symfony/framework-bundle": "~4.0|~5.0",
"symfony/translation": "~4.0|^5.0",
"twig/twig": "^2.10|^3.0"
},
"autoload": {
"psr-4": {
"Gregwar\\CaptchaBundle\\": "/"
}
},
"config": {
"sort-packages": true
},
"require-dev": {
"symplify/easy-coding-standard": "^6.1"
}
}

138
ecs.yaml Normal file
View File

@ -0,0 +1,138 @@
parameters:
exclude_files:
- 'vendor/*'
- 'LICENSE'
- 'README.md'
services:
# PSR1
PhpCsFixer\Fixer\Basic\EncodingFixer: ~
PhpCsFixer\Fixer\PhpTag\FullOpeningTagFixer: ~
PhpCsFixer\Fixer\NamespaceNotation\BlankLineAfterNamespaceFixer: ~
PhpCsFixer\Fixer\ControlStructure\ElseifFixer: ~
PhpCsFixer\Fixer\FunctionNotation\FunctionDeclarationFixer: ~
PhpCsFixer\Fixer\Whitespace\IndentationTypeFixer: ~
PhpCsFixer\Fixer\Whitespace\LineEndingFixer: ~
PhpCsFixer\Fixer\Casing\ConstantCaseFixer: ~
PhpCsFixer\Fixer\Casing\LowercaseKeywordsFixer: ~
PhpCsFixer\Fixer\FunctionNotation\MethodArgumentSpaceFixer:
ensure_fully_multiline: true
PhpCsFixer\Fixer\ControlStructure\NoBreakCommentFixer: ~
PhpCsFixer\Fixer\PhpTag\NoClosingTagFixer: ~
PhpCsFixer\Fixer\FunctionNotation\NoSpacesAfterFunctionNameFixer: ~
PhpCsFixer\Fixer\Whitespace\NoSpacesInsideParenthesisFixer: ~
PhpCsFixer\Fixer\Whitespace\NoTrailingWhitespaceFixer: ~
PhpCsFixer\Fixer\Comment\NoTrailingWhitespaceInCommentFixer: ~
PhpCsFixer\Fixer\Whitespace\SingleBlankLineAtEofFixer: ~
PhpCsFixer\Fixer\ClassNotation\SingleClassElementPerStatementFixer:
elements:
- 'property'
PhpCsFixer\Fixer\Import\SingleImportPerStatementFixer: ~
PhpCsFixer\Fixer\Import\SingleLineAfterImportsFixer: ~
PhpCsFixer\Fixer\ControlStructure\SwitchCaseSemicolonToColonFixer: ~
PhpCsFixer\Fixer\ControlStructure\SwitchCaseSpaceFixer: ~
PhpCsFixer\Fixer\ClassNotation\VisibilityRequiredFixer: ~
PhpCsFixer\Fixer\Basic\BracesFixer:
allow_single_line_closure: true
PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer: ~
PhpCsFixer\Fixer\Operator\ConcatSpaceFixer:
spacing: none
PhpCsFixer\Fixer\Operator\NewWithBracesFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer:
tags:
- method
- param
- property
- return
- throws
- type
- var
PhpCsFixer\Fixer\Operator\BinaryOperatorSpacesFixer: ~
PhpCsFixer\Fixer\Operator\IncrementStyleFixer: ~
PhpCsFixer\Fixer\Operator\UnaryOperatorSpacesFixer: ~
PhpCsFixer\Fixer\Whitespace\BlankLineBeforeStatementFixer: ~
PhpCsFixer\Fixer\CastNotation\CastSpacesFixer: ~
PhpCsFixer\Fixer\LanguageConstruct\DeclareEqualNormalizeFixer: ~
PhpCsFixer\Fixer\FunctionNotation\FunctionTypehintSpaceFixer: ~
PhpCsFixer\Fixer\Comment\SingleLineCommentStyleFixer:
comment_types:
- hash
PhpCsFixer\Fixer\ControlStructure\IncludeFixer: ~
PhpCsFixer\Fixer\CastNotation\LowercaseCastFixer: ~
PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer:
elements:
- method
PhpCsFixer\Fixer\Casing\NativeFunctionCasingFixer: ~
PhpCsFixer\Fixer\ClassNotation\NoBlankLinesAfterClassOpeningFixer: ~
PhpCsFixer\Fixer\Phpdoc\NoBlankLinesAfterPhpdocFixer: ~
PhpCsFixer\Fixer\Comment\NoEmptyCommentFixer: ~
PhpCsFixer\Fixer\Phpdoc\NoEmptyPhpdocFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocSeparationFixer: ~
PhpCsFixer\Fixer\Semicolon\NoEmptyStatementFixer: ~
PhpCsFixer\Fixer\Whitespace\NoExtraBlankLinesFixer:
tokens:
- curly_brace_block
- extra
- parenthesis_brace_block
- square_brace_block
- throw
- use
PhpCsFixer\Fixer\NamespaceNotation\NoLeadingNamespaceWhitespaceFixer: ~
PhpCsFixer\Fixer\ArrayNotation\NoMultilineWhitespaceAroundDoubleArrowFixer: ~
PhpCsFixer\Fixer\CastNotation\NoShortBoolCastFixer: ~
PhpCsFixer\Fixer\Semicolon\NoSinglelineWhitespaceBeforeSemicolonsFixer: ~
PhpCsFixer\Fixer\Whitespace\NoSpacesAroundOffsetFixer: ~
PhpCsFixer\Fixer\ControlStructure\NoTrailingCommaInListCallFixer: ~
PhpCsFixer\Fixer\ArrayNotation\NoTrailingCommaInSinglelineArrayFixer: ~
PhpCsFixer\Fixer\ArrayNotation\TrailingCommaInMultilineArrayFixer: ~
PhpCsFixer\Fixer\ControlStructure\NoUnneededControlParenthesesFixer: ~
PhpCsFixer\Fixer\ArrayNotation\NoWhitespaceBeforeCommaInArrayFixer: ~
PhpCsFixer\Fixer\Whitespace\NoWhitespaceInBlankLineFixer: ~
PhpCsFixer\Fixer\ArrayNotation\NormalizeIndexBraceFixer: ~
PhpCsFixer\Fixer\Operator\ObjectOperatorWithoutWhitespaceFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocAnnotationWithoutDotFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocIndentFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocInlineTagFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocNoAccessFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocNoEmptyReturnFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocNoPackageFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocNoUselessInheritdocFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocReturnSelfReferenceFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocScalarFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocSingleLineVarSpacingFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocSummaryFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocToCommentFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocTrimFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocTypesFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocVarWithoutNameFixer: ~
PhpCsFixer\Fixer\FunctionNotation\ReturnTypeDeclarationFixer: ~
PhpCsFixer\Fixer\ClassNotation\SelfAccessorFixer: ~
PhpCsFixer\Fixer\CastNotation\ShortScalarCastFixer: ~
PhpCsFixer\Fixer\StringNotation\SingleQuoteFixer: ~
PhpCsFixer\Fixer\Semicolon\SpaceAfterSemicolonFixer: ~
PhpCsFixer\Fixer\Operator\StandardizeNotEqualsFixer: ~
PhpCsFixer\Fixer\Operator\TernaryOperatorSpacesFixer: ~
PhpCsFixer\Fixer\ArrayNotation\TrimArraySpacesFixer: ~
PhpCsFixer\Fixer\ArrayNotation\WhitespaceAfterCommaInArrayFixer: ~
PhpCsFixer\Fixer\ClassNotation\ClassDefinitionFixer:
singleLine: true
PhpCsFixer\Fixer\Casing\MagicConstantCasingFixer: ~
PhpCsFixer\Fixer\Alias\NoMixedEchoPrintFixer:
use: echo
PhpCsFixer\Fixer\Import\NoLeadingImportSlashFixer: ~
PhpCsFixer\Fixer\Import\NoUnusedImportsFixer: ~
PhpCsFixer\Fixer\PhpUnit\PhpUnitFqcnAnnotationFixer: ~
PhpCsFixer\Fixer\Phpdoc\PhpdocNoAliasTagFixer: ~
PhpCsFixer\Fixer\ClassNotation\ProtectedToPrivateFixer: ~
PhpCsFixer\Fixer\NamespaceNotation\SingleBlankLineBeforeNamespaceFixer: ~
# new since PHP-CS-Fixer 2.6
PhpCsFixer\Fixer\ControlStructure\NoUnneededCurlyBracesFixer: ~
PhpCsFixer\Fixer\ClassNotation\NoUnneededFinalMethodFixer: ~
PhpCsFixer\Fixer\Semicolon\SemicolonAfterInstructionFixer: ~
PhpCsFixer\Fixer\ControlStructure\YodaStyleFixer: ~
# new since 2.11
PhpCsFixer\Fixer\Operator\StandardizeIncrementFixer: ~