From b757c3dc6d42da845b43569a35f76be50d11e867 Mon Sep 17 00:00:00 2001 From: Simon Erkelens Date: Mon, 10 Oct 2016 18:44:28 +1300 Subject: [PATCH 1/8] SilverStripe 4 and Namespacing --- _config.php | 5 ++++- _config/spamprotection.yml | 4 ++-- code/EditableSpamProtectionField.php | 1 + code/SpamProtectorManager.php | 7 +++++-- code/extensions/CommentSpamProtection.php | 4 ++++ code/extensions/FormSpamProtectionExtension.php | 6 ++++++ code/interfaces/SpamProtector.php | 3 +++ composer.json | 9 ++++++--- 8 files changed, 31 insertions(+), 8 deletions(-) diff --git a/_config.php b/_config.php index febd562..07d962b 100644 --- a/_config.php +++ b/_config.php @@ -8,4 +8,7 @@ * * @package spamprotection */ -Deprecation::notification_version('1.1', 'spamprotection'); + +use SilverStripe\Dev\Deprecation; + +Deprecation::notification_version('3.0', 'spamprotection'); diff --git a/_config/spamprotection.yml b/_config/spamprotection.yml index 8c755d1..b48c4bb 100644 --- a/_config/spamprotection.yml +++ b/_config/spamprotection.yml @@ -1,6 +1,6 @@ --- name: spamprotection --- -Form: +SilverStripe\Forms\Form: extensions: - - FormSpamProtectionExtension \ No newline at end of file + - SilverStripe\Spamprotection\FormSpamProtectionExtension \ No newline at end of file diff --git a/code/EditableSpamProtectionField.php b/code/EditableSpamProtectionField.php index 8993f28..34a4b9f 100644 --- a/code/EditableSpamProtectionField.php +++ b/code/EditableSpamProtectionField.php @@ -1,4 +1,5 @@ Date: Mon, 28 Aug 2017 10:27:53 +1200 Subject: [PATCH 2/8] Update Travis configuration to be standalone, add codecov.io, update license year --- .travis.yml | 40 +++++++++++++++++++--------------------- README.md | 37 ++++++++++++++++++------------------- codecov.yml | 1 + license.md | 2 +- phpunit.xml.dist | 14 ++++++++++++++ 5 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 codecov.yml create mode 100644 phpunit.xml.dist diff --git a/.travis.yml b/.travis.yml index 198f057..c73141d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,33 +1,31 @@ -# See https://github.com/silverstripe/silverstripe-travis-support for setup details - -sudo: false - language: php -php: - - 5.3 - - 5.4 - - 5.5 - env: - - DB=MYSQL CORE_RELEASE=3.5 + global: + - COMPOSER_ROOT_VERSION="4.0.x-dev" matrix: include: - php: 5.6 - env: DB=MYSQL CORE_RELEASE=3 - - php: 5.6 - env: DB=MYSQL CORE_RELEASE=3.1 - - php: 5.6 - env: DB=PGSQL CORE_RELEASE=3.2 + env: DB=MYSQL PHPCS_TEST=1 PHPUNIT_TEST=1 + - php: 7.0 + env: DB=PGSQL PHPUNIT_TEST=1 - php: 7.1 - env: DB=MYSQL CORE_RELEASE=3.6 + env: DB=MYSQL PHPUNIT_COVERAGE_TEST=1 before_script: - - composer self-update || true - - git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support - - php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require silverstripe/userforms - - cd ~/builds/ss + - phpenv rehash + - phpenv config-rm xdebug.ini + + - composer install --prefer-dist + - composer require --prefer-dist --no-update silverstripe/framework:4.0.x-dev + - if [[ $DB == PGSQL ]]; then composer require --prefer-dist --no-update silverstripe/postgresql:2.0.x-dev; fi + - composer update script: - - vendor/bin/phpunit spamprotection/tests + - if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit tests/; fi + - if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi + - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=framework/phpcs.xml.dist src/ tests/ ; fi + +after_success: + - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi diff --git a/README.md b/README.md index 15baff2..1f418cd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # SpamProtection Module [![Build Status](https://secure.travis-ci.org/silverstripe/silverstripe-spamprotection.png?branch=master)](http://travis-ci.org/silverstripe/silverstripe-spamprotection) -![helpfulrobot](https://helpfulrobot.io/silverstripe/spamprotection/badge) - +[![Code Coverage](https://codecov.io/gh/silverstripe/silverstripe-spamprotection/branch/master/graph/badge.svg)](https://codecov.io/gh/silverstripe/silverstripe-spamprotection) ## Maintainer Contact @@ -18,9 +17,9 @@ SilverStripe 3.0.0 or greater ## Documentation -This module provides a generic, consistent API for adding spam protection to -your SilverStripe Forms. This does not provide any spam protection out of the -box, for that, you must also download one of the spam protection +This module provides a generic, consistent API for adding spam protection to +your SilverStripe Forms. This does not provide any spam protection out of the +box, for that, you must also download one of the spam protection implementations. Currently available options are: * [Mollom](https://github.com/silverstripe/silverstripe-mollom) @@ -33,8 +32,8 @@ implements the `SpamProtector` interface. More on that below. ## Configuring -After installing this module and a protector of your choice (i.e mollom) you'll -need to rebuild your database through `dev/build` and set the default protector +After installing this module and a protector of your choice (i.e mollom) you'll +need to rebuild your database through `dev/build` and set the default protector via SilverStripe's config system. This will update any Form instances that have spam protection hooks with that protector. @@ -47,18 +46,18 @@ spam protection hooks with that protector. default_spam_protector: MollomSpamProtector To add spam protection to your form instance call `enableSpamProtection`. - + // your existing form code $form = new Form( .. ); $form->enableSpamProtection(); -The logic to perform the actual spam validation is controlled by each of the -individual `SpamProtector` implementation since they each require a different +The logic to perform the actual spam validation is controlled by each of the +individual `SpamProtector` implementation since they each require a different implementation client side or server side. ### Options -`enableSpamProtection` takes a hash of optional configuration values. +`enableSpamProtection` takes a hash of optional configuration values. $form->enableSpamProtection(array( 'protector' => 'MathSpamProtector', @@ -67,14 +66,14 @@ implementation client side or server side. Options to configure are: -*`protector`* a class name string or class instance which implements -`SpamProtector`. Defaults to your +*`protector`* a class name string or class instance which implements +`SpamProtector`. Defaults to your `FormSpamProtectionExtension.default_spam_protector` value. *`name`* the form field name argument for the Captcha. Defaults to `Catcha`. *`title`* title of the Captcha form field. Defaults to `''` *`insertBefore`* name of existing field to insert the spam protection field prior to -*`mapping`* an array mapping of the Form fields to the standardized list of +*`mapping`* an array mapping of the Form fields to the standardized list of field names. The list of standardized fields to pass to the spam protector are: title @@ -90,7 +89,7 @@ field names. The list of standardized fields to pass to the spam protector are: ## Defining your own `SpamProtector` Any class that implements `SpamProtector` and the `getFormField()` method can -be set as the spam protector. The `getFormField()` method returns the +be set as the spam protector. The `getFormField()` method returns the `FormField` to be inserted into the `Form`. The `FormField` returned should be in charge of the validation process. @@ -110,14 +109,14 @@ in charge of the validation process. This module provides an EditableSpamProtectionField wrapper which you can add to your UserForm instances. After installing this module and running /dev/build -to rebuild the database, your Form Builder interface will have an option for -`Spam Protection Field`. The type of spam protection used will be based on your +to rebuild the database, your Form Builder interface will have an option for +`Spam Protection Field`. The type of spam protection used will be based on your currently selected SpamProtector instance. ## Releasing code with Spam Protection support -Spam protection is useful to provide but in some cases we do not want to require -the developer to use spam protection. In that case, modules can provide the +Spam protection is useful to provide but in some cases we do not want to require +the developer to use spam protection. In that case, modules can provide the following pattern $form = new Form(..); diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..69cb760 --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/license.md b/license.md index 9445c8e..8794670 100644 --- a/license.md +++ b/license.md @@ -1,4 +1,4 @@ -Copyright (c) 2016, SilverStripe Limited +Copyright (c) 2017, SilverStripe Limited All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..d6d5263 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + tests + + + + + src/. + + tests/ + + + + From c5f0827c2e5d78f04f60a9340d70155014721686 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Mon, 28 Aug 2017 10:53:32 +1200 Subject: [PATCH 3/8] Add namespaces, separate test classes, add PSR-4 autoloader, update PSR-2 compliance --- README.md | 2 +- _config/spamprotection.yml | 2 +- code/EditableSpamProtectionField.php | 35 ++++++++++---- .../CommentSpamProtection.php | 2 +- .../FormSpamProtectionExtension.php | 19 ++++---- code/{interfaces => }/SpamProtector.php | 3 +- code/SpamProtectorManager.php | 48 ------------------- composer.json | 8 +++- lang/de.yml | 2 +- lang/en.yml | 2 +- lang/eo.yml | 2 +- lang/es.yml | 2 +- lang/fa_IR.yml | 2 +- lang/hr.yml | 2 +- lang/ru.yml | 2 +- lang/sk.yml | 2 +- tests/EditableSpamProtectionFieldTest.php | 43 ++++++++++++----- tests/FormSpamProtectionExtensionTest.php | 48 ++++++++++++------- .../BarProtector.php | 23 +++++++++ .../BazProtector.php | 22 +++++++++ .../FooProtector.php | 22 +++++++++ ...Test_BarProtector.php => BarProtector.php} | 8 +++- ...Test_BazProtector.php => BazProtector.php} | 8 +++- ...Test_FooProtector.php => FooProtector.php} | 8 +++- ...nFieldTest_Protector.php => Protector.php} | 8 +++- 25 files changed, 214 insertions(+), 111 deletions(-) rename code/{extensions => Extension}/CommentSpamProtection.php (93%) rename code/{extensions => Extension}/FormSpamProtectionExtension.php (91%) rename code/{interfaces => }/SpamProtector.php (96%) delete mode 100644 code/SpamProtectorManager.php create mode 100644 tests/FormSpamProtectionExtensionTest/BarProtector.php create mode 100644 tests/FormSpamProtectionExtensionTest/BazProtector.php create mode 100644 tests/FormSpamProtectionExtensionTest/FooProtector.php rename tests/Stub/{FormSpamProtectionExtensionTest_BarProtector.php => BarProtector.php} (56%) rename tests/Stub/{FormSpamProtectionExtensionTest_BazProtector.php => BazProtector.php} (53%) rename tests/Stub/{FormSpamProtectionExtensionTest_FooProtector.php => FooProtector.php} (53%) rename tests/Stub/{EditableSpamProtectionFieldTest_Protector.php => Protector.php} (50%) diff --git a/README.md b/README.md index 1f418cd..98e9711 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ## Requirements -SilverStripe 3.0.0 or greater +SilverStripe 4.0 or greater ## Documentation diff --git a/_config/spamprotection.yml b/_config/spamprotection.yml index b48c4bb..5d34909 100644 --- a/_config/spamprotection.yml +++ b/_config/spamprotection.yml @@ -3,4 +3,4 @@ name: spamprotection --- SilverStripe\Forms\Form: extensions: - - SilverStripe\Spamprotection\FormSpamProtectionExtension \ No newline at end of file + - SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension diff --git a/code/EditableSpamProtectionField.php b/code/EditableSpamProtectionField.php index 34a4b9f..9322132 100644 --- a/code/EditableSpamProtectionField.php +++ b/code/EditableSpamProtectionField.php @@ -1,5 +1,18 @@ setTitle(_t('EditableSpamProtectionField.SPAMFIELDMAPPING', 'Spam Field Mapping')) + ->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping')) ->setName('SpamFieldMapping') ->setDescription(_t( - 'EditableSpamProtectionField.SPAMFIELDMAPPINGDESCRIPTION', + __CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION', 'Select the form fields that correspond to any relevant spam protection identifiers' )); // Generate field specific settings - $mappableFields = Config::inst()->get('FormSpamProtectionExtension', 'mappable_fields'); + $mappableFields = Config::inst()->get(FormSpamProtectionExtension::class, 'mappable_fields'); $mappableFieldsMerged = array_combine($mappableFields, $mappableFields); foreach ($this->getCandidateFields() as $otherField) { $mapSetting = "Map-{$otherField->Name}"; @@ -228,10 +245,10 @@ if (class_exists('EditableFormField')) { if ($foundError !== false) { // use error messaging already set from validate method - $form->addErrorMessage($this->Name, $foundError['message'], $foundError['messageType'], false); + $form->sessionMessage($foundError['message'], $foundError['messageType']); } else { // fallback to custom message set in CMS or default message if none set - $form->addErrorMessage($this->Name, $this->getErrorMessage()->HTML(), 'error', false); + $form->sessionError($this->getErrorMessage()->HTML()); } } } diff --git a/code/extensions/CommentSpamProtection.php b/code/Extension/CommentSpamProtection.php similarity index 93% rename from code/extensions/CommentSpamProtection.php rename to code/Extension/CommentSpamProtection.php index ca96702..f01e44c 100644 --- a/code/extensions/CommentSpamProtection.php +++ b/code/Extension/CommentSpamProtection.php @@ -1,6 +1,6 @@ get('FormSpamProtectionExtension', 'default_spam_protector'); + $protector = Config::inst()->get(self::class, 'default_spam_protector'); } if ($protector && class_exists($protector)) { @@ -85,12 +86,12 @@ class FormSpamProtectionExtension extends Extension */ public function enableSpamProtection($options = array()) { - + // captcha form field name (must be unique) if (isset($options['name'])) { $name = $options['name']; } else { - $name = Config::inst()->get('FormSpamProtectionExtension', 'field_name'); + $name = Config::inst()->get(self::class, 'field_name'); } // captcha field title @@ -111,7 +112,7 @@ class FormSpamProtectionExtension extends Extension // add the form field if ($field = $protector->getFormField($name, $title)) { $field->setForm($this->owner); - + // Add before field specified by insertBefore $inserted = false; if (!empty($options['insertBefore'])) { @@ -123,7 +124,7 @@ class FormSpamProtectionExtension extends Extension } } } - + return $this->owner; } } diff --git a/code/interfaces/SpamProtector.php b/code/SpamProtector.php similarity index 96% rename from code/interfaces/SpamProtector.php rename to code/SpamProtector.php index f9bd3b3..e4a36e0 100644 --- a/code/interfaces/SpamProtector.php +++ b/code/SpamProtector.php @@ -1,5 +1,6 @@ enableSpamProtection() for adding spamprotection' - ); - - return $form->enableSpamProtection(); - } -} diff --git a/composer.json b/composer.json index 393f809..75de91e 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,13 @@ "dev-master": "3.0.x-dev" } }, - "license": "BSD-3-Clause", + "autoload": { + "psr-4": { + "SilverStripe\\SpamProtection\\": "code/", + "SilverStripe\\SpamProtection\\Tests\\": "tests/" + } + }, + "license": "BSD-3-Clause", "minimum-stability": "dev", "prefer-stable": true } diff --git a/lang/de.yml b/lang/de.yml index b26ed56..06c7d4a 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -1,5 +1,5 @@ de: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: Spamschutzfelder SINGULARNAME: Spamschutzfeld SPAMFIELDMAPPING: 'Spamschutzfeld Zuordnung' diff --git a/lang/en.yml b/lang/en.yml index 03eef43..8d59053 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -1,5 +1,5 @@ en: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Spam Protection Fields' SINGULARNAME: 'Spam Protection Field' SPAMFIELDMAPPING: 'Spam Field Mapping' diff --git a/lang/eo.yml b/lang/eo.yml index c1e9828..e41380c 100644 --- a/lang/eo.yml +++ b/lang/eo.yml @@ -1,5 +1,5 @@ eo: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Spamprotektaj kampoj' SINGULARNAME: 'Spamprotekta kampo' SPAMFIELDMAPPING: 'Spamkampa mapigo' diff --git a/lang/es.yml b/lang/es.yml index cea540c..7b5258a 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -1,5 +1,5 @@ es: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Campos de protección de spam' SINGULARNAME: 'Campo de protección de spam' SPAMFIELDMAPPING: 'Mapeo del campo spam' diff --git a/lang/fa_IR.yml b/lang/fa_IR.yml index a555f8f..888ffb9 100644 --- a/lang/fa_IR.yml +++ b/lang/fa_IR.yml @@ -1,5 +1,5 @@ fa_IR: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'فیلدهای محافظت از هرزنوشته' SINGULARNAME: 'فیلد محافظت از هرزنوشته' SPAMFIELDMAPPING: 'نقشه‌برداری فیلد هرزنوشته' diff --git a/lang/hr.yml b/lang/hr.yml index 5c45fbb..7055d28 100644 --- a/lang/hr.yml +++ b/lang/hr.yml @@ -1,5 +1,5 @@ hr: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Polja Spam zaštite' SINGULARNAME: 'Polje Spam zaštite' SPAMFIELDMAPPING: 'Mapiranje polja Spama' diff --git a/lang/ru.yml b/lang/ru.yml index cf473f6..be73ebf 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -1,5 +1,5 @@ ru: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Поля защиты от спама' SINGULARNAME: 'Поле защиты от спама' SPAMFIELDMAPPING: 'Привязка полей для защиты от спама' diff --git a/lang/sk.yml b/lang/sk.yml index 5ddf352..7bfa04d 100644 --- a/lang/sk.yml +++ b/lang/sk.yml @@ -1,5 +1,5 @@ sk: - EditableSpamProtectionField: + SilverStripe\SpamProtection\EditableSpamProtectionField: PLURALNAME: 'Polia ochrany proti spamu' SINGULARNAME: 'Pole ochrany proti spamu' SPAMFIELDMAPPING: 'Mapovanie spamového poľa' diff --git a/tests/EditableSpamProtectionFieldTest.php b/tests/EditableSpamProtectionFieldTest.php index 5a5660f..2e35a06 100644 --- a/tests/EditableSpamProtectionFieldTest.php +++ b/tests/EditableSpamProtectionFieldTest.php @@ -1,11 +1,21 @@ markTestSkipped('"userforms" module not installed'); } - Config::inst()->update( - 'FormSpamProtectionExtension', + Config::modify()->set( + FormSpamProtectionExtension::class, 'default_spam_protector', - 'EditableSpamProtectionFieldTest_Protector' + Protector::class ); } @@ -33,7 +43,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest $formMock ->expects($this->never()) - ->method('addErrorMessage'); + ->method('sessionMessage'); $formFieldMock->validateField(array('MyField' => null), $formMock); } @@ -53,9 +63,13 @@ class EditableSpamProtectionFieldTest extends SapphireTest $formMock ->expects($this->once()) - ->method('addErrorMessage') - ->with($this->anything(), $this->stringContains('some field message'), $this->anything(), $this->anything()); - ; + ->method('sessionMessage') + ->with( + $this->anything(), + $this->stringContains('some field message'), + $this->anything(), + $this->anything() + ); $formFieldMock->validateField(array('MyField' => null), $formMock); } @@ -75,8 +89,13 @@ class EditableSpamProtectionFieldTest extends SapphireTest $formMock ->expects($this->once()) - ->method('addErrorMessage') - ->with($this->anything(), $this->stringContains('default error message'), $this->anything(), $this->anything()); + ->method('sessionMessage') + ->with( + $this->anything(), + $this->stringContains('default error message'), + $this->anything(), + $this->anything() + ); $formFieldMock->validateField(array('MyField' => null), $formMock); } @@ -102,7 +121,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest protected function getFormMock() { - $formMock = $this->getMockBuilder('Form', array('addErrorMessage')) + $formMock = $this->getMockBuilder(Form::class, array('sessionMessage')) ->disableOriginalConstructor() ->getMock(); $formMock diff --git a/tests/FormSpamProtectionExtensionTest.php b/tests/FormSpamProtectionExtensionTest.php index 80ff1a9..08e2c31 100644 --- a/tests/FormSpamProtectionExtensionTest.php +++ b/tests/FormSpamProtectionExtensionTest.php @@ -1,5 +1,18 @@ form = new Form($this, 'Form', new FieldList( + $this->form = new Form(new Controller, 'Form', new FieldList( new TextField('Title'), new TextField('Comment'), new TextField('URL') ), new FieldList()); + $this->form->disableSecurityToken(); } public function testEnableSpamProtection() { - Config::inst()->update( - 'FormSpamProtectionExtension', + Config::modify()->set( + FormSpamProtectionExtension::class, 'default_spam_protector', - 'FormSpamProtectionExtensionTest_FooProtector' + FooProtector::class ); $form = $this->form->enableSpamProtection(); @@ -40,7 +54,7 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testEnableSpamProtectionCustomProtector() { $form = $this->form->enableSpamProtection(array( - 'protector' => 'FormSpamProtectionExtensionTest_BarProtector' + 'protector' => BarProtector::class )); $this->assertEquals('Bar', $form->Fields()->fieldByName('Captcha')->Title()); @@ -49,7 +63,7 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testEnableSpamProtectionCustomTitle() { $form = $this->form->enableSpamProtection(array( - 'protector' => 'FormSpamProtectionExtensionTest_BarProtector', + 'protector' => BarProtector::class, 'title' => 'Baz', )); @@ -59,7 +73,7 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testCustomOptions() { $form = $this->form->enableSpamProtection(array( - 'protector' => 'FormSpamProtectionExtensionTest_BazProtector', + 'protector' => BazProtector::class, 'title' => 'Qux', 'name' => 'Borris' )); @@ -70,17 +84,19 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testConfigurableName() { $field_name = "test_configurable_name"; - Config::inst()->update( - 'FormSpamProtectionExtension', 'default_spam_protector', - 'FormSpamProtectionExtensionTest_FooProtector' + Config::modify()->set( + FormSpamProtectionExtension::class, + 'default_spam_protector', + FooProtector::class ); - Config::inst()->update( - 'FormSpamProtectionExtension', 'field_name', + Config::modify()->set( + FormSpamProtectionExtension::class, + 'field_name', $field_name ); $form = $this->form->enableSpamProtection(); // remove for subsequent tests - Config::inst()->remove('FormSpamProtectionExtension', 'field_name'); + Config::modify()->remove(FormSpamProtectionExtension::class, 'field_name'); // field should take up configured name $this->assertEquals('Foo', $form->Fields()->fieldByName($field_name)->Title()); } @@ -88,7 +104,7 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testInsertBefore() { $form = $this->form->enableSpamProtection(array( - 'protector' => 'FormSpamProtectionExtensionTest_FooProtector', + 'protector' => FooProtector::class, 'insertBefore' => 'URL' )); @@ -102,7 +118,7 @@ class FormSpamProtectionExtensionTest extends SapphireTest public function testInsertBeforeMissing() { $form = $this->form->enableSpamProtection(array( - 'protector' => 'FormSpamProtectionExtensionTest_FooProtector', + 'protector' => FooProtector::class, 'insertBefore' => 'NotAField' )); diff --git a/tests/FormSpamProtectionExtensionTest/BarProtector.php b/tests/FormSpamProtectionExtensionTest/BarProtector.php new file mode 100644 index 0000000..05fae5d --- /dev/null +++ b/tests/FormSpamProtectionExtensionTest/BarProtector.php @@ -0,0 +1,23 @@ + Date: Mon, 28 Aug 2017 10:57:30 +1200 Subject: [PATCH 4/8] Update usage examples in readme, minor fixes in travis configuration and gitattributes --- .gitattributes | 1 + .travis.yml | 2 +- README.md | 87 +++++++++++++++++++++++++++++------------------- phpunit.xml.dist | 2 +- 4 files changed, 55 insertions(+), 37 deletions(-) diff --git a/.gitattributes b/.gitattributes index 475f5f2..89eb187 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ /.gitignore export-ignore /.travis.yml export-ignore /.scrutinizer.yml export-ignore +/codecov.yml export-ignore diff --git a/.travis.yml b/.travis.yml index c73141d..eae8a3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ before_script: script: - if [[ $PHPUNIT_TEST ]]; then vendor/bin/phpunit tests/; fi - if [[ $PHPUNIT_COVERAGE_TEST ]]; then phpdbg -qrr vendor/bin/phpunit --coverage-clover=coverage.xml; fi - - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=framework/phpcs.xml.dist src/ tests/ ; fi + - if [[ $PHPCS_TEST ]]; then vendor/bin/phpcs --standard=framework/phpcs.xml.dist code/ tests/ ; fi after_success: - if [[ $PHPUNIT_COVERAGE_TEST ]]; then bash <(curl -s https://codecov.io/bash) -f coverage.xml; fi diff --git a/README.md b/README.md index 98e9711..bbb8047 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,21 @@ spam protection hooks with that protector. *mysite/_config/spamprotection.yml* - --- - name: spamprotection - --- - FormSpamProtectionExtension: - default_spam_protector: MollomSpamProtector +```yaml +--- +name: spamprotection +--- +SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension: + default_spam_protector: MollomSpamProtector +``` To add spam protection to your form instance call `enableSpamProtection`. - // your existing form code - $form = new Form( .. ); - $form->enableSpamProtection(); +```php +// your existing form code +$form = new Form(/* .. */); +$form->enableSpamProtection(); +``` The logic to perform the actual spam validation is controlled by each of the individual `SpamProtector` implementation since they each require a different @@ -59,10 +63,12 @@ implementation client side or server side. `enableSpamProtection` takes a hash of optional configuration values. - $form->enableSpamProtection(array( - 'protector' => 'MathSpamProtector', - 'name' => 'Captcha' - )); +```php +$form->enableSpamProtection(array( + 'protector' => MathSpamProtector::class, + 'name' => 'Captcha' +)); +``` Options to configure are: @@ -76,15 +82,17 @@ Options to configure are: *`mapping`* an array mapping of the Form fields to the standardized list of field names. The list of standardized fields to pass to the spam protector are: - title - body - contextUrl - contextTitle - authorName - authorMail - authorUrl - authorIp - authorId +``` +title +body +contextUrl +contextTitle +authorName +authorMail +authorUrl +authorIp +authorId +``` ## Defining your own `SpamProtector` @@ -93,22 +101,26 @@ be set as the spam protector. The `getFormField()` method returns the `FormField` to be inserted into the `Form`. The `FormField` returned should be in charge of the validation process. - hasExtension('FormSpamProtectionExtension')) { - $form->enableSpamProtection(); - } +$form = new Form(/* .. */); + +if ($form->hasExtension(FormSpamProtectionExtension::class)) { + $form->enableSpamProtection(); +} +``` diff --git a/phpunit.xml.dist b/phpunit.xml.dist index d6d5263..4cd5d19 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,7 +5,7 @@ - src/. + code/ tests/ From cef1e24f8652570692cf18d3b89fc98944d7ad71 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Mon, 28 Aug 2017 11:04:57 +1200 Subject: [PATCH 5/8] FIX Add versioned to dev requirements --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 75de91e..16bc2f1 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7", + "silverstripe/versioned": "^1@dev", "squizlabs/php_codesniffer": "^3.0" }, "extra": { From a1ea0df540bf77033f45419a8d29b5fc265bc64d Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Mon, 28 Aug 2017 11:14:54 +1200 Subject: [PATCH 6/8] Add upgrader mapping file --- .upgrade.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .upgrade.yml diff --git a/.upgrade.yml b/.upgrade.yml new file mode 100644 index 0000000..d0e8361 --- /dev/null +++ b/.upgrade.yml @@ -0,0 +1,11 @@ +mappings: + SpamProtector: SilverStripe\SpamProtection\SpamProtector + CommentSpamProtection: SilverStripe\SpamProtection\Extension\CommentSpamProtection + EditableSpamProtectionField: SilverStripe\SpamProtection\EditableSpamProtectionField + FormSpamProtectionExtension: SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension + EditableSpamProtectionFieldTest: SilverStripe\SpamProtection\Tests\EditableSpamProtectionFieldTest + EditableSpamProtectionFieldTest_Protector: SilverStripe\SpamProtection\Tests\Stub\Protector + FormSpamProtectionExtensionTest: SilverStripe\SpamProtection\Tests\FormSpamProtectionExtensionTest + FormSpamProtectionExtensionTest_BarProtector: SilverStripe\SpamProtection\Tests\Stub\BarProtector + FormSpamProtectionExtensionTest_BazProtector: SilverStripe\SpamProtection\Tests\Stub\BazProtector + FormSpamProtectionExtensionTest_FooProtector: SilverStripe\SpamProtection\Tests\Stub\FooProtector From 680411e2acff6aee4469431e6c5f246fd8f6f2a7 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Mon, 28 Aug 2017 13:44:53 +1200 Subject: [PATCH 7/8] FIX Missing syntax opening in readme, and tweaks for pre-emptive userforms compatibility --- README.md | 1 + code/EditableSpamProtectionField.php | 447 +++++++++--------- .../Extension/FormSpamProtectionExtension.php | 3 + tests/EditableSpamProtectionFieldTest.php | 32 +- 4 files changed, 239 insertions(+), 244 deletions(-) diff --git a/README.md b/README.md index bbb8047..394ac28 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ be set as the spam protector. The `getFormField()` method returns the `FormField` to be inserted into the `Form`. The `FormField` returned should be in charge of the validation process. +```php 'Text' + ); + + /** + * @var FormField + */ + protected $formField = null; + + public function getFormField() { - private static $singular_name = 'Spam Protection Field'; - - private static $plural_name = 'Spam Protection Fields'; - - private static $table_name = 'EditableSpamProtectionField'; - - /** - * Fields to include spam detection for - * - * @var array - * @config - */ - private static $check_fields = array( - EditableEmailField::class, - EditableTextField::class, - EditableNumericField::class - ); - - private static $db = array( - 'SpamFieldSettings' => 'Text' - ); - - /** - * @var FormField - */ - protected $formField = null; - - public function getFormField() - { - if ($this->formField) { - return $this->formField; - } - - // Get protector - $protector = FormSpamProtectionExtension::get_protector(); - if (!$protector) { - return false; - } - - // Extract saved field mappings and update this field. - $fieldMapping = array(); - foreach ($this->getCandidateFields() as $otherField) { - $mapSetting = "Map-{$otherField->Name}"; - $spamField = $this->spamMapValue($mapSetting); - $fieldMapping[$otherField->Name] = $spamField; - } - $protector->setFieldMapping($fieldMapping); - - // Generate field - return $protector->getFormField($this->Name, $this->Title, null); + if ($this->formField) { + return $this->formField; } - /** - * @param FormField $field - * @return self - */ - public function setFormField(FormField $field) - { - $this->formField = $field; - - return $this; + // Get protector + $protector = FormSpamProtectionExtension::get_protector(); + if (!$protector) { + return false; } - /** - * Gets the list of all candidate spam detectable fields on this field's form - * - * @return DataList - */ - protected function getCandidateFields() - { + // Extract saved field mappings and update this field. + $fieldMapping = array(); + foreach ($this->getCandidateFields() as $otherField) { + $mapSetting = "Map-{$otherField->Name}"; + $spamField = $this->spamMapValue($mapSetting); + $fieldMapping[$otherField->Name] = $spamField; + } + $protector->setFieldMapping($fieldMapping); - // Get list of all configured classes available for spam detection - $types = self::config()->check_fields; - $typesInherit = array(); - foreach ($types as $type) { - $subTypes = ClassInfo::subclassesFor($type); - $typesInherit = array_merge($typesInherit, $subTypes); - } + // Generate field + return $protector->getFormField($this->Name, $this->Title, null); + } - // Get all candidates of the above types - return $this - ->Parent() - ->Fields() - ->filter('ClassName', $typesInherit) - ->exclude('Title', ''); // Ignore this field and those without titles + /** + * @param FormField $field + * @return self + */ + public function setFormField(FormField $field) + { + $this->formField = $field; + + return $this; + } + + /** + * Gets the list of all candidate spam detectable fields on this field's form + * + * @return DataList + */ + protected function getCandidateFields() + { + + // Get list of all configured classes available for spam detection + $types = $this->config()->get('check_fields'); + $typesInherit = array(); + foreach ($types as $type) { + $subTypes = ClassInfo::subclassesFor($type); + $typesInherit = array_merge($typesInherit, $subTypes); } - /** - * This method is in place for userforms 2.x - * - * @deprecated 3.0 Please use {@link getCMSFields()} instead - */ - public function getFieldConfiguration() - { - return $this->getCMSFields(); + // Get all candidates of the above types + return $this + ->Parent() + ->Fields() + ->filter('ClassName', $typesInherit) + ->exclude('Title', ''); // Ignore this field and those without titles + } + + /** + * Write the spam field mapping values to a serialised DB field + * + * {@inheritDoc} + */ + public function onBeforeWrite() + { + $fieldMap = Convert::json2array($this->SpamFieldSettings); + if (empty($fieldMap)) { + $fieldMap = array(); } - /** - * Write the spam field mapping values to a serialised DB field - * - * {@inheritDoc} - */ - public function onBeforeWrite() - { - $fieldMap = Convert::json2array($this->SpamFieldSettings); - if (empty($fieldMap)) { - $fieldMap = array(); + foreach ($this->record as $key => $value) { + if (substr($key, 0, 8) === 'spammap-') { + $fieldMap[substr($key, 8)] = $value; } - - foreach ($this->record as $key => $value) { - if (substr($key, 0, 8) === 'spammap-') { - $fieldMap[substr($key, 8)] = $value; - } - } - $this->setField('SpamFieldSettings', Convert::raw2json($fieldMap)); - - return parent::onBeforeWrite(); } + $this->setField('SpamFieldSettings', Convert::raw2json($fieldMap)); - /** - * Used in userforms 3.x and above - * - * {@inheritDoc} - */ - public function getCMSFields() - { - /** @var FieldList $fields */ - $fields = parent::getCMSFields(); + return parent::onBeforeWrite(); + } - // Get protector - $protector = FormSpamProtectionExtension::get_protector(); - if (!$protector) { - return $fields; - } - - if ($this->Parent()->Fields() instanceof UnsavedRelationList) { - return $fields; - } - - // Each other text field in this group can be assigned a field mapping - $mapGroup = FieldGroup::create() - ->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping')) - ->setName('SpamFieldMapping') - ->setDescription(_t( - __CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION', - 'Select the form fields that correspond to any relevant spam protection identifiers' - )); - - // Generate field specific settings - $mappableFields = Config::inst()->get(FormSpamProtectionExtension::class, 'mappable_fields'); - $mappableFieldsMerged = array_combine($mappableFields, $mappableFields); - foreach ($this->getCandidateFields() as $otherField) { - $mapSetting = "Map-{$otherField->Name}"; - $fieldOption = DropdownField::create( - 'spammap-' . $mapSetting, - $otherField->Title, - $mappableFieldsMerged, - $this->spamMapValue($mapSetting) - )->setEmptyString(''); - $mapGroup->push($fieldOption); - } - $fields->addFieldToTab('Root.Main', $mapGroup); + /** + * Used in userforms 3.x and above + * + * {@inheritDoc} + */ + public function getCMSFields() + { + /** @var FieldList $fields */ + $fields = parent::getCMSFields(); + // Get protector + $protector = FormSpamProtectionExtension::get_protector(); + if (!$protector) { + var_dump('a'); return $fields; } - /** - * Try to retrieve a value for the given spam field map name from the serialised data - * - * @param string $mapSetting - * @return string - */ - public function spamMapValue($mapSetting) - { - $map = Convert::json2array($this->SpamFieldSettings); - if (empty($map)) { - $map = array(); - } - - if (array_key_exists($mapSetting, $map)) { - return $map[$mapSetting]; - } - return ''; + if ($this->Parent()->Fields() instanceof UnsavedRelationList) { + var_dump('b'); + return $fields; } - /** - * Using custom validateField method - * as Spam Protection Field implementations may have their own error messages - * and may not be based on the field being required, e.g. Honeypot Field - * - * @param array $data - * @param Form $form - * @return void - */ - public function validateField($data, $form) - { - $formField = $this->getFormField(); - $formField->setForm($form); + // Each other text field in this group can be assigned a field mapping + $mapGroup = FieldGroup::create() + ->setTitle(_t(__CLASS__.'.SPAMFIELDMAPPING', 'Spam Field Mapping')) + ->setName('SpamFieldMapping') + ->setDescription(_t( + __CLASS__.'.SPAMFIELDMAPPINGDESCRIPTION', + 'Select the form fields that correspond to any relevant spam protection identifiers' + )); - if (isset($data[$this->Name])) { - $formField->setValue($data[$this->Name]); - } + // Generate field specific settings + $mappableFields = FormSpamProtectionExtension::config()->get('mappable_fields'); + $mappableFieldsMerged = array_combine($mappableFields, $mappableFields); + foreach ($this->getCandidateFields() as $otherField) { + $mapSetting = "Map-{$otherField->Name}"; + $fieldOption = DropdownField::create( + 'spammap-' . $mapSetting, + $otherField->Title, + $mappableFieldsMerged, + $this->spamMapValue($mapSetting) + )->setEmptyString(''); + $mapGroup->push($fieldOption); + } + $fields->addFieldToTab('Root.Main', $mapGroup); - $validator = $form->getValidator(); - if (!$formField->validate($validator)) { - $errors = $validator->getErrors(); - $foundError = false; + return $fields; + } - // field validate implementation may not add error to validator - if (count($errors) > 0) { - // check if error already added from fields' validate method - foreach ($errors as $error) { - if ($error['fieldName'] == $this->Name) { - $foundError = $error; - break; - } + /** + * Try to retrieve a value for the given spam field map name from the serialised data + * + * @param string $mapSetting + * @return string + */ + public function spamMapValue($mapSetting) + { + $map = Convert::json2array($this->SpamFieldSettings); + if (empty($map)) { + $map = array(); + } + + if (array_key_exists($mapSetting, $map)) { + return $map[$mapSetting]; + } + return ''; + } + + /** + * Using custom validateField method + * as Spam Protection Field implementations may have their own error messages + * and may not be based on the field being required, e.g. Honeypot Field + * + * @param array $data + * @param Form $form + * @return void + */ + public function validateField($data, $form) + { + $formField = $this->getFormField(); + $formField->setForm($form); + + if (isset($data[$this->Name])) { + $formField->setValue($data[$this->Name]); + } + + $validator = $form->getValidator(); + if (!$formField->validate($validator)) { + $errors = $validator->getErrors(); + $foundError = false; + + // field validate implementation may not add error to validator + if (count($errors) > 0) { + // check if error already added from fields' validate method + foreach ($errors as $error) { + if ($error['fieldName'] == $this->Name) { + $foundError = $error; + break; } } + } - if ($foundError !== false) { - // use error messaging already set from validate method - $form->sessionMessage($foundError['message'], $foundError['messageType']); - } else { - // fallback to custom message set in CMS or default message if none set - $form->sessionError($this->getErrorMessage()->HTML()); - } + if ($foundError !== false) { + // use error messaging already set from validate method + $form->sessionMessage($foundError['message'], $foundError['messageType']); + } else { + // fallback to custom message set in CMS or default message if none set + $form->sessionError($this->getErrorMessage()->HTML()); } } + } - public function getFieldValidationOptions() - { - return new FieldList(); - } + public function getFieldValidationOptions() + { + return FieldList::create(); + } - public function getRequired() - { - return false; - } + public function getRequired() + { + return false; + } - public function getIcon() - { - return 'spamprotection/images/' . strtolower($this->class) . '.png'; - } + public function getIcon() + { + return 'spamprotection/images/' . strtolower($this->class) . '.png'; + } - public function showInReports() - { - return false; - } + public function showInReports() + { + return false; } } diff --git a/code/Extension/FormSpamProtectionExtension.php b/code/Extension/FormSpamProtectionExtension.php index ad26df3..a07b3e2 100644 --- a/code/Extension/FormSpamProtectionExtension.php +++ b/code/Extension/FormSpamProtectionExtension.php @@ -3,6 +3,7 @@ namespace SilverStripe\SpamProtection\Extension; use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Extension; use SilverStripe\Core\Injector\Injector; @@ -15,6 +16,8 @@ use SilverStripe\Core\Injector\Injector; class FormSpamProtectionExtension extends Extension { + use Configurable; + /** * @config * diff --git a/tests/EditableSpamProtectionFieldTest.php b/tests/EditableSpamProtectionFieldTest.php index 2e35a06..fdb7452 100644 --- a/tests/EditableSpamProtectionFieldTest.php +++ b/tests/EditableSpamProtectionFieldTest.php @@ -2,14 +2,16 @@ namespace SilverStripe\SpamProtection\Tests; -use UserDefinedForm; +use SilverStripe\UserForms\Model\UserDefinedForm; use SilverStripe\Core\Config\Config; use SilverStripe\Dev\SapphireTest; +use SilverStripe\Forms\FieldGroup; use SilverStripe\Forms\Form; use SilverStripe\Forms\RequiredFields; +use SilverStripe\Forms\TextField; use SilverStripe\SpamProtection\EditableSpamProtectionField; use SilverStripe\SpamProtection\Extension\FormSpamProtectionExtension; -use SilverStripe\SpamProtection\Tests\EditableSpamProtectionFieldTest\Protector; +use SilverStripe\SpamProtection\Tests\Stub\Protector; class EditableSpamProtectionFieldTest extends SapphireTest { @@ -19,7 +21,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest { parent::setUp(); - if (!class_exists('EditableSpamProtectionField')) { + if (!class_exists(EditableSpamProtectionField::class)) { $this->markTestSkipped('"userforms" module not installed'); } @@ -64,12 +66,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest $formMock ->expects($this->once()) ->method('sessionMessage') - ->with( - $this->anything(), - $this->stringContains('some field message'), - $this->anything(), - $this->anything() - ); + ->with($this->stringContains('some field message'), $this->anything()); $formFieldMock->validateField(array('MyField' => null), $formMock); } @@ -89,13 +86,8 @@ class EditableSpamProtectionFieldTest extends SapphireTest $formMock ->expects($this->once()) - ->method('sessionMessage') - ->with( - $this->anything(), - $this->stringContains('default error message'), - $this->anything(), - $this->anything() - ); + ->method('sessionError') + ->with($this->stringContains('default error message')); $formFieldMock->validateField(array('MyField' => null), $formMock); } @@ -105,7 +97,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest $field = $this->getEditableFormFieldMock(); $fields = $field->getCMSFields(); - $this->assertInstanceOf('FieldGroup', $fields->fieldByName('Root.Main.SpamFieldMapping')); + $this->assertInstanceOf(FieldGroup::class, $fields->fieldByName('Root.Main.SpamFieldMapping')); } public function testSpamMapSettingsAreSerialised() @@ -121,9 +113,11 @@ class EditableSpamProtectionFieldTest extends SapphireTest protected function getFormMock() { - $formMock = $this->getMockBuilder(Form::class, array('sessionMessage')) + $formMock = $this->getMockBuilder(Form::class) + ->setMethods(['sessionMessage', 'sessionError', 'getValidator']) ->disableOriginalConstructor() ->getMock(); + $formMock ->expects($this->any()) ->method('getValidator') @@ -137,7 +131,7 @@ class EditableSpamProtectionFieldTest extends SapphireTest $page = new UserDefinedForm(); $page->write(); - $formFieldMock = $this->getMockBuilder('TextField') + $formFieldMock = $this->getMockBuilder(TextField::class) ->disableOriginalConstructor() ->getMock(); From 05fcdb6f41e73e57fbc1c6ae6c754d321cb95547 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Mon, 28 Aug 2017 17:41:04 +1200 Subject: [PATCH 8/8] FIX Typo in userforms form field class name --- _config/spamprotection.yml | 2 +- code/EditableSpamProtectionField.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_config/spamprotection.yml b/_config/spamprotection.yml index 5d34909..0af811d 100644 --- a/_config/spamprotection.yml +++ b/_config/spamprotection.yml @@ -1,5 +1,5 @@ --- -name: spamprotection +Name: spamprotection --- SilverStripe\Forms\Form: extensions: diff --git a/code/EditableSpamProtectionField.php b/code/EditableSpamProtectionField.php index 6159736..22e2027 100644 --- a/code/EditableSpamProtectionField.php +++ b/code/EditableSpamProtectionField.php @@ -18,7 +18,7 @@ use SilverStripe\UserForms\Model\EditableFormField\EditableTextField; /** * @todo The userforms namespaces may still change, as the branch is not merged in yet */ -if (!class_exists(EditableFormFields::class)) { +if (!class_exists(EditableFormField::class)) { return; }