Compare commits

..

No commits in common. "master" and "v1.0.10" have entirely different histories.

27 changed files with 397 additions and 533 deletions

View File

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

View File

@ -1,7 +1,5 @@
<?php <?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\DependencyInjection; namespace Gregwar\CaptchaBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder;
@ -9,10 +7,15 @@ use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface class Configuration implements ConfigurationInterface
{ {
public function getConfigTreeBuilder(): TreeBuilder /**
* Generates the configuration tree.
*
* @return TreeBuilder
*/
public function getConfigTreeBuilder()
{ {
$treeBuilder = new TreeBuilder('gregwar_captcha'); $treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->getRootNode(); $rootNode = $treeBuilder->root('gregwar_captcha');
$rootNode $rootNode
->addDefaultsIfNotSet() ->addDefaultsIfNotSet()
@ -27,10 +30,10 @@ class Configuration implements ConfigurationInterface
->scalarNode('as_url')->defaultValue(false)->end() ->scalarNode('as_url')->defaultValue(false)->end()
->scalarNode('reload')->defaultValue(false)->end() ->scalarNode('reload')->defaultValue(false)->end()
->scalarNode('image_folder')->defaultValue('captcha')->end() ->scalarNode('image_folder')->defaultValue('captcha')->end()
->scalarNode('web_path')->defaultValue('%kernel.project_dir%/public')->end() ->scalarNode('web_path')->defaultValue('%kernel.root_dir%/../web')->end()
->scalarNode('gc_freq')->defaultValue(100)->end() ->scalarNode('gc_freq')->defaultValue(100)->end()
->scalarNode('expiration')->defaultValue(60)->end() ->scalarNode('expiration')->defaultValue(60)->end()
->scalarNode('quality')->defaultValue(50)->end() ->scalarNode('quality')->defaultValue(30)->end()
->scalarNode('invalid_message')->defaultValue('Bad code value')->end() ->scalarNode('invalid_message')->defaultValue('Bad code value')->end()
->scalarNode('bypass_code')->defaultValue(null)->end() ->scalarNode('bypass_code')->defaultValue(null)->end()
->scalarNode('whitelist_key')->defaultValue('captcha_whitelist_key')->end() ->scalarNode('whitelist_key')->defaultValue('captcha_whitelist_key')->end()
@ -41,13 +44,11 @@ class Configuration implements ConfigurationInterface
->scalarNode('interpolation')->defaultValue(true)->end() ->scalarNode('interpolation')->defaultValue(true)->end()
->arrayNode('text_color')->prototype('scalar')->end()->end() ->arrayNode('text_color')->prototype('scalar')->end()->end()
->arrayNode('background_color')->prototype('scalar')->end()->end() ->arrayNode('background_color')->prototype('scalar')->end()->end()
->arrayNode('background_images')->prototype('scalar')->end()->end()
->scalarNode('disabled')->defaultValue(false)->end() ->scalarNode('disabled')->defaultValue(false)->end()
->scalarNode('ignore_all_effects')->defaultValue(false)->end()
->scalarNode('session_key')->defaultValue('captcha')->end()
->end() ->end()
; ;
return $treeBuilder; return $treeBuilder;
} }
} }

View File

@ -1,17 +1,14 @@
<?php <?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\DependencyInjection; namespace Gregwar\CaptchaBundle\DependencyInjection;
use Exception;
use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\Config\FileLocator; 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> * @author Gregwar <g.passault@gmail.com>
*/ */
@ -19,11 +16,9 @@ class GregwarCaptchaExtension extends Extension
{ {
/** /**
* @param array $configs * @param array $configs
* @param ContainerBuilder $container * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
*
* @throws Exception
*/ */
public function load(array $configs, ContainerBuilder $container): void public function load(array $configs, ContainerBuilder $container)
{ {
$loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml'); $loader->load('services.yml');
@ -39,6 +34,6 @@ class GregwarCaptchaExtension extends Extension
$container->setParameter('gregwar_captcha.config.whitelist_key', $config['whitelist_key']); $container->setParameter('gregwar_captcha.config.whitelist_key', $config['whitelist_key']);
$resources = $container->getParameter('twig.form.resources'); $resources = $container->getParameter('twig.form.resources');
$container->setParameter('twig.form.resources', array_merge(array('@GregwarCaptcha/captcha.html.twig'), $resources)); $container->setParameter('twig.form.resources', array_merge(array('GregwarCaptchaBundle::captcha.html.twig'), $resources));
} }
} }

