plugin install
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/**
|
||||
* Handles logic for honeypot service.
|
||||
*
|
||||
* @package Gravity_Forms\Gravity_Forms\Honeypot
|
||||
*/
|
||||
|
||||
namespace Gravity_Forms\Gravity_Forms\Honeypot;
|
||||
|
||||
/**
|
||||
* Class GF_Honeypot_Handler
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* Provides functionality for handling honeypot spam prevention services.
|
||||
*/
|
||||
class GF_Honeypot_Handler {
|
||||
|
||||
/**
|
||||
* Target of the gform_entry_is_spam filter. Checks entry for honeypot validation and returns true or false depending on the result.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param bool $is_spam Variable being filtered. True for spam, false for non-spam.
|
||||
* @param array $form Current form object.
|
||||
*
|
||||
* @return bool Returns true if honeypot validation fails. False otherwise.
|
||||
*/
|
||||
public function handle_entry_is_spam( $is_spam, $form ) {
|
||||
|
||||
// If already marked as spam, don't change it.
|
||||
if ( $is_spam ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bypass honeypot validation if disabled.
|
||||
if ( ! $this->is_honeypot_enabled( $form ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$is_spam = ! $this->validate_honeypot( $form );
|
||||
|
||||
// Setting filter that flagged entry as spam so that an entry note is created.
|
||||
if ( $is_spam ) {
|
||||
\GFCommon::set_spam_filter( $form['id'], __( 'Honeypot Spam Filter', 'gravityforms' ), __( 'Failed Honeypot Validation.', 'gravityforms' ) );
|
||||
}
|
||||
|
||||
return $is_spam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Target of the gform_abort_submission_with_confirmation filter. Aborts form submission early with a confirmation when honeypot fails and it is configured not to create an entry.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param bool $do_abort Variable being filtered. True to abort submission, false to continue.
|
||||
* @param array $form Current form object.
|
||||
*
|
||||
* @return bool Returns true to abort form submission early and display confirmation. Returns false to let submission continue.
|
||||
*/
|
||||
public function handle_abort_submission( $do_abort, $form ) {
|
||||
|
||||
// If already marked to abort early, let it abort early.
|
||||
if ( $do_abort ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not abort submission if Honeypot should be disabled or if honeypot action is set to create an entry.
|
||||
if ( ! $this->is_honeypot_enabled( $form ) || rgar( $form, 'honeypotAction' ) == 'spam' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$do_abort = ! $this->validate_honeypot( $form );
|
||||
\GFCommon::log_debug( __METHOD__ . '(): Result from Honeypot: ' . json_encode( $do_abort ) );
|
||||
|
||||
return $do_abort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Target of the gform_after_submission. Clears the cached results.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $entry Current entry object.
|
||||
* @param array $form Current form object.
|
||||
*/
|
||||
public function handle_after_submission( $entry, $form ) {
|
||||
\GFCache::delete( "honeypot_{$form['id']}" );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the honeypot field to the form if honeypot is enabled.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $form Current form object.
|
||||
*
|
||||
* @return array Returns a form object with the new honeypot field appended to the fields array.
|
||||
*/
|
||||
public function maybe_add_honeypot_field( $form ) {
|
||||
|
||||
if ( rgar( $form, 'enableHoneypot' ) ) {
|
||||
$form['fields'][] = $this->get_honeypot_field( $form );
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the submission against the honeypot field.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $form The current form object.
|
||||
*
|
||||
* @return bool True if form passes the honeypot validation (i.e. Not spam). False if honeypot validation fails (i.e. spam)
|
||||
*/
|
||||
public function validate_honeypot( $form ) {
|
||||
|
||||
// If validation has already been computed for this form, no need to validate it again.
|
||||
$cache_key = "honeypot_{$form['id']}";
|
||||
if ( \GFCache::get( $cache_key ) !== false ) {
|
||||
return (bool) \GFCache::get( $cache_key );
|
||||
}
|
||||
|
||||
$honeypot_id = $this->get_honeypot_field_id( $form );
|
||||
$pass_server_side_honeypot = rgempty( "input_{$honeypot_id}" );
|
||||
\GFCommon::log_debug( __METHOD__ . '(): Is honeypot input empty? ' . json_encode( $pass_server_side_honeypot ) );
|
||||
|
||||
// Bypass JS field hash validation on GFAPI submissions.
|
||||
if ( $this->is_api_submission() ) {
|
||||
$pass_js_honeypot = true;
|
||||
\GFCommon::log_debug( __METHOD__ . '(): Submission initiated by GFAPI. Honeypot JS field hash validation bypassed.' );
|
||||
} else {
|
||||
$pass_js_honeypot = $this->is_valid_version_hash( rgpost( 'version_hash' ) );
|
||||
\GFCommon::log_debug( __METHOD__ . '(): Is version_hash input valid? ' . json_encode( $pass_js_honeypot ) );
|
||||
}
|
||||
|
||||
$is_success = $pass_server_side_honeypot && $pass_js_honeypot;
|
||||
\GFCommon::log_debug( __METHOD__ . '(): Are both inputs valid? ' . json_encode( $is_success ) );
|
||||
|
||||
\GFCache::set( $cache_key, (int) $is_success );
|
||||
|
||||
return $is_success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the honeypot field.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $form Current form object.
|
||||
*
|
||||
* @return int Returns the id of the honeypot field.
|
||||
*/
|
||||
public function get_honeypot_field_id( $form ) {
|
||||
|
||||
if ( empty( $form['fields'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Look for honeypot field in the form.
|
||||
$honeypot_field = \GFFormsModel::get_fields_by_type( $form, array( 'honeypot' ) );
|
||||
if ( count( $honeypot_field ) > 0 ) {
|
||||
return $honeypot_field[0]->id;
|
||||
}
|
||||
|
||||
// If no honeypot field in the form, return the largest field ID + 1.
|
||||
return \GFFormsModel::get_next_field_id( $form['fields'] );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the honeypot field object for the given form.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $form The form the honeypot field is to be created for.
|
||||
*
|
||||
* @return GF_Field Returns the honeypot field.
|
||||
*/
|
||||
private function get_honeypot_field( $form ) {
|
||||
|
||||
$labels = $this->get_honeypot_labels();
|
||||
$field_data = array(
|
||||
'type' => 'honeypot',
|
||||
'label' => $labels[ rand( 0, count( $labels ) - 1 ) ],
|
||||
'id' => \GFFormsModel::get_next_field_id( $form['fields'] ),
|
||||
'cssClass' => 'gform_validation_container',
|
||||
'description' => __( 'This field is for validation purposes and should be left unchanged.', 'gravityforms' ),
|
||||
'formId' => absint( $form['id'] ),
|
||||
);
|
||||
|
||||
return \GF_Fields::create( $field_data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of possible labels to be used for the Honeypot field.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @return array Returns an array of possible labels
|
||||
*/
|
||||
private function get_honeypot_labels() {
|
||||
$honeypot_labels = array( 'Name', 'Email', 'Phone', 'Comments' );
|
||||
|
||||
/**
|
||||
* Allow the honeypot field labels to be overridden.
|
||||
*
|
||||
* @since 2.0.7.16
|
||||
*
|
||||
* @param array $honeypot_labels The honeypot field labels.
|
||||
*/
|
||||
return apply_filters( 'gform_honeypot_labels_pre_render', $honeypot_labels );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a version hash.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param string $hash The version hash to be validated.
|
||||
*
|
||||
* @return bool Returns true if the hash is validated against the current and previous version of Gravity Forms
|
||||
*/
|
||||
public function is_valid_version_hash( $hash ) {
|
||||
|
||||
// Allow password to validate on current version and previous version.
|
||||
$allowed_hashes = array( wp_hash( \GFForms::$version ) );
|
||||
|
||||
$previous_version = get_option( 'gf_previous_db_version' );
|
||||
if ( ! empty( $previous_version ) ) {
|
||||
$allowed_hashes[] = wp_hash( $previous_version );
|
||||
}
|
||||
|
||||
return in_array( $hash, $allowed_hashes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if Honeypot should be enabled for this form submission.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @param array $form The current form object.
|
||||
*
|
||||
* @return bool True if honeypot should be enabled. False otherwise.
|
||||
*/
|
||||
public function is_honeypot_enabled( $form ) {
|
||||
|
||||
// Honeypot should be disabled if ANY of the following is true:
|
||||
// 1- honeypot is not enabled by this form in form settings.
|
||||
// 2- the form is submitted from preview.
|
||||
// 3- the form is submitted from the WP dashboard.
|
||||
$is_wp_dashboard = is_admin() && ! ( defined( 'DOING_AJAX' ) && DOING_AJAX );
|
||||
$is_disabled = ! rgar( $form, 'enableHoneypot' ) || \GFCommon::is_preview() || $is_wp_dashboard;
|
||||
|
||||
return ! $is_disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current form submission was initiated via GFAPI.
|
||||
*
|
||||
* @since 2.7
|
||||
*
|
||||
* @return bool True if the current form submission was initiated via GFAPI. False otherwise.
|
||||
*/
|
||||
public function is_api_submission() {
|
||||
return \GFFormDisplay::$submission_initiated_by == \GFFormDisplay::SUBMISSION_INITIATED_BY_API;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Service Provider for Honeypot Service
|
||||
*
|
||||
* @package Gravity_Forms\Gravity_Forms\Honeypot
|
||||
*/
|
||||
|
||||
namespace Gravity_Forms\Gravity_Forms\Honeypot;
|
||||
|
||||
use Gravity_Forms\Gravity_Forms\Config\GF_Config_Service_Provider;
|
||||
use Gravity_Forms\Gravity_Forms\GF_Service_Container;
|
||||
use Gravity_Forms\Gravity_Forms\GF_Service_Provider;
|
||||
use Gravity_Forms\Gravity_Forms\Honeypot\Config\GF_Honeypot_Config;
|
||||
use Gravity_Forms\Gravity_Forms\Util\GF_Util_Service_Provider;
|
||||
|
||||
/**
|
||||
* Class GF_Honeypot_Service_Provider
|
||||
*
|
||||
* Service provider for the Honeypot Service.
|
||||
*/
|
||||
class GF_Honeypot_Service_Provider extends GF_Service_Provider {
|
||||
|
||||
const GF_HONEYPOT_HANDLER = 'gf_honeypot_handler';
|
||||
|
||||
// configs
|
||||
const GF_HONEYPOT_CONFIG = 'gf_honeypot_config';
|
||||
|
||||
/**
|
||||
* Array mapping config class names to their container ID.
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $configs = array(
|
||||
self::GF_HONEYPOT_CONFIG => GF_Honeypot_Config::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Includes all related files and adds all containers.
|
||||
*
|
||||
* @param GF_Service_Container $container Container singleton object.
|
||||
*/
|
||||
public function register( GF_Service_Container $container ) {
|
||||
|
||||
require_once plugin_dir_path( __FILE__ ) . 'class-gf-honeypot-handler.php';
|
||||
require_once plugin_dir_path( __FILE__ ) . 'config/class-gf-honeypot-config.php';
|
||||
|
||||
$container->add(
|
||||
self::GF_HONEYPOT_HANDLER,
|
||||
function () {
|
||||
return new GF_Honeypot_Handler( \GFCommon::get_base_url() );
|
||||
}
|
||||
);
|
||||
|
||||
$this->add_configs( $container );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes service.
|
||||
*
|
||||
* @param GF_Service_Container $container Service Container.
|
||||
*/
|
||||
public function init( GF_Service_Container $container ) {
|
||||
parent::init( $container );
|
||||
|
||||
$honeypot_handler = $container->get( self::GF_HONEYPOT_HANDLER );
|
||||
|
||||
// Maybe abort early. If configured not to create entry.
|
||||
add_filter( 'gform_abort_submission_with_confirmation', array( $honeypot_handler, 'handle_abort_submission' ), 10, 2 );
|
||||
|
||||
// Marks entry as spam.
|
||||
add_filter( 'gform_entry_is_spam', array( $honeypot_handler, 'handle_entry_is_spam' ), 1, 2 );
|
||||
|
||||
// Clear validation cache.
|
||||
add_action( 'gform_after_submission', array( $honeypot_handler, 'handle_after_submission' ), 10, 2 );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* For each config defined in $configs, instantiate and add to container.
|
||||
*
|
||||
* @since 2.6
|
||||
*
|
||||
* @param GF_Service_Container $container
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_configs( GF_Service_Container $container ) {
|
||||
foreach ( $this->configs as $name => $class ) {
|
||||
$container->add( $name, function () use ( $container, $class ) {
|
||||
return new $class( $container->get( GF_Config_Service_Provider::DATA_PARSER ) );
|
||||
} );
|
||||
|
||||
$container->get( GF_Config_Service_Provider::CONFIG_COLLECTION )->add_config( $container->get( $name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Gravity_Forms\Gravity_Forms\Honeypot\Config;
|
||||
|
||||
use Gravity_Forms\Gravity_Forms\Config\GF_Config;
|
||||
use GFForms;
|
||||
|
||||
/**
|
||||
* Config items for the Honeypot Field
|
||||
*
|
||||
* @since 2.6
|
||||
*/
|
||||
class GF_Honeypot_Config extends GF_Config {
|
||||
|
||||
protected $name = 'gform_theme_config';
|
||||
protected $script_to_localize = 'gform_gravityforms_theme';
|
||||
|
||||
/**
|
||||
* Config data.
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function data() {
|
||||
return array(
|
||||
'common' => array(
|
||||
'form' => array(
|
||||
'honeypot' => array(
|
||||
'version_hash' => wp_hash( GFForms::$version ),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user