diff options
Diffstat (limited to 'plugins/jetpack/modules/contact-form/grunion-contact-form.php')
-rw-r--r-- | plugins/jetpack/modules/contact-form/grunion-contact-form.php | 790 |
1 files changed, 587 insertions, 203 deletions
diff --git a/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/plugins/jetpack/modules/contact-form/grunion-contact-form.php index fb893222..3eaa34c3 100644 --- a/plugins/jetpack/modules/contact-form/grunion-contact-form.php +++ b/plugins/jetpack/modules/contact-form/grunion-contact-form.php @@ -4,10 +4,11 @@ * Add a contact form to any post, page or text widget. * Emails will be sent to the post's author by default, or any email address you choose. * - * @package Jetpack + * @package automattic/jetpack */ use Automattic\Jetpack\Assets; +use Automattic\Jetpack\Blocks; use Automattic\Jetpack\Sync\Settings; define( 'GRUNION_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); @@ -106,14 +107,14 @@ class Grunion_Contact_Form_Plugin { public static function strip_tags( $data_with_tags ) { if ( is_array( $data_with_tags ) ) { foreach ( $data_with_tags as $index => $value ) { - $index = sanitize_text_field( strval( $index ) ); - $value = wp_kses( strval( $value ), array() ); + $index = sanitize_text_field( (string) $index ); + $value = wp_kses( (string) $value, array() ); $value = str_replace( '&', '&', $value ); // undo damage done by wp_kses_normalize_entities() $data_without_tags[ $index ] = $value; } } else { - $data_without_tags = wp_kses( $data_with_tags, array() ); + $data_without_tags = wp_kses( (string) $data_with_tags, array() ); $data_without_tags = str_replace( '&', '&', $data_without_tags ); // undo damage done by wp_kses_normalize_entities() } @@ -140,8 +141,8 @@ class Grunion_Contact_Form_Plugin { add_filter( 'widget_text', array( $this, 'widget_shortcode_hack' ), 5 ); } - add_filter( 'jetpack_contact_form_is_spam', array( $this, 'is_spam_blacklist' ), 10, 2 ); - + add_filter( 'jetpack_contact_form_is_spam', array( $this, 'is_spam_blocklist' ), 10, 2 ); + add_filter( 'jetpack_contact_form_in_comment_disallowed_list', array( $this, 'is_in_disallowed_list' ), 10, 2 ); // Akismet to the rescue if ( defined( 'AKISMET_VERSION' ) || function_exists( 'akismet_http_post' ) ) { add_filter( 'jetpack_contact_form_is_spam', array( $this, 'is_spam_akismet' ), 10, 2 ); @@ -149,6 +150,7 @@ class Grunion_Contact_Form_Plugin { } add_action( 'loop_start', array( 'Grunion_Contact_Form', '_style_on' ) ); + add_action( 'pre_amp_render_post', array( 'Grunion_Contact_Form', '_style_on' ) ); add_action( 'wp_ajax_grunion-contact-form', array( $this, 'ajax_request' ) ); add_action( 'wp_ajax_nopriv_grunion-contact-form', array( $this, 'ajax_request' ) ); @@ -161,23 +163,25 @@ class Grunion_Contact_Form_Plugin { if ( is_admin() ) { add_action( 'admin_init', array( $this, 'download_feedback_as_csv' ) ); add_action( 'admin_footer-edit.php', array( $this, 'export_form' ) ); - add_action( 'admin_menu', array( $this, 'admin_menu' ) ); - add_action( 'current_screen', array( $this, 'unread_count' ) ); } + add_action( 'admin_menu', array( $this, 'admin_menu' ) ); + add_action( 'current_screen', array( $this, 'unread_count' ) ); + + add_filter( 'use_block_editor_for_post_type', array( $this, 'use_block_editor_for_post_type' ), 10, 2 ); // custom post type we'll use to keep copies of the feedback items register_post_type( 'feedback', array( 'labels' => array( - 'name' => __( 'Feedback', 'jetpack' ), - 'singular_name' => __( 'Feedback', 'jetpack' ), - 'search_items' => __( 'Search Feedback', 'jetpack' ), - 'not_found' => __( 'No feedback found', 'jetpack' ), - 'not_found_in_trash' => __( 'No feedback found', 'jetpack' ), + 'name' => __( 'Form Responses', 'jetpack' ), + 'singular_name' => __( 'Form Responses', 'jetpack' ), + 'search_items' => __( 'Search Responses', 'jetpack' ), + 'not_found' => __( 'No responses found', 'jetpack' ), + 'not_found_in_trash' => __( 'No responses found', 'jetpack' ), ), - // Matrial Ballot icon - 'menu_icon' => 'data:image/svg+xml;base64,' . base64_encode('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M13 7.5h5v2h-5zm0 7h5v2h-5zM19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zM11 6H6v5h5V6zm-1 4H7V7h3v3zm1 3H6v5h5v-5zm-1 4H7v-3h3v3z"/></svg>'), + 'menu_icon' => 'dashicons-feedback', 'show_ui' => true, + 'show_in_menu' => false, 'show_in_admin_bar' => false, 'public' => false, 'rewrite' => false, @@ -201,7 +205,7 @@ class Grunion_Contact_Form_Plugin { ) ); - // Add to REST API post type whitelist + // Add to REST API post type allowed list. add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_feedback_rest_api_type' ) ); // Add "spam" as a post status @@ -243,58 +247,112 @@ class Grunion_Contact_Form_Plugin { } private static function register_contact_form_blocks() { - jetpack_register_block( 'jetpack/contact-form', array( - 'render_callback' => array( __CLASS__, 'gutenblock_render_form' ), - ) ); + Blocks::jetpack_register_block( + 'jetpack/contact-form', + array( + 'render_callback' => array( __CLASS__, 'gutenblock_render_form' ), + ) + ); // Field render methods. - jetpack_register_block( 'jetpack/field-text', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_text' ), - ) ); - jetpack_register_block( 'jetpack/field-name', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_name' ), - ) ); - jetpack_register_block( 'jetpack/field-email', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_email' ), - ) ); - jetpack_register_block( 'jetpack/field-url', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_url' ), - ) ); - jetpack_register_block( 'jetpack/field-date', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_date' ), - ) ); - jetpack_register_block( 'jetpack/field-telephone', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_telephone' ), - ) ); - jetpack_register_block( 'jetpack/field-textarea', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_textarea' ), - ) ); - jetpack_register_block( 'jetpack/field-checkbox', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox' ), - ) ); - jetpack_register_block( 'jetpack/field-checkbox-multiple', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox_multiple' ), - ) ); - jetpack_register_block( 'jetpack/field-radio', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_radio' ), - ) ); - jetpack_register_block( 'jetpack/field-select', array( - 'parent' => array( 'jetpack/contact-form' ), - 'render_callback' => array( __CLASS__, 'gutenblock_render_field_select' ), - ) ); + Blocks::jetpack_register_block( + 'jetpack/field-text', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_text' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-name', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_name' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-email', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_email' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-url', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_url' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-date', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_date' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-telephone', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_telephone' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-textarea', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_textarea' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-checkbox', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-checkbox-multiple', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_checkbox_multiple' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-radio', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_radio' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-select', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_select' ), + ) + ); + Blocks::jetpack_register_block( + 'jetpack/field-consent', + array( + 'parent' => array( 'jetpack/contact-form' ), + 'render_callback' => array( __CLASS__, 'gutenblock_render_field_consent' ), + ) + ); } public static function gutenblock_render_form( $atts, $content ) { + + // Render fallback in other contexts than frontend (i.e. feed, emails, API, etc.), unless the form is being submitted. + if ( ! jetpack_is_frontend() && ! isset( $_POST['contact-form-id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing + return sprintf( + '<div class="%1$s"><a href="%2$s" target="_blank" rel="noopener noreferrer">%3$s</a></div>', + esc_attr( Blocks::classes( 'contact-form', $atts ) ), + esc_url( get_the_permalink() ), + esc_html__( 'Submit a form.', 'jetpack' ) + ); + } + return Grunion_Contact_Form::parse( $atts, do_blocks( $content ) ); } @@ -359,21 +417,59 @@ class Grunion_Contact_Form_Plugin { } /** - * Add the 'Export' menu item as a submenu of Feedback. + * Render the consent field. + * + * @param string $atts consent attributes. + * @param string $content html content. + */ + public static function gutenblock_render_field_consent( $atts, $content ) { + $atts = self::block_attributes_to_shortcode_attributes( $atts, 'consent' ); + + if ( ! isset( $atts['implicitConsentMessage'] ) ) { + $atts['implicitConsentMessage'] = __( "By submitting your information, you're giving us permission to email you. You may unsubscribe at any time.", 'jetpack' ); + } + + if ( ! isset( $atts['explicitConsentMessage'] ) ) { + $atts['explicitConsentMessage'] = __( 'Can we send you an email from time to time?', 'jetpack' ); + } + + return Grunion_Contact_Form::parse_contact_field( $atts, $content ); + } + + /** + * Add the 'Form Responses' menu item as a submenu of Feedback. */ public function admin_menu() { + $slug = 'feedback'; + + add_menu_page( + __( 'Feedback', 'jetpack' ), + __( 'Feedback', 'jetpack' ), + 'edit_pages', + $slug, + null, + 'dashicons-feedback', + 45 + ); + add_submenu_page( + $slug, + __( 'Form Responses', 'jetpack' ), + __( 'Form Responses', 'jetpack' ), + 'edit_pages', 'edit.php?post_type=feedback', - __( 'Export feedback as CSV', 'jetpack' ), - __( 'Export CSV', 'jetpack' ), - 'export', - 'feedback-export', - array( $this, 'export_form' ) + null, + 0 + ); + + remove_submenu_page( + $slug, + $slug ); } /** - * Add to REST API post type whitelist + * Add to REST API post type allowed list. */ function allow_feedback_rest_api_type( $post_types ) { $post_types[] = 'feedback'; @@ -391,14 +487,16 @@ class Grunion_Contact_Form_Plugin { if ( isset( $screen->post_type ) && 'feedback' == $screen->post_type ) { update_option( 'feedback_unread_count', 0 ); } else { - global $menu; - if ( isset( $menu ) && is_array( $menu ) && ! empty( $menu ) ) { - foreach ( $menu as $index => $menu_item ) { + global $submenu; + if ( isset( $submenu['feedback'] ) && is_array( $submenu['feedback'] ) && ! empty( $submenu['feedback'] ) ) { + foreach ( $submenu['feedback'] as $index => $menu_item ) { if ( 'edit.php?post_type=feedback' == $menu_item[2] ) { $unread = get_option( 'feedback_unread_count', 0 ); if ( $unread > 0 ) { - $unread_count = current_user_can( 'publish_pages' ) ? " <span class='feedback-unread count-{$unread} awaiting-mod'><span class='feedback-unread-count'>" . number_format_i18n( $unread ) . '</span></span>' : ''; - $menu[ $index ][0] .= $unread_count; + $unread_count = current_user_can( 'publish_pages' ) ? " <span class='feedback-unread count-{$unread} awaiting-mod'><span class='feedback-unread-count'>" . number_format_i18n( $unread ) . '</span></span>' : ''; + + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu['feedback'][ $index ][0] .= $unread_count; } break; } @@ -417,9 +515,13 @@ class Grunion_Contact_Form_Plugin { add_filter( 'contact_form_subject', array( $this, 'replace_tokens_with_input' ), 10, 2 ); $id = stripslashes( $_POST['contact-form-id'] ); - $hash = isset( $_POST['contact-form-hash'] ) ? $_POST['contact-form-hash'] : null; + $hash = isset( $_POST['contact-form-hash'] ) ? $_POST['contact-form-hash'] : ''; $hash = preg_replace( '/[^\da-f]/i', '', $hash ); + if ( ! is_string( $id ) || ! is_string( $hash ) ) { + return false; + } + if ( is_user_logged_in() ) { check_admin_referer( "contact-form_{$id}" ); } @@ -458,24 +560,26 @@ class Grunion_Contact_Form_Plugin { $post = get_post( $id ); // Process the content to populate Grunion_Contact_Form::$last - /** This filter is already documented in core. wp-includes/post-template.php */ - apply_filters( 'the_content', $post->post_content ); + if ( $post ) { + /** This filter is already documented in core. wp-includes/post-template.php */ + apply_filters( 'the_content', $post->post_content ); + } } $form = isset( Grunion_Contact_Form::$forms[ $hash ] ) ? Grunion_Contact_Form::$forms[ $hash ] : null; // No form may mean user is using do_shortcode, grab the form using the stored post meta - if ( ! $form ) { + if ( ! $form && is_numeric( $id ) && $hash ) { // Get shortcode from post meta - $shortcode = get_post_meta( $_POST['contact-form-id'], "_g_feedback_shortcode_{$hash}", true ); + $shortcode = get_post_meta( $id, "_g_feedback_shortcode_{$hash}", true ); // Format it if ( $shortcode != '' ) { // Get attributes from post meta. $parameters = ''; - $attributes = get_post_meta( $_POST['contact-form-id'], "_g_feedback_shortcode_atts_{$hash}", true ); + $attributes = get_post_meta( $id, "_g_feedback_shortcode_atts_{$hash}", true ); if ( ! empty( $attributes ) && is_array( $attributes ) ) { foreach ( array_filter( $attributes ) as $param => $value ) { $parameters .= " $param=\"$value\""; @@ -488,10 +592,10 @@ class Grunion_Contact_Form_Plugin { // Recreate form $form = Grunion_Contact_Form::$last; } + } - if ( ! $form ) { - return false; - } + if ( ! $form ) { + return false; } if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) { @@ -553,7 +657,10 @@ class Grunion_Contact_Form_Plugin { } static function sanitize_value( $value ) { - return preg_replace( '=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $value ); + if ( null === $value ) { + return ''; + } + return preg_replace( '=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', '', $value ); } /** @@ -634,8 +741,8 @@ class Grunion_Contact_Form_Plugin { } /** - * Check if a submission matches the Comment Blacklist. - * The Comment Blacklist is a means to moderate discussion, and contact + * Check if a submission matches the Comment Blocklist. + * The Comment Blocklist is a means to moderate discussion, and contact * forms are 1:1 discussion forums, ripe for abuse by users who are being * removed from the public discussion. * Attached to `jetpack_contact_form_is_spam` @@ -644,12 +751,37 @@ class Grunion_Contact_Form_Plugin { * @param array $form * @return bool TRUE => spam, FALSE => not spam */ - function is_spam_blacklist( $is_spam, $form = array() ) { + public function is_spam_blocklist( $is_spam, $form = array() ) { if ( $is_spam ) { return $is_spam; } - if ( wp_blacklist_check( $form['comment_author'], $form['comment_author_email'], $form['comment_author_url'], $form['comment_content'], $form['user_ip'], $form['user_agent'] ) ) { + return $this->is_in_disallowed_list( false, $form ); + } + + /** + * Check if a submission matches the comment disallowed list. + * Attached to `jetpack_contact_form_in_comment_disallowed_list`. + * + * @param boolean $in_disallowed_list Whether the feedback is in the disallowed list. + * @param array $form The form array. + * @return bool Returns true if the form submission matches the disallowed list and false if it doesn't. + */ + public function is_in_disallowed_list( $in_disallowed_list, $form = array() ) { + if ( $in_disallowed_list ) { + return $in_disallowed_list; + } + + if ( + wp_check_comment_disallowed_list( + $form['comment_author'], + $form['comment_author_email'], + $form['comment_author_url'], + $form['comment_content'], + $form['user_ip'], + $form['user_agent'] + ) + ) { return true; } @@ -666,8 +798,8 @@ class Grunion_Contact_Form_Plugin { function prepare_for_akismet( $form ) { $form['comment_type'] = 'contact_form'; $form['user_ip'] = $_SERVER['REMOTE_ADDR']; - $form['user_agent'] = $_SERVER['HTTP_USER_AGENT']; - $form['referrer'] = $_SERVER['HTTP_REFERER']; + $form['user_agent'] = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : ''; + $form['referrer'] = isset( $_SERVER['HTTP_REFERER'] ) ? $_SERVER['HTTP_REFERER'] : ''; $form['blog'] = get_option( 'home' ); foreach ( $_SERVER as $key => $value ) { @@ -687,7 +819,16 @@ class Grunion_Contact_Form_Plugin { } } - return $form; + /** + * Filter the values that are sent to Akismet for the spam check. + * + * @module contact-form + * + * @since 10.2.0 + * + * @param array $form The form values being sent to Akismet. + */ + return apply_filters( 'jetpack_contact_form_akismet_values', $form ); } /** @@ -794,13 +935,13 @@ class Grunion_Contact_Form_Plugin { ?> <div id="feedback-export" style="display:none"> - <h2><?php _e( 'Export feedback as CSV', 'jetpack' ); ?></h2> + <h2><?php esc_html_e( 'Export responses as CSV', 'jetpack' ); ?></h2> <div class="clear"></div> <form action="<?php echo admin_url( 'admin-post.php' ); ?>" method="post" class="form"> <?php wp_nonce_field( 'feedback_export', 'feedback_export_nonce' ); ?> <input name="action" value="feedback_export" type="hidden"> - <label for="post"><?php _e( 'Select feedback to download', 'jetpack' ); ?></label> + <label for="post"><?php esc_html_e( 'Select responses to download', 'jetpack' ); ?></label> <select name="post"> <option value="all"><?php esc_html_e( 'All posts', 'jetpack' ); ?></option> <?php echo $this->get_feedbacks_as_options(); ?> @@ -854,6 +995,17 @@ class Grunion_Contact_Form_Plugin { $md['feedback_date'] = get_the_date( DATE_RFC3339, $post_id ); $content_fields = self::parse_fields_from_content( $post_id ); $md['feedback_ip'] = ( isset( $content_fields['_feedback_ip'] ) ) ? $content_fields['_feedback_ip'] : 0; + + // add the email_marketing_consent to the post meta. + $md['email_marketing_consent'] = 0; + if ( isset( $content_fields['_feedback_all_fields'] ) ) { + $all_fields = $content_fields['_feedback_all_fields']; + // check if the email_marketing_consent field exists. + if ( isset( $all_fields['email_marketing_consent'] ) ) { + $md['email_marketing_consent'] = $all_fields['email_marketing_consent']; + } + } + return $md; } @@ -1584,6 +1736,17 @@ class Grunion_Contact_Form_Plugin { public static function get_ip_address() { return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null; } + + /** + * Disable Block Editor for feedbacks. + * + * @param bool $can_edit Whether the post type can be edited or not. + * @param string $post_type The post type being checked. + * @return bool + */ + public function use_block_editor_for_post_type( $can_edit, $post_type ) { + return 'feedback' === $post_type ? false : $can_edit; + } } /** @@ -1651,9 +1814,9 @@ class Crunion_Contact_Form_Shortcode { function parse_content( $content ) { if ( is_null( $content ) ) { $this->body = null; + } else { + $this->body = do_shortcode( $content ); } - - $this->body = do_shortcode( $content ); } /** @@ -1697,7 +1860,8 @@ class Crunion_Contact_Form_Shortcode { // For back-compat with old Grunion encoding // Also, unencode commas $value = strtr( - $value, array( + (string) $value, + array( '%26' => '&', '%25' => '%', ) @@ -1814,7 +1978,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $this->hash = sha1( json_encode( $attributes ) . $content ); self::$forms[ $this->hash ] = $this; - // Set up the default subject and recipient for this form + // Set up the default subject and recipient for this form. $default_to = ''; $default_subject = '[' . get_option( 'blogname' ) . ']'; @@ -1833,7 +1997,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $default_to .= $post_author->user_email; } - // Keep reference to $this for parsing form fields + // Keep reference to $this for parsing form fields. self::$current_form = $this; $this->defaults = array( @@ -1845,20 +2009,22 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { 'submit_button_text' => __( 'Submit', 'jetpack' ), // These attributes come from the block editor, so use camel case instead of snake case. 'customThankyou' => '', // Whether to show a custom thankyou response after submitting a form. '' for no, 'message' for a custom message, 'redirect' to redirect to a new URL. + 'customThankyouHeading' => __( 'Message Sent', 'jetpack' ), // The text to show above customThankyouMessage. 'customThankyouMessage' => __( 'Thank you for your submission!', 'jetpack' ), // The message to show when customThankyou is set to 'message'. 'customThankyouRedirect' => '', // The URL to redirect to when customThankyou is set to 'redirect'. + 'jetpackCRM' => true, // Whether Jetpack CRM should store the form submission. ); $attributes = shortcode_atts( $this->defaults, $attributes, 'contact-form' ); - // We only enable the contact-field shortcode temporarily while processing the contact-form shortcode + // We only enable the contact-field shortcode temporarily while processing the contact-form shortcode. Grunion_Contact_Form_Plugin::$using_contact_form_field = true; parent::__construct( $attributes, $content ); // There were no fields in the contact form. The form was probably just [contact-form /]. Build a default form. if ( empty( $this->fields ) ) { - // same as the original Grunion v1 form + // same as the original Grunion v1 form. $default_form = ' [contact-field label="' . __( 'Name', 'jetpack' ) . '" type="name" required="true" /] [contact-field label="' . __( 'Email', 'jetpack' ) . '" type="email" required="true" /] @@ -1874,10 +2040,10 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $this->parse_content( $default_form ); - // Store the shortcode + // Store the shortcode. $this->store_shortcode( $default_form, $attributes, $this->hash ); } else { - // Store the shortcode + // Store the shortcode. $this->store_shortcode( $content, $attributes, $this->hash ); } @@ -1981,16 +2147,17 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } if ( isset( $_GET['contact-form-id'] ) - && $_GET['contact-form-id'] == self::$last->get_attribute( 'id' ) - && isset( $_GET['contact-form-sent'], $_GET['contact-form-hash'] ) - && hash_equals( $form->hash, $_GET['contact-form-hash'] ) ) { - // The contact form was submitted. Show the success message/results + && (int) $_GET['contact-form-id'] === (int) self::$last->get_attribute( 'id' ) + && isset( $_GET['contact-form-sent'], $_GET['contact-form-hash'] ) + && is_string( $_GET['contact-form-hash'] ) + && hash_equals( $form->hash, $_GET['contact-form-hash'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended + // The contact form was submitted. Show the success message/results. $feedback_id = (int) $_GET['contact-form-sent']; $back_url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', '_wpnonce' ) ); $r_success_message = - '<h3>' . __( 'Message Sent', 'jetpack' ) . + '<h3>' . esc_html( $form->get_attribute( 'customThankyouHeading' ) ) . ' (<a href="' . esc_url( $back_url ) . '">' . esc_html__( 'go back', 'jetpack' ) . '</a>)' . "</h3>\n\n"; @@ -2034,50 +2201,63 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { * @param $post $GLOBALS['post'] Post global variable. * @param int $id Contact Form ID. */ - $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id ); + $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id ); + $has_submit_button_block = ! ( false === strpos( $content, 'wp-block-jetpack-button' ) ); + $form_classes = 'contact-form commentsblock'; + + if ( $has_submit_button_block ) { + $form_classes .= ' wp-block-jetpack-contact-form'; + } + + $r .= "<form action='" . esc_url( $url ) . "' method='post' class='" . esc_attr( $form_classes ) . "'>\n"; + $r .= self::get_script_for_form(); - $r .= "<form action='" . esc_url( $url ) . "' method='post' class='contact-form commentsblock'>\n"; $r .= $form->body; - $r .= "\t<p class='contact-submit'>\n"; - $gutenberg_submit_button_classes = ''; - if ( ! empty( $attributes['submitButtonClasses'] ) ) { - $gutenberg_submit_button_classes = ' ' . $attributes['submitButtonClasses']; - } + // In new versions of the contact form block the button is an inner block + // so the button does not need to be constructed server-side. + if ( ! $has_submit_button_block ) { + $r .= "\t<p class='contact-submit'>\n"; - /** - * Filter the contact form submit button class attribute. - * - * @module contact-form - * - * @since 6.6.0 - * - * @param string $class Additional CSS classes for button attribute. - */ - $submit_button_class = apply_filters( 'jetpack_contact_form_submit_button_class', 'pushbutton-wide' . $gutenberg_submit_button_classes ); + $gutenberg_submit_button_classes = ''; + if ( ! empty( $attributes['submitButtonClasses'] ) ) { + $gutenberg_submit_button_classes = ' ' . $attributes['submitButtonClasses']; + } - $submit_button_styles = ''; - if ( ! empty( $attributes['customBackgroundButtonColor'] ) ) { - $submit_button_styles .= 'background-color: ' . $attributes['customBackgroundButtonColor'] . '; '; - } - if ( ! empty( $attributes['customTextButtonColor'] ) ) { - $submit_button_styles .= 'color: ' . $attributes['customTextButtonColor'] . ';'; - } - if ( ! empty( $attributes['submitButtonText'] ) ) { - $submit_button_text = $attributes['submitButtonText']; - } else { - $submit_button_text = $form->get_attribute( 'submit_button_text' ); - } + /** + * Filter the contact form submit button class attribute. + * + * @module contact-form + * + * @since 6.6.0 + * + * @param string $class Additional CSS classes for button attribute. + */ + $submit_button_class = apply_filters( 'jetpack_contact_form_submit_button_class', 'pushbutton-wide' . $gutenberg_submit_button_classes ); + + $submit_button_styles = ''; + if ( ! empty( $attributes['customBackgroundButtonColor'] ) ) { + $submit_button_styles .= 'background-color: ' . $attributes['customBackgroundButtonColor'] . '; '; + } + if ( ! empty( $attributes['customTextButtonColor'] ) ) { + $submit_button_styles .= 'color: ' . $attributes['customTextButtonColor'] . ';'; + } + if ( ! empty( $attributes['submitButtonText'] ) ) { + $submit_button_text = $attributes['submitButtonText']; + } else { + $submit_button_text = $form->get_attribute( 'submit_button_text' ); + } - $r .= "\t\t<button type='submit' class='" . esc_attr( $submit_button_class ) . "'"; - if ( ! empty( $submit_button_styles ) ) { - $r .= " style='" . esc_attr( $submit_button_styles ) . "'"; + $r .= "\t\t<button type='submit' class='" . esc_attr( $submit_button_class ) . "'"; + if ( ! empty( $submit_button_styles ) ) { + $r .= " style='" . esc_attr( $submit_button_styles ) . "'"; + } + $r .= ">"; + $r .= wp_kses( + $submit_button_text, + self::$allowed_html_tags_for_submit_button + ) . "</button>"; } - $r .= ">"; - $r .= wp_kses( - $submit_button_text, - self::$allowed_html_tags_for_submit_button - ) . "</button>"; if ( is_user_logged_in() ) { $r .= "\t\t" . wp_nonce_field( 'contact-form_' . $id, '_wpnonce', true, false ) . "\n"; // nonce and referer @@ -2089,13 +2269,26 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $r .= "\t\t<input type='hidden' name='contact-form-id' value='$id' />\n"; $r .= "\t\t<input type='hidden' name='action' value='grunion-contact-form' />\n"; $r .= "\t\t<input type='hidden' name='contact-form-hash' value='" . esc_attr( $form->hash ) . "' />\n"; - $r .= "\t</p>\n"; + + if ( ! $has_submit_button_block ) { + $r .= "\t</p>\n"; + } + $r .= "</form>\n"; } $r .= '</div>'; - return $r; + /** + * Filter the contact form, allowing plugins to modify the HTML. + * + * @module contact-form + * + * @since 10.2.0 + * + * @param string $r The contact form HTML. + */ + return apply_filters( 'jetpack_contact_form_html', $r ); } /** @@ -2126,6 +2319,29 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } /** + * Returns a script that disables the contact form button after a form submission. + * + * @return string The script. + */ + private static function get_script_for_form() { + return "<script> + ( function () { + const contact_forms = document.getElementsByClassName('contact-form'); + + for ( const form of contact_forms ) { + form.onsubmit = function() { + const buttons = form.getElementsByTagName('button'); + + for( const button of buttons ) { + button.setAttribute('disabled', true); + } + } + } + } )(); + </script>"; + } + + /** * Returns a compiled form with labels and values in a form of an array * of lines. * @@ -2150,7 +2366,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $compiled_form = array(); - // "Standard" field whitelist + // "Standard" field allowed list. foreach ( $field_value_map as $type => $meta_key ) { if ( isset( $field_ids[ $type ] ) ) { $field = $form->fields[ $field_ids[ $type ] ]; @@ -2166,6 +2382,11 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $value = trim( $value ); } + // If we still do not have any value, bail. + if ( empty( $value ) ) { + continue; + } + $field_index = array_search( $field_ids[ $type ], $field_ids['all'] ); $compiled_form[ $field_index ] = sprintf( '<b>%1$s:</b> %2$s<br /><br />', @@ -2228,6 +2449,34 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } /** + * Escape a shortcode value. + * + * Shortcode attribute values have a number of unfortunate restrictions, which fortunately we + * can get around by adding some extra HTML encoding. + * + * The output HTML will have a few extra escapes, but that makes no functional difference. + * + * @since 9.1.0 + * @param string $val Value to escape. + * @return string + */ + private static function esc_shortcode_val( $val ) { + return strtr( + esc_html( $val ), + array( + // Brackets in attribute values break the shortcode parser. + '[' => '[', + ']' => ']', + // Shortcode parser screws up backslashes too, thanks to calls to `stripcslashes`. + '\\' => '\', + // The existing code here represents arrays as comma-separated strings. + // Rather than trying to change representations now, just escape the commas in values. + ',' => ',', + ) + ); + } + + /** * The contact-field shortcode processor * We use an object method here instead of a static Grunion_Contact_Form_Field class method to parse contact-field shortcodes so that we can tie them to the contact-form object. * @@ -2245,18 +2494,18 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } foreach ( $attributes as $att => $val ) { if ( is_numeric( $att ) ) { // Is a valueless attribute - $att_strs[] = esc_html( $val ); + $att_strs[] = self::esc_shortcode_val( $val ); } elseif ( isset( $val ) ) { // A regular attr - value pair if ( ( $att === 'options' || $att === 'values' ) && is_string( $val ) ) { // remove any empty strings $val = explode( ',', $val ); } - if ( is_array( $val ) ) { + if ( is_array( $val ) ) { $val = array_filter( $val, array( __CLASS__, 'remove_empty' ) ); // removes any empty strings - $att_strs[] = esc_html( $att ) . '="' . implode( ',', array_map( 'esc_html', $val ) ) . '"'; + $att_strs[] = esc_html( $att ) . '="' . implode( ',', array_map( array( __CLASS__, 'esc_shortcode_val' ), $val ) ) . '"'; } elseif ( is_bool( $val ) ) { - $att_strs[] = esc_html( $att ) . '="' . esc_html( $val ? '1' : '' ) . '"'; + $att_strs[] = esc_html( $att ) . '="' . ( $val ? '1' : '' ) . '"'; } else { - $att_strs[] = esc_html( $att ) . '="' . esc_html( $val ) . '"'; + $att_strs[] = esc_html( $att ) . '="' . self::esc_shortcode_val( $val ) . '"'; } } } @@ -2299,59 +2548,75 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } static function get_default_label_from_type( $type ) { + $str = null; switch ( $type ) { case 'text': - return __( 'Text', 'jetpack' ); + $str = __( 'Text', 'jetpack' ); + break; case 'name': - return __( 'Name', 'jetpack' ); + $str = __( 'Name', 'jetpack' ); + break; case 'email': - return __( 'Email', 'jetpack' ); + $str = __( 'Email', 'jetpack' ); + break; case 'url': - return __( 'Website', 'jetpack' ); + $str = __( 'Website', 'jetpack' ); + break; case 'date': - return __( 'Date', 'jetpack' ); + $str = __( 'Date', 'jetpack' ); + break; case 'telephone': - return __( 'Phone', 'jetpack' ); + $str = __( 'Phone', 'jetpack' ); + break; case 'textarea': - return __( 'Message', 'jetpack' ); + $str = __( 'Message', 'jetpack' ); + break; case 'checkbox': - return __( 'Checkbox', 'jetpack' ); + $str = __( 'Checkbox', 'jetpack' ); + break; case 'checkbox-multiple': - return __( 'Choose several', 'jetpack' ); + $str = __( 'Choose several', 'jetpack' ); + break; case 'radio': - return __( 'Choose one', 'jetpack' ); + $str = __( 'Choose one', 'jetpack' ); + break; case 'select': - return __( 'Select one', 'jetpack' ); + $str = __( 'Select one', 'jetpack' ); + break; + case 'consent': + $str = __( 'Consent', 'jetpack' ); + break; default: - return null; + $str = null; } + return $str; } /** * Loops through $this->fields to generate a (structured) list of field IDs. * - * Important: Currently the whitelisted fields are defined as follows: + * Important: Currently the allowed fields are defined as follows: * `name`, `email`, `url`, `subject`, `textarea` * * If you need to add new fields to the Contact Form, please don't add them - * to the whitelisted fields and leave them as extra fields. + * to the allowed fields and leave them as extra fields. * * The reasoning behind this is that both the admin Feedback view and the CSV * export will not include any fields that are added to the list of - * whitelisted fields without taking proper care to add them to all the + * allowed fields without taking proper care to add them to all the * other places where they accessed/used/saved. * * The safest way to add new fields is to add them to the dropdown and the * HTML list ( @see Grunion_Contact_Form_Field::render ) and don't add them - * to the list of whitelisted fields. This way they will become a part of the + * to the list of allowed fields. This way they will become a part of the * `extra fields` which are saved in the post meta and will be properly * handled by the admin Feedback view and the CSV Export without any extra * work. * - * If there is need to add a field to the whitelisted fields, then please + * If there is need to add a field to the allowed fields, then please * take proper care to add logic to handle the field in the following places: * - * - Below in the switch statement - so the field is recognized as whitelisted. + * - Below in the switch statement - so the field is recognized as allowed. * * - Grunion_Contact_Form::process_submission - validation and logic. * @@ -2372,10 +2637,10 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { */ function get_field_ids() { $field_ids = array( - 'all' => array(), // array of all field_ids - 'extra' => array(), // array of all non-whitelisted field IDs + 'all' => array(), // array of all field_ids. + 'extra' => array(), // array of all non-allowed field IDs. - // Whitelisted "standard" field IDs: + // Allowed "standard" field IDs: // 'email' => field_id, // 'name' => field_id, // 'url' => field_id, @@ -2388,7 +2653,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $type = $field->get_attribute( 'type' ); if ( isset( $field_ids[ $type ] ) ) { - // This type of field is already present in our whitelist of "standard" fields for this form + // This type of field is already present in our allowed list of "standard" fields for this form // Put it in extra $field_ids['extra'][] = $id; continue; @@ -2403,6 +2668,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { case 'url': case 'subject': case 'textarea': + case 'consent': $field_ids[ $type ] = $id; break; default: @@ -2427,7 +2693,8 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $to = $this->get_attribute( 'to' ); $widget = $this->get_attribute( 'widget' ); - $contact_form_subject = $this->get_attribute( 'subject' ); + $contact_form_subject = $this->get_attribute( 'subject' ); + $email_marketing_consent = false; $to = str_replace( ' ', '', $to ); $emails = explode( ',', $to ); @@ -2528,6 +2795,13 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { } } + if ( isset( $field_ids['consent'] ) ) { + $field = $this->fields[ $field_ids['consent'] ]; + if ( $field->value ) { + $email_marketing_consent = true; + } + } + $all_values = $extra_values = array(); $i = 1; // Prefix counter for stored metadata @@ -2566,7 +2840,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $vars = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'contact_form_subject', 'comment_author_IP' ); foreach ( $vars as $var ) { - $$var = str_replace( array( "\n", "\r" ), '', $$var ); + $$var = str_replace( array( "\n", "\r" ), '', (string) $$var ); } // Ensure that Akismet gets all of the relevant information from the contact form, @@ -2615,6 +2889,18 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $spam = '***SPAM*** '; } + /** + * Filter whether a submitted contact form is in the comment disallowed list. + * + * @module contact-form + * + * @since 8.9.0 + * + * @param bool $result Is the submitted feedback in the disallowed list. + * @param array $akismet_values Feedack values returned by the Akismet plugin. + */ + $in_comment_disallowed_list = apply_filters( 'jetpack_contact_form_in_comment_disallowed_list', false, $akismet_values ); + if ( ! $comment_author ) { $comment_author = $comment_author_email; } @@ -2643,22 +2929,56 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $reply_to_addr = $comment_author_email; } - $headers = 'From: "' . $comment_author . '" <' . $from_email_addr . ">\r\n" . - 'Reply-To: "' . $comment_author . '" <' . $reply_to_addr . ">\r\n"; + /* + * The email headers here are formatted in a format + * that is the most likely to be accepted by wp_mail(), + * without escaping. + * More info: https://github.com/Automattic/jetpack/pull/19727 + */ + $headers = 'From: ' . $comment_author . ' <' . $from_email_addr . ">\r\n" . + 'Reply-To: ' . $comment_author . ' <' . $reply_to_addr . ">\r\n"; + + /** + * Allow customizing the email headers. + * + * Warning: DO NOT add headers or header data from the form submission without proper + * escaping and validation, or you're liable to allow abusers to use your site to send spam. + * + * Especially DO NOT take email addresses from the form data to add as CC or BCC headers + * without strictly validating each address against a list of allowed addresses. + * + * @module contact-form + * + * @since 10.2.0 + * + * @param string|array $headers Email headers. + * @param string $comment_author Name of the author of the submitted feedback, if provided in form. + * @param string $reply_to_addr Email of the author of the submitted feedback, if provided in form. + * @param string|array $to Array of valid email addresses, or single email address, where the form is sent. + */ + $headers = apply_filters( + 'jetpack_contact_form_email_headers', + $headers, + $comment_author, + $reply_to_addr, + $to + ); + + $all_values['email_marketing_consent'] = $email_marketing_consent; // Build feedback reference $feedback_time = current_time( 'mysql' ); $feedback_title = "{$comment_author} - {$feedback_time}"; $feedback_id = md5( $feedback_title ); - $all_values = array_merge( - $all_values, array( - 'entry_title' => the_title_attribute( 'echo=0' ), - 'entry_permalink' => esc_url( get_permalink( get_the_ID() ) ), - 'feedback_id' => $feedback_id, - ) + $entry_values = array( + 'entry_title' => the_title_attribute( 'echo=0' ), + 'entry_permalink' => esc_url( get_permalink( get_the_ID() ) ), + 'feedback_id' => $feedback_id, ); + $all_values = array_merge( $all_values, $entry_values ); + /** This filter is already documented in modules/contact-form/admin.php */ $subject = apply_filters( 'contact_form_subject', $contact_form_subject, $all_values ); $url = $widget ? home_url( '/' ) : get_permalink( $post->ID ); @@ -2667,8 +2987,14 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { $date_time_format = sprintf( $date_time_format, get_option( 'date_format' ), get_option( 'time_format' ) ); $time = date_i18n( $date_time_format, current_time( 'timestamp' ) ); - // keep a copy of the feedback as a custom post type - $feedback_status = $is_spam === true ? 'spam' : 'publish'; + // Keep a copy of the feedback as a custom post type. + if ( $in_comment_disallowed_list ) { + $feedback_status = 'trash'; + } elseif ( $is_spam ) { + $feedback_status = 'spam'; + } else { + $feedback_status = 'publish'; + } foreach ( (array) $akismet_values as $av_key => $av_value ) { $akismet_values[ $av_key ] = Grunion_Contact_Form_Plugin::strip_tags( $av_value ); @@ -2720,6 +3046,20 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { update_post_meta( $post_id, '_feedback_akismet_values', $this->addslashes_deep( $akismet_values ) ); } + /** + * Fires after the feedback post for the contact form submission has been inserted. + * + * @module contact-form + * + * @since 8.6.0 + * + * @param integer $post_id The post id that contains the contact form data. + * @param array $this->fields An array containg the form's Grunion_Contact_Form_Field objects. + * @param boolean $is_spam Whether the form submission has been identified as spam. + * @param array $entry_values The feedback entry values. + */ + do_action( 'grunion_after_feedback_post_inserted', $post_id, $this->fields, $is_spam, $entry_values ); + $message = self::get_compiled_form( $post_id, $this ); array_push( @@ -2909,7 +3249,7 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { * Add a display name part to an email address * * SpamAssassin doesn't like addresses in HTML messages that are missing display names (e.g., `foo@bar.org` - * instead of `"Foo Bar" <foo@bar.org>`. + * instead of `Foo Bar <foo@bar.org>`. * * @param string $address * @@ -2919,7 +3259,14 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { // If it's just the address, without a display name if ( is_email( $address ) ) { $address_parts = explode( '@', $address ); - $address = sprintf( '"%s" <%s>', $address_parts[0], $address ); + + /* + * The email address format here is formatted in a format + * that is the most likely to be accepted by wp_mail(), + * without escaping. + * More info: https://github.com/Automattic/jetpack/pull/19727 + */ + $address = sprintf( '%s <%s>', $address_parts[0], $address ); } return $address; @@ -3005,7 +3352,8 @@ class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { return addslashes( $value ); } -} + +} // end class Grunion_Contact_Form /** * Class for the contact-field shortcode. @@ -3038,15 +3386,19 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { function __construct( $attributes, $content = null, $form = null ) { $attributes = shortcode_atts( array( - 'label' => null, - 'type' => 'text', - 'required' => false, - 'options' => array(), - 'id' => null, - 'default' => null, - 'values' => null, - 'placeholder' => null, - 'class' => null, + 'label' => null, + 'type' => 'text', + 'required' => false, + 'options' => array(), + 'id' => null, + 'default' => null, + 'values' => null, + 'placeholder' => null, + 'class' => null, + 'width' => null, + 'consenttype' => null, + 'implicitconsentmessage' => null, + 'explicitconsentmessage' => null, ), $attributes, 'contact-field' ); @@ -3155,7 +3507,7 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { switch ( $field_type ) { case 'email': // Make sure the email address is valid - if ( ! is_email( $field_value ) ) { + if ( ! is_string( $field_value ) || ! is_email( $field_value ) ) { /* translators: %s is the name of a form field */ $this->add_error( sprintf( __( '%s requires a valid email address', 'jetpack' ), $field_label ) ); } @@ -3169,7 +3521,7 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { break; default: // Just check for presence of any text - if ( ! strlen( trim( $field_value ) ) ) { + if ( ! is_string( $field_value ) || ! strlen( trim( $field_value ) ) ) { /* translators: %s is the name of a form field */ $this->add_error( sprintf( __( '%s is required', 'jetpack' ), $field_label ) ); } @@ -3206,8 +3558,13 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { $field_label = $this->get_attribute( 'label' ); $field_required = $this->get_attribute( 'required' ); $field_placeholder = $this->get_attribute( 'placeholder' ); + $field_width = $this->get_attribute( 'width' ); $class = 'date' === $field_type ? 'jp-contact-form-date' : $this->get_attribute( 'class' ); + if ( ! empty( $field_width ) ) { + $class .= ' grunion-field-width-' . $field_width; + } + /** * Filters the "class" attribute of the contact form input * @@ -3279,7 +3636,7 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { return apply_filters( 'grunion_contact_form_field_html', $rendered_field, $field_label, ( in_the_loop() ? get_the_ID() : null ) ); } - function render_label( $type = '', $id, $label, $required, $required_field_text ) { + public function render_label( $type, $id, $label, $required, $required_field_text ) { $type_class = $type ? ' ' .$type : ''; return @@ -3366,6 +3723,29 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { return $field; } + /** + * Render the consent field. + * + * @param string $id field id. + * @param string $class html classes (can be set by the admin). + */ + private function render_consent_field( $id, $class ) { + $consent_type = 'explicit' === $this->get_attribute( 'consenttype' ) ? 'explicit' : 'implicit'; + $consent_message = 'explicit' === $consent_type ? $this->get_attribute( 'explicitconsentmessage' ) : $this->get_attribute( 'implicitconsentmessage' ); + + $field = "<label class='grunion-field-label consent consent-" . $consent_type . "'>"; + + if ( 'implicit' === $consent_type ) { + $field .= "\t\t<input aria-hidden='true' type='checkbox' checked name='" . esc_attr( $id ) . "' value='" . esc_attr__( 'Yes', 'jetpack' ) . "' style='display:none;' /> \n"; + } else { + $field .= "\t\t<input type='checkbox' name='" . esc_attr( $id ) . "' value='" . esc_attr__( 'Yes', 'jetpack' ) . "' " . $class . "/> \n"; + } + $field .= "\t\t" . esc_html( $consent_message ); + $field .= "</label>\n"; + $field .= "<div class='clear-form'></div>\n"; + return $field; + } + function render_checkbox_multiple_field( $id, $label, $value, $class, $required, $required_field_text ) { $field = $this->render_label( '', $id, $label, $required, $required_field_text ); foreach ( (array) $this->get_attribute( 'options' ) as $optionIndex => $option ) { @@ -3486,6 +3866,9 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { case 'date': $field .= $this->render_date_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder ); break; + case 'consent': + $field .= $this->render_consent_field( $id, $field_class ); + break; default: // text field $field .= $this->render_default_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder, $type ); break; @@ -3493,6 +3876,7 @@ class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode { $field .= "\t</div>\n"; return $field; } + } add_action( 'init', array( 'Grunion_Contact_Form_Plugin', 'init' ), 9 ); @@ -3588,7 +3972,7 @@ function jetpack_tracks_record_grunion_pre_message_sent( $post_id, $all_values, $event_user = get_userdata( $event_user_id ); } - require_lib( 'tracks/client' ); + jetpack_require_lib( 'tracks/client' ); tracks_record_event( $event_user, $event_name, $event_props ); } else { // If the form was sent by a logged out visitor, record event with Jetpack master user. |