View File

@ -1,54 +1,64 @@
<?php <?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Generator; namespace Gregwar\CaptchaBundle\Generator;
use Gregwar\Captcha\CaptchaBuilder; use Symfony\Component\Finder\Finder;
use Gregwar\Captcha\PhraseBuilder; use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\RouterInterface;
use Gregwar\Captcha\CaptchaBuilderInterface; use Gregwar\Captcha\CaptchaBuilderInterface;
use Gregwar\Captcha\PhraseBuilderInterface; 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 Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy.j.livingston@gmail.com> * @author Jeremy Livingston <jeremy.j.livingston@gmail.com>
*/ */
class CaptchaGenerator class CaptchaGenerator
{ {
/** @var RouterInterface */ /**
* @var \Symfony\Component\Routing\RouterInterface
*/
protected $router; protected $router;
/** @var CaptchaBuilder */ /**
* @var CaptchaBuilder
*/
protected $builder; protected $builder;
/** @var PhraseBuilder */ /**
* @var PhraseBuilder
*/
protected $phraseBuilder; protected $phraseBuilder;
/** @var ImageFileHandler */ /**
* @var ImageFileHandler
*/
protected $imageFileHandler; protected $imageFileHandler;
/** /**
* @param RouterInterface $router * @param \Symfony\Component\Routing\RouterInterface $router
* @param CaptchaBuilderInterface $builder * @param CaptchaBuilderInterface $builder
* @param PhraseBuilderInterface $phraseBuilder * @param ImageFileHandlerInterface $imageFileHandler
* @param ImageFileHandler $imageFileHandler
*/ */
public function __construct( public function __construct(RouterInterface $router, CaptchaBuilderInterface $builder, PhraseBuilderInterface $phraseBuilder, ImageFileHandler $imageFileHandler)
RouterInterface $router, {
CaptchaBuilderInterface $builder,
PhraseBuilderInterface $phraseBuilder,
ImageFileHandler $imageFileHandler
) {
$this->router = $router; $this->router = $router;
$this->builder = $builder; $this->builder = $builder;
$this->phraseBuilder = $phraseBuilder; $this->phraseBuilder = $phraseBuilder;
$this->imageFileHandler = $imageFileHandler; $this->imageFileHandler = $imageFileHandler;
} }
public function getCaptchaCode(array &$options): string /**
* Get the captcha URL, stream, or filename that will go in the image's src attribute
*
* @param $key
* @param array $options
*
* @return array
*/
public function getCaptchaCode(array &$options)
{ {
$this->builder->setPhrase($this->getPhrase($options)); $this->builder->setPhrase($this->getPhrase($options));
@ -61,21 +71,27 @@ class CaptchaGenerator
// Returns the image generation URL // Returns the image generation URL
if ($options['as_url']) { if ($options['as_url']) {
return $this->router->generate( return $this->router->generate('gregwar_captcha.generate_captcha', array('key' => $options['session_key']));
'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));
} }
public function setPhrase(string $phrase): void /**
* Sets the phrase to the builder
*/
public function setPhrase($phrase)
{ {
$this->builder->setPhrase($phrase); $this->builder->setPhrase($phrase);
} }
public function generate(array &$options): string /**
* @param string $key
* @param array $options
*
* @return string
*/
public function generate(array &$options)
{ {
$this->builder->setDistortion($options['distortion']); $this->builder->setDistortion($options['distortion']);
@ -83,7 +99,7 @@ class CaptchaGenerator
$this->builder->setMaxBehindLines($options['max_behind_lines']); $this->builder->setMaxBehindLines($options['max_behind_lines']);
if (isset($options['text_color']) && $options['text_color']) { if (isset($options['text_color']) && $options['text_color']) {
if (3 !== count($options['text_color'])) { if (count($options['text_color']) !== 3) {
throw new \RuntimeException('text_color should be an array of r, g and b'); throw new \RuntimeException('text_color should be an array of r, g and b');
} }
@ -92,7 +108,7 @@ class CaptchaGenerator
} }
if (isset($options['background_color']) && $options['background_color']) { if (isset($options['background_color']) && $options['background_color']) {
if (3 !== count($options['background_color'])) { if (count($options['background_color']) !== 3) {
throw new \RuntimeException('background_color should be an array of r, g and b'); throw new \RuntimeException('background_color should be an array of r, g and b');
} }
@ -104,9 +120,6 @@ class CaptchaGenerator
$fingerprint = isset($options['fingerprint']) ? $options['fingerprint'] : null; $fingerprint = isset($options['fingerprint']) ? $options['fingerprint'] : null;
$this->builder->setBackgroundImages($options['background_images']);
$this->builder->setIgnoreAllEffects($options['ignore_all_effects']);
$content = $this->builder->build( $content = $this->builder->build(
$options['width'], $options['width'],
$options['height'], $options['height'],
@ -128,7 +141,13 @@ class CaptchaGenerator
return $this->imageFileHandler->saveAsFile($content); return $this->imageFileHandler->saveAsFile($content);
} }
public function getPhrase(array &$options): string /**
* @param string $key
* @param array $options
*
* @return string
*/
public function getPhrase(array &$options)
{ {
// Get the phrase that we'll use for this image // Get the phrase that we'll use for this image
if ($options['keep_value'] && isset($options['phrase'])) { if ($options['keep_value'] && isset($options['phrase'])) {
@ -141,3 +160,4 @@ class CaptchaGenerator
return $phrase; return $phrase;
} }
} }

View File

@ -1,13 +1,11 @@
<?php <?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Generator; namespace Gregwar\CaptchaBundle\Generator;
use Symfony\Component\Finder\Finder; 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 Gregwar <g.passault@gmail.com>
* @author Jeremy Livingston <jeremy@quizzle.com> * @author Jeremy Livingston <jeremy@quizzle.com>
@ -15,40 +13,36 @@ use Symfony\Component\Finder\Finder;
class ImageFileHandler class ImageFileHandler
{ {
/** /**
* Name of folder for captcha images. * Name of folder for captcha images
*
* @var string * @var string
*/ */
protected $imageFolder; protected $imageFolder;
/** /**
* Absolute path to public web folder. * Absolute path to public web folder
*
* @var string * @var string
*/ */
protected $webPath; protected $webPath;
/** /**
* Frequency of garbage collection in fractions of 1. * Frequency of garbage collection in fractions of 1
*
* @var int * @var int
*/ */
protected $gcFreq; protected $gcFreq;
/** /**
* Maximum age of images in minutes. * Maximum age of images in minutes
*
* @var int * @var int
*/ */
protected $expiration; protected $expiration;
/** /**
* @param string $imageFolder * @param $imageFolder
* @param string $webPath * @param $webPath
* @param string $gcFreq * @param $gcFreq
* @param string $expiration * @param $expiration
*/ */
public function __construct(string $imageFolder, string $webPath, string $gcFreq, string $expiration) public function __construct($imageFolder, $webPath, $gcFreq, $expiration)
{ {
$this->imageFolder = $imageFolder; $this->imageFolder = $imageFolder;
$this->webPath = $webPath; $this->webPath = $webPath;
@ -56,7 +50,14 @@ class ImageFileHandler
$this->expiration = $expiration; $this->expiration = $expiration;
} }
public function saveAsFile($contents): string /**
* Saves the provided image content as a file
*
* @param string $contents
*
* @return string
*/
public function saveAsFile($contents)
{ {
$this->createFolderIfMissing(); $this->createFolderIfMissing();
@ -67,9 +68,14 @@ class ImageFileHandler
return '/' . $this->imageFolder . '/' . $filename; return '/' . $this->imageFolder . '/' . $filename;
} }
public function collectGarbage(): bool /**
* Randomly runs garbage collection on the image directory
*
* @return bool
*/
public function collectGarbage()
{ {
if (1 == !mt_rand(1, $this->gcFreq)) { if (!mt_rand(1, $this->gcFreq) == 1) {
return false; return false;
} }
@ -87,10 +93,14 @@ class ImageFileHandler
return true; return true;
} }
protected function createFolderIfMissing(): void /**
* Creates the folder if it doesn't exist
*/
protected function createFolderIfMissing()
{ {
if (!file_exists($this->webPath . '/' . $this->imageFolder)) { if (!file_exists($this->webPath . '/' . $this->imageFolder)) {
mkdir($this->webPath . '/' . $this->imageFolder, 0755); mkdir($this->webPath . '/' . $this->imageFolder, 0755);
} }
} }
} }

View File

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

View File

@ -1,4 +1,4 @@
Copyright (c) <2011-2015> Grégoire Passault Copyright (c) <2011-2013> Grégoire Passault
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

130
README.md
View File

@ -1,54 +1,98 @@
Gregwar's CaptchaBundle Gregwar's CaptchaBundle
===================== =====================
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YUXRLWHQSWS6L) The `GregwarCaptchaBundle` adds support for a "captcha" form type for the
Symfony2 form component.
The `GregwarCaptchaBundle` adds support for a captcha form type for the
Symfony form component.
It uses [gregwar/captcha](https://github.com/Gregwar/Captcha) as captcha generator, which is a separate standalone library that can be used for none-symfony projects.
Compatibility with Symfony
==========================
| CaptchaBundle | Symfony | PHP |
|:---------------:|:---------:|:--------:|
| 2.1.* | 4.* - 5.* | >= 7.1 |
| 2.0.* | 2.8 - 3.* | >= 5.3.9 |
| 1.* | 2.1 - 2.7 | >= 5.3.0 |
Important note: the master of this repository is containing current development
in order to work with Symfony 2.1. If you are using Symfony 2.0 please checkout
the 2.0 branch.
Installation Installation
============ ============
### Step 1: Download the GregwarCaptchaBundle ### Step 1: Download the GregwarCaptchaBundle
Use composer require to download and install the package. Ultimately, the GregwarCaptchaBundle files should be downloaded to the
At the end of the installation, the bundle is automatically registered thanks to the Symfony recipe. 'vendor/bundles/Gregwar/CaptchaBundle' directory.
``` bash You can accomplish this several ways, depending on your personal preference.
composer require gregwar/captcha-bundle The first method is the standard Symfony2 method.
***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
``` ```
If you don't use flex, register it manually: 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
```
***Using Composer***
Add the following to the "require" section of your `composer.json` file:
```
"gregwar/captcha-bundle": "dev-master"
```
And update your dependencies
### 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
<?php <?php
// config/bundles.php // app/autoload.php
return [
$loader->registerNamespaces(array(
// ... // ...
Gregwar\CaptchaBundle\GregwarCaptchaBundle::class => ['all' => true] '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 Configuration
============= =============
If you need to customize the global bundle configuration, you can create a `/config/packages/gregwar_captcha.yaml` file with your configuration: Add the following configuration to your `app/config/config.yml`:
``` yaml
gregwar_captcha: gregwar_captcha: ~
width: 160
height: 50
```
Usage Usage
===== =====
@ -57,9 +101,8 @@ You can use the "captcha" type in your forms this way:
```php ```php
<?php <?php
use Gregwar\CaptchaBundle\Type\CaptchaType;
// ... // ...
$builder->add('captcha', CaptchaType::class); // That's all ! $builder->add('captcha', 'captcha'); // That's all !
// ... // ...
``` ```
@ -72,7 +115,7 @@ Options
You can define the following configuration options globally: 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") * **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.project_dir%/public') * **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) * **gc_freq**: frequency of garbage collection in fractions of 1 (default=100)
* **expiration**: maximum lifetime of captcha image files in minutes (default=60) * **expiration**: maximum lifetime of captcha image files in minutes (default=60)
@ -83,7 +126,7 @@ You can define the following configuration options globally or on the CaptchaTyp
* **disabled**: disable globally the CAPTCHAs (can be useful in dev environment), it will * **disabled**: disable globally the CAPTCHAs (can be useful in dev environment), it will
still appear but won't be editable and won't be checked still appear but won't be editable and won't be checked
* **length**: the length of the captcha (number of chars, default 5) * **length**: the length of the captcha (number of chars, default 5)
* **quality**: jpeg quality of captchas (default=30) * **quality**: jpeg quality of captchas (default=15)
* **charset**: the charset used for code generation (default=abcdefhjkmnprstuvwxyz23456789) * **charset**: the charset used for code generation (default=abcdefhjkmnprstuvwxyz23456789)
* **font**: the font to use (default is random among some pre-provided fonts), this should be an absolute path * **font**: the font to use (default is random among some pre-provided fonts), this should be an absolute path
* **keep_value**: the value will be the same until the form is posted, even if the page is refreshed (default=true) * **keep_value**: the value will be the same until the form is posted, even if the page is refreshed (default=true)
@ -98,18 +141,14 @@ You can define the following configuration options globally or on the CaptchaTyp
* **max_front_lines**, **max_behind_lines**: the maximum number of lines to draw on top/behind the image. `0` will draw no lines; `null` will use the default algorithm (the * **max_front_lines**, **max_behind_lines**: the maximum number of lines to draw on top/behind the image. `0` will draw no lines; `null` will use the default algorithm (the
number of lines depends on the size of the image). (default=null) number of lines depends on the size of the image). (default=null)
* **background_color**: sets the background color, if you want to force it, this should be an array of r,g &b, for instance [255, 255, 255] will force the background to be white * **background_color**: sets the background color, if you want to force it, this should be an array of r,g &b, for instance [255, 255, 255] will force the background to be white
* **background_images**: Sets custom user defined images as the captcha background (1 image is selected randomly). It is recommended to turn off all the effects on the image (ignore_all_effects). The full paths to the images must be passed.
* **interpolation**: enable or disable the interpolation on the captcha * **interpolation**: enable or disable the interpolation on the captcha
* **ignore_all_effects**: Recommended to use when setting background images, will disable all image effects.
* **session_key**, if you want to host multiple CAPTCHA on the same page, you might have different session keys to ensure proper storage of the clear phrase for those different forms
Example : Example :
```php ```php
<?php <?php
use Gregwar\CaptchaBundle\Type\CaptchaType;
// ... // ...
$builder->add('captcha', CaptchaType::class, array( $builder->add('captcha', 'captcha', array(
'width' => 200, 'width' => 200,
'height' => 50, 'height' => 50,
'length' => 6, 'length' => 6,
@ -118,12 +157,11 @@ $builder->add('captcha', CaptchaType::class, array(
You can also set these options for your whole application using the `gregwar_captcha` You can also set these options for your whole application using the `gregwar_captcha`
configuration entry in your `config.yml` file: configuration entry in your `config.yml` file:
``` yaml
gregwar_captcha: gregwar_captcha:
width: 200 width: 200
height: 50 height: 50
length: 6 length: 6
```
Translation Translation
=========== ===========
@ -132,20 +170,16 @@ The messages are using the translator, you can either change the `invalid_messag
As URL As URL
============ ============
To use a URL to generate a captcha image, you must add the bundle's routing configuration to your `config/routes.yaml` file: 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: gregwar_captcha_routing:
resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml" 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: 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: gregwar_captcha_routing:
resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml" resource: "@GregwarCaptchaBundle/Resources/config/routing/routing.yml"
prefix: /_gcb 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 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
@ -156,7 +190,7 @@ The widget support the standard Symfony theming, see the [documentation](http://
The default rendering is: The default rendering is:
``` twig ```html
{% block captcha_widget %} {% block captcha_widget %}
{% spaceless %} {% spaceless %}
<img src="{{ captcha_code }}" title="captcha" width="{{ captcha_width }}" height="{{ captcha_height }}" /> <img src="{{ captcha_code }}" title="captcha" width="{{ captcha_width }}" height="{{ captcha_height }}" />

View File

@ -1,3 +1,3 @@
gregwar_captcha.generate_captcha: gregwar_captcha.generate_captcha:
path: /generate-captcha/{key} pattern: /generate-captcha/{key}
defaults: { _controller: Gregwar\CaptchaBundle\Controller\CaptchaController::generateCaptchaAction } defaults: { _controller: GregwarCaptchaBundle:Captcha:generateCaptcha }

View File

@ -1,58 +1,32 @@
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
gregwar_captcha.captcha_builder.class: Gregwar\Captcha\CaptchaBuilder
gregwar_captcha.phrase_builder.class: Gregwar\Captcha\PhraseBuilder
services: services:
Gregwar\CaptchaBundle\Controller\CaptchaController: captcha.type:
public: true class: Gregwar\CaptchaBundle\Type\CaptchaType
alias: 'gregwar_captcha.controller'
gregwar_captcha.controller:
class: '%gregwar_captcha.controller.class%'
public: true
arguments: arguments:
- '@gregwar_captcha.generator' - @session
- '%gregwar_captcha.config%' - @gregwar_captcha.generator
autowire: true - @translator
- %gregwar_captcha.config%
# captcha.type:
gregwar_captcha.type:
class: '%gregwar_captcha.captcha_type.class%'
public: true
arguments:
- '@session'
- '@gregwar_captcha.generator'
- '@translator'
- '%gregwar_captcha.config%'
tags: tags:
- { name: form.type, alias: captcha } - { name: form.type, alias: captcha }
gregwar_captcha.generator: gregwar_captcha.generator:
class: '%gregwar_captcha.captcha_generator.class%' class: Gregwar\CaptchaBundle\Generator\CaptchaGenerator
public: true
arguments: arguments:
- '@router' - @router
- '@gregwar_captcha.captcha_builder' - @gregwar_captcha.captcha_builder
- '@gregwar_captcha.phrase_builder' - @gregwar_captcha.phrase_builder
- '@gregwar_captcha.image_file_handler' - @gregwar_captcha.image_file_handler
gregwar_captcha.image_file_handler: gregwar_captcha.image_file_handler:
class: '%gregwar_captcha.image_file_handler.class%' class: Gregwar\CaptchaBundle\Generator\ImageFileHandler
public: true
arguments: arguments:
- '%gregwar_captcha.config.image_folder%' - %gregwar_captcha.config.image_folder%
- '%gregwar_captcha.config.web_path%' - %gregwar_captcha.config.web_path%
- '%gregwar_captcha.config.gc_freq%' - %gregwar_captcha.config.gc_freq%
- '%gregwar_captcha.config.expiration%' - %gregwar_captcha.config.expiration%
gregwar_captcha.captcha_builder: gregwar_captcha.captcha_builder:
class: '%gregwar_captcha.captcha_builder.class%' class: Gregwar\Captcha\CaptchaBuilder
public: true
gregwar_captcha.phrase_builder: gregwar_captcha.phrase_builder:
class: '%gregwar_captcha.phrase_builder.class%' class: Gregwar\Captcha\PhraseBuilder
public: true

View File

@ -1 +0,0 @@
Renew: تجديد

View File

@ -1 +0,0 @@
Renew: Обнови

View File

@ -1 +0,0 @@
Renew: Obnovit

View File

@ -1 +0,0 @@
Renew: Przeładuj

View File

@ -1 +0,0 @@
Renew: Yenile

View File

@ -1 +0,0 @@
Renew: Оновити

View File

@ -1 +0,0 @@
Bad code value: الرمز غير متطابق

View File

@ -1 +0,0 @@
Bad code value: Грешен код

View File

@ -1 +0,0 @@
Bad code value: Špatný kontrolní kód

View File

@ -1 +0,0 @@
Bad code value: Kod jest niepoprawny

View File

@ -1 +0,0 @@
Bad code value: Kod eşleşmiyor

View File

@ -1 +0,0 @@
Bad code value: Невірний код

View File

@ -2,8 +2,8 @@
{% if is_human %} {% if is_human %}
- -
{% else %} {% else %}
{% apply spaceless %} {% spaceless %}
<img class="captcha_image" id="{{ image_id }}" src="{{ captcha_code }}" alt="" title="captcha" width="{{ captcha_width }}" height="{{ captcha_height }}" /> <img id="{{ image_id }}" src="{{ captcha_code }}" alt="" title="captcha" width="{{ captcha_width }}" height="{{ captcha_height }}" />
{% if reload %} {% if reload %}
<script type="text/javascript"> <script type="text/javascript">
function reload_{{ image_id }}() { function reload_{{ image_id }}() {
@ -14,6 +14,7 @@
<a class="captcha_reload" href="javascript:reload_{{ image_id }}();">{{ 'Renew'|trans({}, 'gregwar_captcha') }}</a> <a class="captcha_reload" href="javascript:reload_{{ image_id }}();">{{ 'Renew'|trans({}, 'gregwar_captcha') }}</a>
{% endif %} {% endif %}
{{ form_widget(form) }} {{ form_widget(form) }}
{% endapply %} {% endspaceless %}
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -1,46 +1,56 @@
<?php <?php
declare(strict_types=1);
namespace Gregwar\CaptchaBundle\Type; namespace Gregwar\CaptchaBundle\Type;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Contracts\Translation\TranslatorInterface; use Symfony\Component\Translation\TranslatorInterface;
use Gregwar\CaptchaBundle\Validator\CaptchaValidator; use Gregwar\CaptchaBundle\Validator\CaptchaValidator;
use Gregwar\CaptchaBundle\Generator\CaptchaGenerator; use Gregwar\CaptchaBundle\Generator\CaptchaGenerator;
/** /**
* Captcha type. * Captcha type
* *
* @author Gregwar <g.passault@gmail.com> * @author Gregwar <g.passault@gmail.com>
*/ */
class CaptchaType extends AbstractType class CaptchaType extends AbstractType
{ {
const SESSION_KEY_PREFIX = '_captcha_'; /**
* @var \Symfony\Component\HttpFoundation\Session\SessionInterface
/** @var SessionInterface */ */
protected $session; protected $session;
/** @var CaptchaGenerator */ /**
* The session key
* @var string
*/
protected $key = null;
/**
* @var \Gregwar\CaptchaBundle\Generator\CaptchaGenerator
*/
protected $generator; protected $generator;
/** @var TranslatorInterface */ /**
* @var TranslatorInterface
*/
protected $translator; protected $translator;
/** @var array */ /**
* Options
* @var array
*/
private $options = array(); private $options = array();
/** /**
* @param SessionInterface $session * @param \Symfony\Component\HttpFoundation\Session\SessionInterface $session
* @param CaptchaGenerator $generator * @param \Gregwar\CaptchaBundle\Generator\CaptchaGenerator $generator
* @param TranslatorInterface $translator
* @param array $options * @param array $options
*/ */
public function __construct(SessionInterface $session, CaptchaGenerator $generator, TranslatorInterface $translator, $options) public function __construct(SessionInterface $session, CaptchaGenerator $generator, TranslatorInterface $translator, $options)
@ -52,49 +62,53 @@ class CaptchaType extends AbstractType
} }
/** /**
* {@inheritdoc} * @param \Symfony\Component\Form\FormBuilderInterface $builder
* @param array $options
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$this->key = 'gcb_'.$builder->getForm()->getName();
$validator = new CaptchaValidator( $validator = new CaptchaValidator(
$this->translator, $this->translator,
$this->session, $this->session,
sprintf('%s%s', self::SESSION_KEY_PREFIX, $options['session_key']), $this->key,
$options['invalid_message'], $options['invalid_message'],
$options['bypass_code'], $options['bypass_code'],
$options['humanity'], $options['humanity']
$options['request']
); );
$builder->addEventListener(FormEvents::POST_SUBMIT, array($validator, 'validate')); $builder->addEventListener(FormEvents::POST_BIND, array($validator, 'validate'));
} }
/** /**
* {@inheritdoc} * @param \Symfony\Component\Form\FormView $view
* @param \Symfony\Component\Form\FormInterface $form
* @param array $options
*/ */
public function buildView(FormView $view, FormInterface $form, array $options) public function buildView(FormView $view, FormInterface $form, array $options)
{ {
$isHuman = false;
if ($options['reload'] && !$options['as_url']) { if ($options['reload'] && !$options['as_url']) {
throw new \InvalidArgumentException('GregwarCaptcha: The reload option cannot be set without as_url, see the README for more information'); throw new \InvalidArgumentException('GregwarCaptcha: The reload option cannot be set without as_url, see the README for more information');
} }
$sessionKey = sprintf('%s%s', self::SESSION_KEY_PREFIX, $options['session_key']);
$isHuman = false;
if ($options['humanity'] > 0) { if ($options['humanity'] > 0) {
$humanityKey = sprintf('%s_humanity', $sessionKey); $humanityKey = $this->key.'_humanity';
if ($this->session->get($humanityKey, 0) > 0) { if ($this->session->get($humanityKey, 0) > 0) {
$isHuman = true; $isHuman = true;
} }
} }
if ($options['as_url']) { if ($options['as_url']) {
$key = $this->key;
$keys = $this->session->get($options['whitelist_key'], array()); $keys = $this->session->get($options['whitelist_key'], array());
if (!in_array($sessionKey, $keys)) { if (!in_array($key, $keys)) {
$keys[] = $sessionKey; $keys[] = $key;
} }
$this->session->set($options['whitelist_key'], $keys); $this->session->set($options['whitelist_key'], $keys);
$options['session_key'] = $sessionKey; $options['session_key'] = $this->key;
} }
$view->vars = array_merge($view->vars, array( $view->vars = array_merge($view->vars, array(
@ -104,39 +118,38 @@ class CaptchaType extends AbstractType
'image_id' => uniqid('captcha_'), 'image_id' => uniqid('captcha_'),
'captcha_code' => $this->generator->getCaptchaCode($options), 'captcha_code' => $this->generator->getCaptchaCode($options),
'value' => '', 'value' => '',
'is_human' => $isHuman, 'is_human' => $isHuman
)); ));
$persistOptions = array(); $persistOptions = array();
foreach (array('phrase', 'width', 'height', 'distortion', 'length', foreach (array('phrase', 'width', 'height', 'distortion', 'length', 'quality') as $key) {
'quality', 'background_color', 'background_images', 'text_color', ) as $key) {
$persistOptions[$key] = $options[$key]; $persistOptions[$key] = $options[$key];
} }
$this->session->set($sessionKey, $persistOptions); $this->session->set($this->key, $persistOptions);
} }
/** /**
* {@inheritdoc} * @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver
*/ */
public function configureOptions(OptionsResolver $resolver) public function setDefaultOptions(OptionsResolverInterface $resolver)
{ {
$this->options['mapped'] = false; $this->options['mapped'] = false;
$this->options['request'] = null;
$resolver->setDefaults($this->options); $resolver->setDefaults($this->options);
} }
public function getParent(): string /**
* @return string
*/
public function getParent()
{ {
return TextType::class; return 'text';
} }
public function getName(): string /**
{ * @return string
return $this->getBlockPrefix(); */
} public function getName()
public function getBlockPrefix(): string
{ {
return 'captcha'; return 'captcha';
} }

View File

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

View File

@ -1,9 +1,9 @@
{ {
"name": "cadoles/captcha", "name": "gregwar/captcha-bundle",
"type": "symfony-bundle", "type": "captcha-bundle",
"description": "Captcha bundle", "description": "Captcha bundle",
"keywords": ["symfony2", "symfony", "captcha", "bot", "visual", "code", "security", "spam"], "keywords": ["symfony2", "captcha", "bot", "visual", "code", "security", "spam"],
"homepage": "https://github.com/Cadoles/CaptchaBundle", "homepage": "https://github.com/Gregwar/CaptchaBundle",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
@ -17,23 +17,13 @@
} }
], ],
"require": { "require": {
"php": ">=7.1.3", "php": ">=5.3.0",
"ext-gd": "*", "gregwar/captcha": "v1.0.10"
"gregwar/captcha": "^1.1.9",
"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": { "autoload": {
"psr-4": { "psr-0": {
"Gregwar\\CaptchaBundle\\": "/" "Gregwar\\CaptchaBundle": ""
} }
}, },
"config": { "target-dir": "Gregwar/CaptchaBundle"
"sort-packages": true
},
"require-dev": {
"symplify/easy-coding-standard": "^6.1"
}
} }

138
ecs.yaml
View File

@ -1,138 +0,0 @@
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: ~