diff --git a/README.md b/README.md
index 555eab9..3e9fdf4 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ Use standard HTML5 markup to create fully functional forms for WordPress
- Multilingual support with Polylang
- Predefined static HTML forms via filter hooks
- Dynamic values, like %USER_EMAIL% for pre-populating form data
+- Simple spam prevention
## Why?
@@ -157,6 +158,39 @@ function my_email_thankyou( $return ) {
}
```
+### Filter: wplf_honeypot
+
+By default, forms has honeypot field to detect spambots. Submissions detected as spam are saved as a trash, so false positives can be detected. WordPress core cleans the trash after 30 days from saving. Turn off dy returning false.
+
+```php
+add_filter( 'wplf_honeypot' , '__return_false' );
+```
+
+### Filter: wplf_honeypot_field_name
+
+Change the honeypot default field name (send_hugs_to_developers) to something else. Use obsecure but obiviosuly fake name.
+
+```php
+add_filter( 'wplf_honeypot_field_name' , 'please_send_candy' );
+```
+
+### Filter: wplf_save_spam
+
+Submissions marked as spam are saved as a trash, so false positives can be detected. WordPress core cleans the trash after 30 days from saving. Turn off dy returning false.
+
+```php
+add_filter( 'wplf_save_spam' , '__return_false' );
+```
+
+#### Form specific hooks
+
+This filter supports form specific hooks:
+
+- `wplf_{form_id}_save_spam`
+- `wplf_{form_slug}_save_spam`
+
+These filters are only applied for the target form by ID or slug.
+
### Filter: wplf_disable_validate_additional_fields
Dynamically generated fields are disabled by default. If you want to allow fields that are not set in the form to be submitted you can use this filter.
diff --git a/classes/class-cpt-wplf-form.php b/classes/class-cpt-wplf-form.php
index b793c3c..83e4d4d 100644
--- a/classes/class-cpt-wplf-form.php
+++ b/classes/class-cpt-wplf-form.php
@@ -46,6 +46,10 @@ public function __construct() {
add_filter( 'the_content', array( $this, 'use_shortcode_for_preview' ), 0 );
add_action( 'wp_enqueue_scripts', array( $this, 'maybe_enqueue_frontend_script' ) );
+ add_filter( 'template_include', array( $this, 'template_include' ) );
+ add_filter( 'the_content', array( $this, 'the_content' ) );
+ add_filter( 'the_title', array( $this, 'wp_title' ) );
+
// default filters for the_content, but we don't want to use actual the_content
add_filter( 'wplf_form', 'convert_smilies' );
add_filter( 'wplf_form', 'convert_chars' );
@@ -63,6 +67,74 @@ public function __construct() {
add_action( 'before_delete_post', array( $this, 'clean_up_entry' ) );
}
+ public static function the_content( $content ) {
+ if ( ! isset( $_GET['wplf-form'] ) ) {
+ return $content;
+ }
+
+ if ( ! isset( $_GET['wplf-form'] )
+ || ! is_numeric( $_GET['wplf-form'] )
+ || 'publish' !== get_post_status( $_GET['wplf-form'] )
+ || 'wplf-form' !== get_post_type( $_GET['wplf-form'] )
+ ) {
+ return $content;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return $content;
+ }
+
+ $content = '
+
+ ' . esc_html__( 'This form preview URL is not public and cannot be shared.', 'wp-libre-form' ) . '
+
+
+ ' . esc_html__( 'Non-logged in visitors will see a 404 error page instead.', 'wp-libre-form' ) . '
+
';
+ $content .= do_shortcode( '[libre-form id="' . $_GET['wplf-form'] . '"]' );
+ return $content;
+ }
+
+ public static function wp_title( $title ) {
+ if ( ! isset( $_GET['wplf-form'] ) ) {
+ return $title;
+ }
+
+ if ( ! isset( $_GET['wplf-form'] )
+ || ! is_numeric( $_GET['wplf-form'] )
+ || 'publish' !== get_post_status( $_GET['wplf-form'] )
+ || 'wplf-form' !== get_post_type( $_GET['wplf-form'] )
+ ) {
+ return $title;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return $title;
+ }
+
+ return __( 'Libre Form preview', 'wp-libre-form' );
+ }
+
+ public static function template_include( $template ) {
+ if ( ! isset( $_GET['wplf-form'] ) ) {
+ return $template;
+ }
+
+ if ( ! isset( $_GET['wplf-form'] )
+ || ! is_numeric( $_GET['wplf-form'] )
+ || 'publish' !== get_post_status( $_GET['wplf-form'] )
+ || 'wplf-form' !== get_post_type( $_GET['wplf-form'] )
+ ) {
+ return $template;
+ }
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ return $template;
+ }
+
+ return get_page_template();
+ }
+
public static function register_cpt() {
$labels = array(
'name' => _x( 'Forms', 'post type general name', 'wp-libre-form' ),
@@ -82,8 +154,8 @@ public static function register_cpt() {
$args = array(
'labels' => $labels,
- 'public' => true,
- 'publicly_queryable' => true,
+ 'public' => false,
+ 'publicly_queryable' => false,
'exclude_from_search' => true,
'show_ui' => true,
'show_in_menu' => true,
@@ -93,9 +165,7 @@ public static function register_cpt() {
'has_archive' => false,
'hierarchical' => false,
'menu_position' => null,
- 'rewrite' => array(
- 'slug' => 'libre-forms',
- ),
+ 'rewrite' => null,
'supports' => array(
'title',
'editor',
@@ -961,7 +1031,11 @@ class="libre-form libre-form-"
-
+
+
+
+
post_type ) {
+ return $post_states;
+ }
+
+ $is_spam = get_post_meta( $post->ID, '_is_spam', true );
+
+ if ( $is_spam ) {
+ $post_states['wplf_is_spam'] = __( 'Spam', 'wp-libre-form' );
+ }
+
+ return $post_states;
+ }
+
public function register_wplf_submission_bulk_actions( $bulk_actions ) {
$bulk_actions['wplf_resend_copy'] = __( 'Resend email copy', 'wp-libre-form' );
return $bulk_actions;
diff --git a/inc/wplf-ajax.php b/inc/wplf-ajax.php
index 4f7eeb8..e57db8f 100644
--- a/inc/wplf-ajax.php
+++ b/inc/wplf-ajax.php
@@ -9,6 +9,8 @@ function wplf_ajax_submit_handler() {
$return = new stdClass();
$return->ok = 1;
+ $return->spam = false;
+ $return->spam_save = true;
// allow user to pre-process the post fields
do_action( 'wplf_pre_validate_submission' );
@@ -26,16 +28,33 @@ function wplf_ajax_submit_handler() {
$return->title = $form->post_title;
$return = apply_filters( "wplf_{$form->post_name}_validate_submission", $return );
$return = apply_filters( "wplf_{$form->ID}_validate_submission", $return );
+
+ // allow save spam setting filtering
+ $return->spam_save = apply_filters( 'wplf_save_spam', $return->spam_save );
+ $return->spam_save = apply_filters( "wplf_{$form->post_name}_save_spam", $return->spam_save );
+ $return->spam_save = apply_filters( "wplf_{$form->ID}_save_spam", $return->spam_save );
+ }
+
+ // if message is spam and spam messages should not be saved as trash, return error.
+ if ( $return->spam && ! $return->spam_save ) {
+ $return->ok = 0;
+ $return->error = __( 'Something went wrong, please try again.', 'wp-libre-form' );
}
if ( $return->ok ) {
// the title is the value of whatever the first field was in the form
$title_format = get_post_meta( $form->ID, '_wplf_title_format', true );
+ // change post status to trash if spam, WP core cleans trash every 30 days
+ $post_status = 'publish';
+ if ( $return->spam ) {
+ $post_status = 'trash';
+ }
+
// create submission post
$post_id = wp_insert_post( array(
'post_title' => '',
- 'post_status' => 'publish',
+ 'post_status' => $post_status,
'post_type' => 'wplf-submission',
) );
@@ -73,6 +92,20 @@ function wplf_ajax_submit_handler() {
}
}
+ // bail if spam submission
+ if ( $return->spam ) {
+ // mark submission as a spam
+ add_post_meta( $post_id, '_is_spam', true, true );
+
+ // set return
+ $return->ok = 0;
+ $return->error = __( 'Something went wrong, please try again.', 'wp-libre-form' );
+
+ // respond with json
+ wp_send_json( $return );
+ wp_die();
+ }
+
// handle files
$uploads_path = wp_upload_dir();
$should_store_images_in_medialibrary = get_post_meta( $form->ID, '_wplf_media_library', true );
diff --git a/inc/wplf-form-validation.php b/inc/wplf-form-validation.php
index fec0672..cc52d02 100644
--- a/inc/wplf-form-validation.php
+++ b/inc/wplf-form-validation.php
@@ -26,11 +26,35 @@ function wplf_validate_form_exists( $return ) {
return $return;
}
+/**
+ * Check simple honeypot form spam
+ */
+add_filter( 'wplf_validate_submission', 'wplf_validate_check_honeypot', 2 );
+function wplf_validate_check_honeypot( $return ) {
+ // skip this validation if submission has already failed
+ if ( ! $return->ok ) {
+ return $return;
+ }
+
+ // skip this validation if honeypot is turned off
+ if ( ! apply_filters( 'wplf_honeypot', true ) ) {
+ return $return;
+ }
+
+ // check if honeypot exists and has some value, mark as spam if true
+ $honeypot_name = apply_filters( 'wplf_honeypot_field_name', 'send_hugs_to_developers' );
+
+ if ( ! empty( $_POST[ $honeypot_name ] ) && true === (bool) $_POST[ $honeypot_name ] ) {
+ $return->spam = true;
+ }
+
+ return $return;
+}
/**
* Check for required fields that are empty
*/
-add_filter( 'wplf_validate_submission', 'wplf_validate_required_empty', 2 );
+add_filter( 'wplf_validate_submission', 'wplf_validate_required_empty', 3 );
function wplf_validate_required_empty( $return ) {
// skip this validation if submission has already failed
if ( ! $return->ok ) {
@@ -68,7 +92,7 @@ function wplf_validate_required_empty( $return ) {
/**
* Check that submission has only fields that are set in form
*/
-add_filter( 'wplf_validate_submission', 'wplf_validate_additional_fields', 3 );
+add_filter( 'wplf_validate_submission', 'wplf_validate_additional_fields', 4 );
function wplf_validate_additional_fields( $return ) {
// skip this validation if submission has already failed
if ( ! $return->ok ) {
diff --git a/views/preview.php b/views/preview.php
new file mode 100644
index 0000000..bd7eb08
--- /dev/null
+++ b/views/preview.php
@@ -0,0 +1,16 @@
+