From 9d9f7471a0df0f821e5249cf5e04b038d2a32234 Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 16:17:19 +0200 Subject: [PATCH 1/6] add simple honeypot check --- classes/class-cpt-wplf-form.php | 1 + classes/class-cpt-wplf-submission.php | 18 ++++++++++++++ inc/wplf-ajax.php | 36 ++++++++++++++++++++++++++- inc/wplf-form-validation.php | 21 ++++++++++++++-- 4 files changed, 73 insertions(+), 3 deletions(-) diff --git a/classes/class-cpt-wplf-form.php b/classes/class-cpt-wplf-form.php index b793c3c..96eacad 100644 --- a/classes/class-cpt-wplf-form.php +++ b/classes/class-cpt-wplf-form.php @@ -962,6 +962,7 @@ 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..0ff3ebe 100644 --- a/inc/wplf-ajax.php +++ b/inc/wplf-ajax.php @@ -9,6 +9,13 @@ function wplf_ajax_submit_handler() { $return = new stdClass(); $return->ok = 1; + $return->spam = false; + $return->spam_save = true; + + // 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 ); // allow user to pre-process the post fields do_action( 'wplf_pre_validate_submission' ); @@ -28,14 +35,27 @@ function wplf_ajax_submit_handler() { $return = apply_filters( "wplf_{$form->ID}_validate_submission", $return ); } + // 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 +93,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..ecc6377 100644 --- a/inc/wplf-form-validation.php +++ b/inc/wplf-form-validation.php @@ -26,11 +26,28 @@ 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; + } + + // check if honeypot exists and has some value, mark as spam if true + if ( ! empty( $_POST['send_hugs_to_developers'] ) && true === (bool) $_POST['send_hugs_to_developers'] ) { + $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 +85,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 ) { From 1b40ce64f84e71b306eee26995db175cbc775b3e Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 16:23:39 +0200 Subject: [PATCH 2/6] allow filtering the honeypot feature off and change the honeypot name --- classes/class-cpt-wplf-form.php | 7 +++++-- inc/wplf-form-validation.php | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/classes/class-cpt-wplf-form.php b/classes/class-cpt-wplf-form.php index 96eacad..ec56b88 100644 --- a/classes/class-cpt-wplf-form.php +++ b/classes/class-cpt-wplf-form.php @@ -961,8 +961,11 @@ class="libre-form libre-form-" - - + + + + spam = true; } From f7a45f4e7c38a14f214f4ed0ccf558a5b813e9fd Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 16:32:44 +0200 Subject: [PATCH 3/6] update documentation --- README.md | 34 +++++++++++++++++++++++++++++++++ classes/class-cpt-wplf-form.php | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) 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 ec56b88..828f616 100644 --- a/classes/class-cpt-wplf-form.php +++ b/classes/class-cpt-wplf-form.php @@ -963,7 +963,7 @@ class="libre-form libre-form-" + $honeypot_name = apply_filters( 'wplf_honeypot_field_name', 'send_hugs_to_developers' ); ?> From 74dd861c8967a362133e54e90bd457ab7699c901 Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 16:42:30 +0200 Subject: [PATCH 4/6] travis phpcs shutup --- classes/class-cpt-wplf-form.php | 2 +- inc/wplf-ajax.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/classes/class-cpt-wplf-form.php b/classes/class-cpt-wplf-form.php index 828f616..8aedf6a 100644 --- a/classes/class-cpt-wplf-form.php +++ b/classes/class-cpt-wplf-form.php @@ -964,7 +964,7 @@ class="libre-form libre-form-" - + spam_save = true; // allow save spam setting filtering - $return->spam_save = apply_filters( "wplf_save_spam", $return->spam_save ); + $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 ); @@ -45,7 +45,6 @@ function wplf_ajax_submit_handler() { // 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 ) { From 1b14da5cbd145667791dd41690bbb4bf7eb096fa Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 16:51:46 +0200 Subject: [PATCH 5/6] move filters to better place --- inc/wplf-ajax.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/inc/wplf-ajax.php b/inc/wplf-ajax.php index c0c8818..e57db8f 100644 --- a/inc/wplf-ajax.php +++ b/inc/wplf-ajax.php @@ -12,11 +12,6 @@ function wplf_ajax_submit_handler() { $return->spam = false; $return->spam_save = true; - // 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 ); - // allow user to pre-process the post fields do_action( 'wplf_pre_validate_submission' ); @@ -33,6 +28,11 @@ 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. From 7fd69b1f12b4a7d1e2004a5251d8074956289ca4 Mon Sep 17 00:00:00 2001 From: Timi Wahalahti Date: Sat, 2 Feb 2019 17:42:31 +0200 Subject: [PATCH 6/6] set form cpt to private and alter content when preview url --- classes/class-cpt-wplf-form.php | 80 ++++++++++++++++++++++++++++++--- views/preview.php | 16 +++++++ 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 views/preview.php diff --git a/classes/class-cpt-wplf-form.php b/classes/class-cpt-wplf-form.php index 8aedf6a..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', 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 @@ +