summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/modules/contact-form')
-rw-r--r--plugins/jetpack/modules/contact-form/admin.php894
-rw-r--r--plugins/jetpack/modules/contact-form/class-grunion-contact-form-endpoint.php52
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.css825
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.css764
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.min.css2
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-style-rtl.css613
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-style-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-style.css554
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-style.min.css2
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-ui-rtl.css27
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-ui-rtl.min.css1
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-ui.css26
-rw-r--r--plugins/jetpack/modules/contact-form/css/editor-ui.min.css2
-rw-r--r--plugins/jetpack/modules/contact-form/css/grunion-rtl.css1
-rw-r--r--plugins/jetpack/modules/contact-form/css/grunion.css91
-rw-r--r--plugins/jetpack/modules/contact-form/css/jquery-ui-datepicker.css160
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-contact-form.php3501
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-editor-view.php299
-rw-r--r--plugins/jetpack/modules/contact-form/grunion-form-view.php266
-rw-r--r--plugins/jetpack/modules/contact-form/images/blank-screen-akismet.pngbin0 -> 2270 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/blank-screen-button.pngbin0 -> 1823 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-form-2x.pngbin0 -> 153 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-form.pngbin0 -> 188 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.pngbin0 -> 201 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.pngbin0 -> 207 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover.gifbin0 -> 144 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-field.gifbin0 -> 139 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-option-2x.pngbin0 -> 99 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover-2x.pngbin0 -> 94 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover.gifbin0 -> 73 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/images/grunion-remove-option.gifbin0 -> 73 bytes
-rw-r--r--plugins/jetpack/modules/contact-form/js/editor-view.js284
-rw-r--r--plugins/jetpack/modules/contact-form/js/grunion-admin.js30
-rw-r--r--plugins/jetpack/modules/contact-form/js/grunion-frontend.js3
-rw-r--r--plugins/jetpack/modules/contact-form/js/grunion.js1180
-rw-r--r--plugins/jetpack/modules/contact-form/js/tinymce-plugin-form-button.js37
37 files changed, 9616 insertions, 0 deletions
diff --git a/plugins/jetpack/modules/contact-form/admin.php b/plugins/jetpack/modules/contact-form/admin.php
new file mode 100644
index 00000000..0596f798
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/admin.php
@@ -0,0 +1,894 @@
+<?php
+/**
+ * Add a contact form button to the post composition screen
+ */
+add_action( 'media_buttons', 'grunion_media_button', 999 );
+function grunion_media_button() {
+ global $post_ID, $temp_ID, $pagenow;
+
+ if ( 'press-this.php' === $pagenow ) {
+ return;
+ }
+
+ $iframe_post_id = (int) ( 0 == $post_ID ? $temp_ID : $post_ID );
+ $title = __( 'Add Contact Form', 'jetpack' );
+ $plugin_url = esc_url( GRUNION_PLUGIN_URL );
+ $site_url = esc_url( admin_url( "/admin-ajax.php?post_id={$iframe_post_id}&action=grunion_form_builder&TB_iframe=true&width=768" ) );
+ ?>
+
+ <a id="insert-jetpack-contact-form" class="button thickbox" title="<?php echo esc_attr( $title ); ?>" data-editor="content" href="<?php echo $site_url; ?>&id=add_form">
+ <span class="jetpack-contact-form-icon"></span> <?php echo esc_html( $title ); ?>
+ </a>
+
+ <?php
+}
+
+add_action( 'wp_ajax_grunion_form_builder', 'grunion_display_form_view' );
+
+function grunion_display_form_view() {
+ if ( current_user_can( 'edit_posts' ) ) {
+ require_once GRUNION_PLUGIN_DIR . 'grunion-form-view.php';
+ }
+ exit;
+}
+
+// feedback specific css items
+add_action( 'admin_print_styles', 'grunion_admin_css' );
+function grunion_admin_css() {
+ global $current_screen;
+ if ( is_null( $current_screen ) ) {
+ return;
+ }
+ if ( 'edit-feedback' !== $current_screen->id ) {
+ return;
+ }
+
+ wp_enqueue_script( 'wp-lists' );
+?>
+
+<style type='text/css'>
+.add-new-h2, .view-switch, body.no-js .tablenav select[name^=action], body.no-js #doaction, body.no-js #doaction2 {
+ display: none
+}
+
+.column-feedback_from img {
+ float:left;
+ margin-right:10px;
+ margin-top:3px;
+}
+
+.widefat .column-feedback_from {
+ width: 17%;
+}
+.widefat .column-feedback_date {
+ width: 17%;
+}
+
+.spam a {
+ color: #BC0B0B;
+}
+
+.untrash a {
+ color: #D98500;
+}
+
+.unspam a {
+color: #D98500;
+}
+
+</style>
+
+<?php
+}
+
+/**
+ * Hack a 'Bulk Spam' option for bulk edit in other than spam view
+ * Hack a 'Bulk Delete' option for bulk edit in spam view
+ *
+ * There isn't a better way to do this until
+ * http://core.trac.wordpress.org/changeset/17297 is resolved
+ */
+add_action( 'admin_head', 'grunion_add_bulk_edit_option' );
+function grunion_add_bulk_edit_option() {
+
+ $screen = get_current_screen();
+
+ if ( is_null( $screen ) ) {
+ return;
+ }
+
+ if ( 'edit-feedback' != $screen->id ) {
+ return;
+ }
+
+ // When viewing spam we want to be able to be able to bulk delete
+ // When viewing anything we want to be able to bulk move to spam
+ if ( isset( $_GET['post_status'] ) && 'spam' == $_GET['post_status'] ) {
+ // Create Delete Permanently bulk item
+ $option_val = 'delete';
+ $option_txt = __( 'Delete Permanently', 'jetpack' );
+ $pseudo_selector = 'last-child';
+
+ } else {
+ // Create Mark Spam bulk item
+ $option_val = 'spam';
+ $option_txt = __( 'Mark as Spam', 'jetpack' );
+ $pseudo_selector = 'first-child';
+ }
+
+ ?>
+ <script type="text/javascript">
+ jQuery(document).ready(function($) {
+ $('#posts-filter .actions select').filter('[name=action], [name=action2]').find('option:<?php echo $pseudo_selector; ?>').after('<option value="<?php echo $option_val; ?>"><?php echo esc_attr( $option_txt ); ?></option>' );
+ })
+ </script>
+ <?php
+}
+
+/**
+ * Hack an 'Empty Spam' button to spam view
+ *
+ * Leverages core's delete_all functionality
+ */
+add_action( 'admin_head', 'grunion_add_empty_spam_button' );
+function grunion_add_empty_spam_button() {
+ $screen = get_current_screen();
+
+ if ( is_null( $screen ) ) {
+ return;
+ }
+
+ // Only add to feedback, only to spam view
+ if ( 'edit-feedback' != $screen->id
+ || empty( $_GET['post_status'] )
+ || 'spam' !== $_GET['post_status'] ) {
+ return;
+ }
+
+ // Get HTML for the button
+ $button_html = wp_nonce_field( 'bulk-destroy', '_destroy_nonce', true, false );
+ $button_html .= get_submit_button( __( 'Empty Spam', 'jetpack' ), 'apply', 'delete_all', false );
+
+ // Add the button next to the filter button via js
+ ?>
+ <script type="text/javascript">
+ jQuery(document).ready(function($) {
+ $('#posts-filter #post-query-submit').after('<?php echo $button_html; ?>' );
+ })
+ </script>
+ <?php
+}
+
+/**
+ * Handle a bulk spam report
+ */
+add_action( 'admin_init', 'grunion_handle_bulk_spam' );
+function grunion_handle_bulk_spam() {
+ global $pagenow;
+
+ if ( 'edit.php' != $pagenow
+ || ( empty( $_REQUEST['post_type'] ) || 'feedback' != $_REQUEST['post_type'] ) ) {
+ return;
+ }
+
+ // Slip in a success message
+ if ( ! empty( $_REQUEST['message'] ) && 'marked-spam' == $_REQUEST['message'] ) {
+ add_action( 'admin_notices', 'grunion_message_bulk_spam' );
+ }
+
+ if ( ( empty( $_REQUEST['action'] ) || 'spam' != $_REQUEST['action'] ) && ( empty( $_REQUEST['action2'] ) || 'spam' != $_REQUEST['action2'] ) ) {
+ return;
+ }
+
+ check_admin_referer( 'bulk-posts' );
+
+ if ( empty( $_REQUEST['post'] ) ) {
+ wp_safe_redirect( wp_get_referer() );
+ exit;
+ }
+
+ $post_ids = array_map( 'intval', $_REQUEST['post'] );
+
+ foreach ( $post_ids as $post_id ) {
+ if ( ! current_user_can( 'edit_page', $post_id ) ) {
+ wp_die( __( 'You are not allowed to manage this item.', 'jetpack' ) );
+ }
+
+ $post = array(
+ 'ID' => $post_id,
+ 'post_status' => 'spam',
+ );
+ $akismet_values = get_post_meta( $post_id, '_feedback_akismet_values', true );
+ wp_update_post( $post );
+
+ /**
+ * Fires after a comment has been marked by Akismet.
+ *
+ * Typically this means the comment is spam.
+ *
+ * @module contact-form
+ *
+ * @since 2.2.0
+ *
+ * @param string $comment_status Usually is 'spam', otherwise 'ham'.
+ * @param array $akismet_values From '_feedback_akismet_values' in comment meta
+ */
+ do_action( 'contact_form_akismet', 'spam', $akismet_values );
+ }
+
+ $redirect_url = add_query_arg( 'message', 'marked-spam', wp_get_referer() );
+ wp_safe_redirect( $redirect_url );
+ exit;
+}
+
+function grunion_message_bulk_spam() {
+ echo '<div class="updated"><p>' . __( 'Feedback(s) marked as spam', 'jetpack' ) . '</p></div>';
+}
+
+// remove admin UI parts that we don't support in feedback management
+add_action( 'admin_menu', 'grunion_admin_menu' );
+function grunion_admin_menu() {
+ global $menu, $submenu;
+ unset( $submenu['edit.php?post_type=feedback'] );
+}
+
+add_filter( 'bulk_actions-edit-feedback', 'grunion_admin_bulk_actions' );
+function grunion_admin_bulk_actions( $actions ) {
+ global $current_screen;
+ if ( 'edit-feedback' != $current_screen->id ) {
+ return $actions;
+ }
+
+ unset( $actions['edit'] );
+ return $actions;
+}
+
+add_filter( 'views_edit-feedback', 'grunion_admin_view_tabs' );
+function grunion_admin_view_tabs( $views ) {
+ global $current_screen;
+ if ( 'edit-feedback' != $current_screen->id ) {
+ return $views;
+ }
+
+ unset( $views['publish'] );
+
+ preg_match( '|post_type=feedback\'( class="current")?\>(.*)\<span class=|', $views['all'], $match );
+ if ( ! empty( $match[2] ) ) {
+ $views['all'] = str_replace( $match[2], __( 'Messages', 'jetpack' ) . ' ', $views['all'] );
+ }
+
+ return $views;
+}
+
+add_filter( 'manage_feedback_posts_columns', 'grunion_post_type_columns_filter' );
+function grunion_post_type_columns_filter( $cols ) {
+ $cols = array(
+ 'cb' => '<input type="checkbox" />',
+ 'feedback_from' => __( 'From', 'jetpack' ),
+ 'feedback_message' => __( 'Message', 'jetpack' ),
+ 'feedback_date' => __( 'Date', 'jetpack' ),
+ );
+
+ return $cols;
+}
+
+add_action( 'manage_posts_custom_column', 'grunion_manage_post_columns', 10, 2 );
+function grunion_manage_post_columns( $col, $post_id ) {
+ global $post;
+
+ /**
+ * Only call parse_fields_from_content if we're dealing with a Grunion custom column.
+ */
+ if ( ! in_array( $col, array( 'feedback_date', 'feedback_from', 'feedback_message' ) ) ) {
+ return;
+ }
+
+ $content_fields = Grunion_Contact_Form_Plugin::parse_fields_from_content( $post_id );
+
+ switch ( $col ) {
+ case 'feedback_from':
+ $author_name = isset( $content_fields['_feedback_author'] ) ? $content_fields['_feedback_author'] : '';
+ $author_email = isset( $content_fields['_feedback_author_email'] ) ? $content_fields['_feedback_author_email'] : '';
+ $author_url = isset( $content_fields['_feedback_author_url'] ) ? $content_fields['_feedback_author_url'] : '';
+ $author_ip = isset( $content_fields['_feedback_ip'] ) ? $content_fields['_feedback_ip'] : '';
+ $form_url = isset( $post->post_parent ) ? get_permalink( $post->post_parent ) : null;
+
+ $author_name_line = '';
+ if ( ! empty( $author_name ) ) {
+ if ( ! empty( $author_email ) ) {
+ $author_name_line = get_avatar( $author_email, 32 );
+ }
+
+ $author_name_line .= sprintf( '<strong>%s</strong><br />', esc_html( $author_name ) );
+ }
+
+ $author_email_line = '';
+ if ( ! empty( $author_email ) ) {
+ $author_email_line = sprintf( "<a href='%1\$s' target='_blank'>%2\$s</a><br />", esc_url( 'mailto:' . $author_email ), esc_html( $author_email ) );
+ }
+
+ $author_url_line = '';
+ if ( ! empty( $author_url ) ) {
+ $author_url_line = sprintf( "<a href='%1\$s'>%1\$s</a><br />", esc_url( $author_url ) );
+ }
+
+ echo $author_name_line;
+ echo $author_email_line;
+ echo $author_url_line;
+ echo "<a href='edit.php?post_type=feedback&s=" . urlencode( $author_ip );
+ echo "&mode=detail'>" . esc_html( $author_ip ) . '</a><br />';
+ if ( $form_url ) {
+ echo '<a href="' . esc_url( $form_url ) . '">' . esc_html( $form_url ) . '</a>';
+ }
+ break;
+
+ case 'feedback_message':
+ $post_type_object = get_post_type_object( $post->post_type );
+ if ( isset( $content_fields['_feedback_subject'] ) ) {
+ echo '<strong>';
+ echo esc_html( $content_fields['_feedback_subject'] );
+ echo '</strong>';
+ echo '<br />';
+ }
+ echo sanitize_text_field( get_the_content( '' ) );
+ echo '<br />';
+
+ $extra_fields = get_post_meta( $post_id, '_feedback_extra_fields', true );
+ if ( ! empty( $extra_fields ) ) {
+ echo '<br /><hr />';
+ echo '<table cellspacing="0" cellpadding="0" style="">' . "\n";
+ foreach ( (array) $extra_fields as $k => $v ) {
+ // Remove prefix from exta fields
+ echo "<tr><td align='right'><b>" . esc_html( preg_replace( '#^\d+_#', '', $k ) ) . '</b></td><td>' . sanitize_text_field( $v ) . "</td></tr>\n";
+ }
+ echo '</table>';
+ }
+
+ echo '<div class="row-actions">';
+ if ( $post->post_status == 'trash' ) {
+ echo '<span class="untrash" id="feedback-restore-' . $post_id;
+ echo '"><a title="';
+ echo esc_attr__( 'Restore this item from the Trash', 'jetpack' );
+ echo '" href="' . wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $post->ID ) ), 'untrash-' . $post->post_type . '_' . $post->ID );
+ echo '">' . __( 'Restore', 'jetpack' ) . '</a></span> | ';
+
+ echo "<span class='delete'> <a class='submitdelete' title='";
+ echo esc_attr( __( 'Delete this item permanently', 'jetpack' ) );
+ echo "' href='" . get_delete_post_link( $post->ID, '', true );
+ echo "'>" . __( 'Delete Permanently', 'jetpack' ) . '</a></span>';
+?>
+
+<script>
+jQuery(document).ready(function($) {
+$('#feedback-restore-<?php echo $post_id; ?>').click(function(e) {
+ e.preventDefault();
+ $.post(ajaxurl, {
+ action: 'grunion_ajax_spam',
+ post_id: '<?php echo $post_id; ?>',
+ make_it: 'publish',
+ sub_menu: jQuery('.subsubsub .current').attr('href'),
+ _ajax_nonce: '<?php echo wp_create_nonce( 'grunion-post-status-' . $post_id ); ?>'
+ },
+ function(r) {
+ $('#post-<?php echo $post_id; ?>')
+ .css({backgroundColor: '#59C859'})
+ .fadeOut(350, function() {
+ $(this).remove();
+ $('.subsubsub').html(r);
+ });
+ }
+ );
+});
+});
+</script>
+
+<?php
+ } elseif ( $post->post_status == 'publish' ) {
+ echo '<span class="spam" id="feedback-spam-' . $post_id;
+ echo '"><a title="';
+ echo __( 'Mark this message as spam', 'jetpack' );
+ echo '" href="' . wp_nonce_url( admin_url( 'admin-ajax.php?post_id=' . $post_id . '&amp;action=spam' ), 'spam-feedback_' . $post_id );
+ echo '">Spam</a></span>';
+ echo ' | ';
+
+ echo '<span class="delete" id="feedback-trash-' . $post_id;
+ echo '">';
+ echo '<a class="submitdelete" title="' . esc_attr__( 'Trash', 'jetpack' );
+ echo '" href="' . get_delete_post_link( $post_id );
+ echo '">' . __( 'Trash', 'jetpack' ) . '</a></span>';
+
+?>
+
+<script>
+jQuery(document).ready( function($) {
+ $('#feedback-spam-<?php echo $post_id; ?>').click( function(e) {
+ e.preventDefault();
+ $.post( ajaxurl, {
+ action: 'grunion_ajax_spam',
+ post_id: '<?php echo $post_id; ?>',
+ make_it: 'spam',
+ sub_menu: jQuery('.subsubsub .current').attr('href'),
+ _ajax_nonce: '<?php echo wp_create_nonce( 'grunion-post-status-' . $post_id ); ?>'
+ },
+ function( r ) {
+ $('#post-<?php echo $post_id; ?>')
+ .css( {backgroundColor:'#FF7979'} )
+ .fadeOut(350, function() {
+ $(this).remove();
+ $('.subsubsub').html(r);
+ });
+ });
+ });
+
+ $('#feedback-trash-<?php echo $post_id; ?>').click(function(e) {
+ e.preventDefault();
+ $.post(ajaxurl, {
+ action: 'grunion_ajax_spam',
+ post_id: '<?php echo $post_id; ?>',
+ make_it: 'trash',
+ sub_menu: jQuery('.subsubsub .current').attr('href'),
+ _ajax_nonce: '<?php echo wp_create_nonce( 'grunion-post-status-' . $post_id ); ?>'
+ },
+ function(r) {
+ $('#post-<?php echo $post_id; ?>')
+ .css({backgroundColor: '#FF7979'})
+ .fadeOut(350, function() {
+ $(this).remove();
+ $('.subsubsub').html(r);
+ });
+ }
+ );
+ });
+});
+</script>
+
+<?php
+ } elseif ( $post->post_status == 'spam' ) {
+ echo '<span class="unspam unapprove" id="feedback-ham-' . $post_id;
+ echo '"><a title="';
+ echo __( 'Mark this message as NOT spam', 'jetpack' );
+ echo '" href="">Not Spam</a></span>';
+ echo ' | ';
+
+ echo "<span class='delete' id='feedback-trash-" . $post_id;
+ echo "'> <a class='submitdelete' title='";
+ echo esc_attr( __( 'Delete this item permanently', 'jetpack' ) );
+ echo "' href='" . get_delete_post_link( $post->ID, '', true );
+ echo "'>" . __( 'Delete Permanently', 'jetpack' ) . '</a></span>';
+?>
+
+<script>
+jQuery(document).ready( function($) {
+ $('#feedback-ham-<?php echo $post_id; ?>').click( function(e) {
+ e.preventDefault();
+ $.post( ajaxurl, {
+ action: 'grunion_ajax_spam',
+ post_id: '<?php echo $post_id; ?>',
+ make_it: 'ham',
+ sub_menu: jQuery('.subsubsub .current').attr('href'),
+ _ajax_nonce: '<?php echo wp_create_nonce( 'grunion-post-status-' . $post_id ); ?>'
+ },
+ function( r ) {
+ $('#post-<?php echo $post_id; ?>')
+ .css( {backgroundColor:'#59C859'} )
+ .fadeOut(350, function() {
+ $(this).remove();
+ $('.subsubsub').html(r);
+ });
+ });
+ });
+});
+</script>
+
+<?php
+ }
+ break;
+
+ case 'feedback_date':
+ $date_time_format = _x( '%1$s \a\t %2$s', '{$date_format} \a\t {$time_format}', 'jetpack' );
+ $date_time_format = sprintf( $date_time_format, get_option( 'date_format' ), get_option( 'time_format' ) );
+ $time = date_i18n( $date_time_format, get_the_time( 'U' ) );
+
+ echo $time;
+ break;
+ }
+}
+
+function grunion_esc_attr( $attr ) {
+ $out = esc_attr( $attr );
+ // we also have to entity-encode square brackets so they don't interfere with the shortcode parser
+ // FIXME: do this better - just stripping out square brackets for now since they mysteriously keep reappearing
+ $out = str_replace( '[', '', $out );
+ $out = str_replace( ']', '', $out );
+ return $out;
+}
+
+function grunion_sort_objects( $a, $b ) {
+ if ( isset( $a['order'] ) && isset( $b['order'] ) ) {
+ return $a['order'] - $b['order'];
+ }
+ return 0;
+}
+
+// take an array of field types from the form builder, and construct a shortcode form
+// returns both the shortcode form, and HTML markup representing a preview of the form
+function grunion_ajax_shortcode() {
+ check_ajax_referer( 'grunion_shortcode' );
+
+ if ( ! current_user_can( 'edit_posts' ) ) {
+ die( '-1' );
+ }
+
+ $attributes = array();
+
+ foreach ( array( 'subject', 'to' ) as $attribute ) {
+ if ( isset( $_POST[ $attribute ] ) && strlen( $_POST[ $attribute ] ) ) {
+ $attributes[ $attribute ] = stripslashes( $_POST[ $attribute ] );
+ }
+ }
+
+ if ( is_array( $_POST['fields'] ) ) {
+ $fields = stripslashes_deep( $_POST['fields'] );
+ usort( $fields, 'grunion_sort_objects' );
+
+ $field_shortcodes = array();
+
+ foreach ( $fields as $field ) {
+ $field_attributes = array();
+
+ if ( isset( $field['required'] ) && 'true' === $field['required'] ) {
+ $field_attributes['required'] = 'true';
+ }
+
+ foreach ( array( 'options', 'label', 'type' ) as $attribute ) {
+ if ( isset( $field[ $attribute ] ) ) {
+ $field_attributes[ $attribute ] = $field[ $attribute ];
+ }
+ }
+
+ $field_shortcodes[] = new Grunion_Contact_Form_Field( $field_attributes );
+ }
+ }
+
+ $grunion = new Grunion_Contact_Form( $attributes, $field_shortcodes );
+
+ die( "\n$grunion\n" );
+}
+
+// takes a post_id, extracts the contact-form shortcode from that post (if there is one), parses it,
+// and constructs a json object representing its contents and attributes
+function grunion_ajax_shortcode_to_json() {
+ global $post, $grunion_form;
+
+ check_ajax_referer( 'grunion_shortcode_to_json' );
+
+ if ( ! empty( $_POST['post_id'] ) && ! current_user_can( 'edit_post', $_POST['post_id'] ) ) {
+ die( '-1' );
+ } elseif ( ! current_user_can( 'edit_posts' ) ) {
+ die( '-1' );
+ }
+
+ if ( ! isset( $_POST['content'] ) || ! is_numeric( $_POST['post_id'] ) ) {
+ die( '-1' );
+ }
+
+ $content = stripslashes( $_POST['content'] );
+
+ // doesn't look like a post with a [contact-form] already.
+ if ( false === has_shortcode( $content, 'contact-form' ) ) {
+ die( '' );
+ }
+
+ $post = get_post( $_POST['post_id'] );
+
+ do_shortcode( $content );
+
+ $grunion = Grunion_Contact_Form::$last;
+
+ $out = array(
+ 'to' => '',
+ 'subject' => '',
+ 'fields' => array(),
+ );
+
+ foreach ( $grunion->fields as $field ) {
+ $out['fields'][ $field->get_attribute( 'id' ) ] = $field->attributes;
+ }
+
+ $to = $grunion->get_attribute( 'to' );
+ $subject = $grunion->get_attribute( 'subject' );
+ foreach ( array( 'to', 'subject' ) as $attribute ) {
+ $value = $grunion->get_attribute( $attribute );
+ if ( isset( $grunion->defaults[ $attribute ] ) && $value == $grunion->defaults[ $attribute ] ) {
+ $value = '';
+ }
+ $out[ $attribute ] = $value;
+ }
+
+ die( json_encode( $out ) );
+}
+
+
+add_action( 'wp_ajax_grunion_shortcode', 'grunion_ajax_shortcode' );
+add_action( 'wp_ajax_grunion_shortcode_to_json', 'grunion_ajax_shortcode_to_json' );
+
+
+// process row-action spam/not spam clicks
+add_action( 'wp_ajax_grunion_ajax_spam', 'grunion_ajax_spam' );
+function grunion_ajax_spam() {
+ global $wpdb;
+
+ if ( empty( $_POST['make_it'] ) ) {
+ return;
+ }
+
+ $post_id = (int) $_POST['post_id'];
+ check_ajax_referer( 'grunion-post-status-' . $post_id );
+ if ( ! current_user_can( 'edit_page', $post_id ) ) {
+ wp_die( __( 'You are not allowed to manage this item.', 'jetpack' ) );
+ }
+
+ require_once dirname( __FILE__ ) . '/grunion-contact-form.php';
+
+ $current_menu = '';
+ if ( isset( $_POST['sub_menu'] ) && preg_match( '|post_type=feedback|', $_POST['sub_menu'] ) ) {
+ if ( preg_match( '|post_status=spam|', $_POST['sub_menu'] ) ) {
+ $current_menu = 'spam';
+ } elseif ( preg_match( '|post_status=trash|', $_POST['sub_menu'] ) ) {
+ $current_menu = 'trash';
+ } else {
+ $current_menu = 'messages';
+ }
+ }
+
+ $post = get_post( $post_id );
+ $post_type_object = get_post_type_object( $post->post_type );
+ $akismet_values = get_post_meta( $post_id, '_feedback_akismet_values', true );
+ if ( $_POST['make_it'] == 'spam' ) {
+ $post->post_status = 'spam';
+ $status = wp_insert_post( $post );
+ wp_transition_post_status( 'spam', 'publish', $post );
+
+ /** This action is already documented in modules/contact-form/admin.php */
+ do_action( 'contact_form_akismet', 'spam', $akismet_values );
+ } elseif ( $_POST['make_it'] == 'ham' ) {
+ $post->post_status = 'publish';
+ $status = wp_insert_post( $post );
+ wp_transition_post_status( 'publish', 'spam', $post );
+
+ /** This action is already documented in modules/contact-form/admin.php */
+ do_action( 'contact_form_akismet', 'ham', $akismet_values );
+
+ $comment_author_email = $reply_to_addr = $message = $to = $headers = false;
+ $blog_url = wp_parse_url( site_url() );
+
+ // resend the original email
+ $email = get_post_meta( $post_id, '_feedback_email', true );
+ $content_fields = Grunion_Contact_Form_Plugin::parse_fields_from_content( $post_id );
+
+ if ( ! empty( $email ) && ! empty( $content_fields ) ) {
+ if ( isset( $content_fields['_feedback_author_email'] ) ) {
+ $comment_author_email = $content_fields['_feedback_author_email'];
+ }
+
+ if ( isset( $email['to'] ) ) {
+ $to = $email['to'];
+ }
+
+ if ( isset( $email['message'] ) ) {
+ $message = $email['message'];
+ }
+
+ if ( isset( $email['headers'] ) ) {
+ $headers = $email['headers'];
+ } else {
+ $headers = 'From: "' . $content_fields['_feedback_author'] . '" <wordpress@' . $blog_url['host'] . ">\r\n";
+
+ if ( ! empty( $comment_author_email ) ) {
+ $reply_to_addr = $comment_author_email;
+ } elseif ( is_array( $to ) ) {
+ $reply_to_addr = $to[0];
+ }
+
+ if ( $reply_to_addr ) {
+ $headers .= 'Reply-To: "' . $content_fields['_feedback_author'] . '" <' . $reply_to_addr . ">\r\n";
+ }
+
+ $headers .= 'Content-Type: text/plain; charset="' . get_option( 'blog_charset' ) . '"';
+ }
+
+ /**
+ * Filters the subject of the email sent after a contact form submission.
+ *
+ * @module contact-form
+ *
+ * @since 3.0.0
+ *
+ * @param string $content_fields['_feedback_subject'] Feedback's subject line.
+ * @param array $content_fields['_feedback_all_fields'] Feedback's data from old fields.
+ */
+ $subject = apply_filters( 'contact_form_subject', $content_fields['_feedback_subject'], $content_fields['_feedback_all_fields'] );
+
+ Grunion_Contact_Form::wp_mail( $to, $subject, $message, $headers );
+ }
+ } elseif ( $_POST['make_it'] == 'publish' ) {
+ if ( ! current_user_can( $post_type_object->cap->delete_post, $post_id ) ) {
+ wp_die( __( 'You are not allowed to move this item out of the Trash.', 'jetpack' ) );
+ }
+
+ if ( ! wp_untrash_post( $post_id ) ) {
+ wp_die( __( 'Error in restoring from Trash.', 'jetpack' ) );
+ }
+ } elseif ( $_POST['make_it'] == 'trash' ) {
+ if ( ! current_user_can( $post_type_object->cap->delete_post, $post_id ) ) {
+ wp_die( __( 'You are not allowed to move this item to the Trash.', 'jetpack' ) );
+ }
+
+ if ( ! wp_trash_post( $post_id ) ) {
+ wp_die( __( 'Error in moving to Trash.', 'jetpack' ) );
+ }
+ }
+
+ $sql = "
+ SELECT post_status,
+ COUNT( * ) AS post_count
+ FROM `{$wpdb->posts}`
+ WHERE post_type = 'feedback'
+ GROUP BY post_status
+ ";
+ $status_count = (array) $wpdb->get_results( $sql, ARRAY_A );
+
+ $status = array();
+ $status_html = '';
+ foreach ( $status_count as $i => $row ) {
+ $status[ $row['post_status'] ] = $row['post_count'];
+ }
+
+ if ( isset( $status['publish'] ) ) {
+ $status_html .= '<li><a href="edit.php?post_type=feedback"';
+ if ( $current_menu == 'messages' ) {
+ $status_html .= ' class="current"';
+ }
+
+ $status_html .= '>' . __( 'Messages', 'jetpack' ) . ' <span class="count">';
+ $status_html .= '(' . number_format( $status['publish'] ) . ')';
+ $status_html .= '</span></a> |</li>';
+ }
+
+ if ( isset( $status['trash'] ) ) {
+ $status_html .= '<li><a href="edit.php?post_status=trash&amp;post_type=feedback"';
+ if ( $current_menu == 'trash' ) {
+ $status_html .= ' class="current"';
+ }
+
+ $status_html .= '>' . __( 'Trash', 'jetpack' ) . ' <span class="count">';
+ $status_html .= '(' . number_format( $status['trash'] ) . ')';
+ $status_html .= '</span></a>';
+ if ( isset( $status['spam'] ) ) {
+ $status_html .= ' |';
+ }
+ $status_html .= '</li>';
+ }
+
+ if ( isset( $status['spam'] ) ) {
+ $status_html .= '<li><a href="edit.php?post_status=spam&amp;post_type=feedback"';
+ if ( $current_menu == 'spam' ) {
+ $status_html .= ' class="current"';
+ }
+
+ $status_html .= '>' . __( 'Spam', 'jetpack' ) . ' <span class="count">';
+ $status_html .= '(' . number_format( $status['spam'] ) . ')';
+ $status_html .= '</span></a></li>';
+ }
+
+ echo $status_html;
+ exit;
+}
+
+/**
+ * Add the scripts that will add the "Check for Spam" button to the Feedbacks dashboard page.
+ */
+function grunion_enable_spam_recheck() {
+ if ( ! defined( 'AKISMET_VERSION' ) ) {
+ return;
+ }
+
+ $screen = get_current_screen();
+
+ // Only add to feedback, only to non-spam view
+ if ( 'edit-feedback' != $screen->id || ( ! empty( $_GET['post_status'] ) && 'spam' == $_GET['post_status'] ) ) {
+ return;
+ }
+
+ // Add the scripts that handle the spam check event.
+ wp_register_script(
+ 'grunion-admin',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion-admin.min.js',
+ 'modules/contact-form/js/grunion-admin.js'
+ ),
+ array( 'jquery' )
+ );
+ wp_enqueue_script( 'grunion-admin' );
+
+ wp_enqueue_style( 'grunion.css' );
+
+ // Add the actual "Check for Spam" button.
+ add_action( 'admin_head', 'grunion_check_for_spam_button' );
+}
+
+add_action( 'admin_enqueue_scripts', 'grunion_enable_spam_recheck' );
+
+/**
+ * Add the "Check for Spam" button to the Feedbacks dashboard page.
+ */
+function grunion_check_for_spam_button() {
+ // Get HTML for the button
+ $button_html = get_submit_button(
+ __( 'Check for Spam', 'jetpack' ),
+ 'secondary',
+ 'jetpack-check-feedback-spam',
+ false,
+ array( 'class' => 'jetpack-check-feedback-spam' )
+ );
+ $button_html .= '<span class="jetpack-check-feedback-spam-spinner"></span>';
+
+ // Add the button next to the filter button via js
+ ?>
+ <script type="text/javascript">
+ jQuery( function( $ ) {
+ $( '#posts-filter #post-query-submit' ).after( '<?php echo $button_html; ?>' );
+ } );
+ </script>
+ <?php
+}
+
+/**
+ * Recheck all approved feedbacks for spam.
+ */
+function grunion_recheck_queue() {
+ global $wpdb;
+
+ $query = 'post_type=feedback&post_status=publish';
+
+ if ( isset( $_POST['limit'], $_POST['offset'] ) ) {
+ $query .= '&posts_per_page=' . intval( $_POST['limit'] ) . '&offset=' . intval( $_POST['offset'] );
+ }
+
+ $approved_feedbacks = get_posts( $query );
+
+ foreach ( $approved_feedbacks as $feedback ) {
+ $meta = get_post_meta( $feedback->ID, '_feedback_akismet_values', true );
+
+ /**
+ * Filter whether the submitted feedback is considered as spam.
+ *
+ * @module contact-form
+ *
+ * @since 3.4.0
+ *
+ * @param bool false Is the submitted feedback spam? Default to false.
+ * @param array $meta Feedack values returned by the Akismet plugin.
+ */
+ $is_spam = apply_filters( 'jetpack_contact_form_is_spam', false, $meta );
+
+ if ( $is_spam ) {
+ wp_update_post(
+ array(
+ 'ID' => $feedback->ID,
+ 'post_status' => 'spam',
+ )
+ );
+ /** This action is already documented in modules/contact-form/admin.php */
+ do_action( 'contact_form_akismet', 'spam', $meta );
+ }
+ }
+
+ wp_send_json(
+ array(
+ 'processed' => count( $approved_feedbacks ),
+ )
+ );
+}
+
+add_action( 'wp_ajax_grunion_recheck_queue', 'grunion_recheck_queue' );
diff --git a/plugins/jetpack/modules/contact-form/class-grunion-contact-form-endpoint.php b/plugins/jetpack/modules/contact-form/class-grunion-contact-form-endpoint.php
new file mode 100644
index 00000000..ba2785a6
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/class-grunion-contact-form-endpoint.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * Plugin Name: Feedback CPT Permissions over-ride
+ */
+
+if ( class_exists( 'WP_REST_Posts_Controller' ) ) {
+
+ /**
+ * Class Grunion_Contact_Form_Endpoint
+ * Used as 'rest_controller_class' parameter when 'feedback' post type is registered in modules/contact-form/grunion-contact-form.php.
+ */
+ class Grunion_Contact_Form_Endpoint extends WP_REST_Posts_Controller {
+ /**
+ * Check whether a given request has proper authorization to view feedback items.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_items_permissions_check( $request ) {
+ if ( ! is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) ) {
+ return new WP_Error(
+ 'rest_cannot_view',
+ esc_html__( 'Sorry, you cannot view this resource.', 'jetpack' ),
+ array( 'status' => 401 )
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * Check whether a given request has proper authorization to view feedback item.
+ *
+ * @param WP_REST_Request $request Full details about the request.
+ * @return WP_Error|boolean
+ */
+ public function get_item_permissions_check( $request ) {
+ if ( ! is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) ) {
+ return new WP_Error(
+ 'rest_cannot_view',
+ esc_html__( 'Sorry, you cannot view this resource.', 'jetpack' ),
+ array( 'status' => 401 )
+ );
+ }
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.css b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.css
new file mode 100644
index 00000000..1cfbf3ce
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.css
@@ -0,0 +1,825 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+/* ==========================================================================
+** Normalize
+** ======================================================================== */
+
+html {
+ direction: rtl;
+}
+
+body {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
+ font-size: 16px;
+ line-height: 1.4em;
+ margin: 0;
+}
+
+/* Links */
+a,
+a:visited {
+ color: #0087be;
+ text-decoration: none;
+}
+
+a:hover,
+a:focus,
+a:active {
+ color: $link-highlight;
+}
+
+/* ==========================================================================
+** Card
+** ======================================================================= */
+
+.card,
+body {
+ display: block;
+ position: relative;
+ margin: 0 auto 10px auto;
+ padding: 16px;
+ box-sizing: border-box;
+ background: white;
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+body {
+ margin: 0;
+ background: #f5f5f5;
+}
+
+.card:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.card:hover,
+.card:focus {
+ box-shadow: 0 0 0 1px #999, 0 1px 2px #e9eff3;
+}
+
+.card .delete-field {
+ display: block;
+ float: left;
+}
+
+@media ( min-width: 481px ) {
+ .card {
+ margin-bottom: 16px;
+ padding: 24px;
+ }
+ body {
+ padding: 24px;
+ }
+}
+
+.card.is-compact {
+ margin-bottom: 1px;
+}
+
+@media ( min-width: 481px ) {
+ .card.is-compact {
+ margin-bottom: 1px;
+ padding: 16px 24px;
+ }
+}
+
+.card > div {
+ margin-top: 24px;
+}
+
+.card > div:first-child {
+ margin-top: 0;
+}
+
+
+/* ==========================================================================
+** Labels
+** ======================================================================= */
+
+label {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 5px;
+ margin-top: 8px;
+}
+
+label:first-of-type {
+ margin-top: 4px;
+}
+
+
+/* ==========================================================================
+** Text Inputs
+** ======================================================================= */
+
+input[type="text"],
+input[type="tel"],
+input[type="email"],
+input[type="url"] {
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+input[type="text"]:-ms-input-placeholder,
+input[type="tel"]:-ms-input-placeholder,
+input[type="email"]:-ms-input-placeholder,
+input[type="url"]:-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]::-ms-input-placeholder,
+input[type="tel"]::-ms-input-placeholder,
+input[type="email"]::-ms-input-placeholder,
+input[type="url"]::-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]::placeholder,
+input[type="tel"]::placeholder,
+input[type="email"]::placeholder,
+input[type="url"]::placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]:hover,
+input[type="tel"]:hover,
+input[type="email"]:hover,
+input[type="url"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="text"]:focus,
+input[type="tel"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="text"]:focus::-ms-clear,
+input[type="tel"]:focus::-ms-clear,
+input[type="email"]:focus::-ms-clear,
+input[type="url"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="text"]:disabled,
+input[type="tel"]:disabled,
+input[type="email"]:disabled,
+input[type="url"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="text"]:disabled:hover,
+input[type="tel"]:disabled:hover,
+input[type="email"]:disabled:hover,
+input[type="url"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="text"]:disabled:-ms-input-placeholder,
+input[type="tel"]:disabled:-ms-input-placeholder,
+input[type="email"]:disabled:-ms-input-placeholder,
+input[type="url"]:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="text"]:disabled::-ms-input-placeholder,
+input[type="tel"]:disabled::-ms-input-placeholder,
+input[type="email"]:disabled::-ms-input-placeholder,
+input[type="url"]:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="text"]:disabled::placeholder,
+input[type="tel"]:disabled::placeholder,
+input[type="email"]:disabled::placeholder,
+input[type="url"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Textareas
+** ======================================================================= */
+
+textarea {
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ height: 92px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+textarea:-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+textarea::-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+textarea::placeholder {
+ color: #87a6bc;
+}
+
+textarea:hover {
+ border-color: #a8bece;
+}
+
+textarea:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+textarea:focus::-ms-clear {
+ display: none;
+}
+
+textarea:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+textarea:disabled:hover {
+ cursor: default;
+}
+
+textarea:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+textarea:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+textarea:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Checkboxes
+** ======================================================================= */
+
+.checkbox,
+input[type="checkbox"] {
+ -webkit-appearance: none;
+ display: inline-block;
+ box-sizing: border-box;
+ margin: 2px 0 0;
+ padding: 7px 14px;
+ width: 16px;
+ height: 16px;
+ float: right;
+ outline: 0;
+ padding: 0;
+ box-shadow: none;
+ background-color: #fff;
+ border: 1px solid #c8d7e1;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 0;
+ text-align: center;
+ vertical-align: middle;
+ -moz-appearance: none;
+ appearance: none;
+ transition: all .15s ease-in-out;
+ clear: none;
+ cursor: pointer;
+}
+
+.checkbox:checked:before,
+input[type="checkbox"]:checked:before {
+ content: '\f147';
+ font-family: Dashicons;
+ margin: -3px -4px 0 0;
+ float: right;
+ display: inline-block;
+ vertical-align: middle;
+ width: 16px;
+ font-size: 20px;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ speak: none;
+ color: #00aadc;
+}
+
+.checkbox:disabled:checked:before,
+input[type="checkbox"]:disabled:checked:before {
+ color: #a8bece;
+}
+
+.checkbox:hover,
+input[type="checkbox"]:hover {
+ border-color: #a8bece;
+}
+
+.checkbox:focus,
+input[type="checkbox"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+.checkbox:disabled,
+input[type="checkbox"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+}
+
+.checkbox:disabled:hover,
+input[type="checkbox"]:disabled:hover {
+ cursor: default;
+}
+
+.checkbox + span,
+input[type="checkbox"] + span {
+ display: block;
+ font-weight: normal;
+ margin-right: 24px;
+}
+
+
+/* ==========================================================================
+** Radio buttons
+** ======================================================================== */
+
+.radio-button,
+input[type=radio] {
+ color: #2e4453;
+ font-size: 16px;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ clear: none;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 0;
+ height: 16px;
+ margin: 2px 0 0 4px;
+ float: right;
+ outline: 0;
+ padding: 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+ min-width: 16px;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 50%;
+ line-height: 10px;
+}
+
+.radio-button:hover,
+input[type="radio"]:hover {
+ border-color: #a8bece;
+}
+
+.radio-button:focus,
+input[type="radio"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+.radio-button:focus::-ms-clear,
+input[type="radio"]:focus::-ms-clear {
+ display: none;
+}
+
+.radio-button:checked:before,
+input[type="radio"]:checked:before {
+ float: right;
+ display: inline-block;
+ content: '\2022';
+ margin: 3px;
+ width: 8px;
+ height: 8px;
+ text-indent: -9999px;
+ background: #00aadc;
+ vertical-align: middle;
+ border-radius: 50%;
+ animation: grow .2s ease-in-out;
+}
+
+.radio-button:disabled,
+input[type="radio"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+ -webkit-text-fill-color: #a8bece;
+}
+
+.radio-button:disabled:hover,
+input[type="radio"]:disabled:hover {
+ cursor: default;
+}
+
+.radio-button:disabled:-ms-input-placeholder,
+input[type="radio"]:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+.radio-button:disabled::-ms-input-placeholder,
+input[type="radio"]:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+.radio-button:disabled::placeholder,
+input[type="radio"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+.radio-button:disabled:checked::before,
+input[type="radio"]:disabled:checked:before {
+ background: #e9eff3;
+}
+
+.radio-button + span,
+input[type="radio"] + span {
+ display: block;
+ font-weight: normal;
+ margin-right: 24px;
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+
+/* ==========================================================================
+** Selects
+** ======================================================================== */
+
+select {
+ background: #fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-radius: 4px;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0;
+ outline: 0;
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 21px;
+ font-weight: 600;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ white-space: nowrap;
+ box-sizing: border-box;
+ /* Aligns the text to the 8px baseline grid and adds padding on right to allow for the arrow. */
+ padding: 7px 14px 9px 32px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+select:hover {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==);
+}
+
+select:focus {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+ outline: 0;
+ -moz-outline:none;
+ -moz-user-focus:ignore;
+}
+
+select:disabled,
+select:hover:disabled {
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;;
+}
+
+select.is-compact {
+ min-width: 0;
+ padding: 0 6px 2px 20px;
+ margin: 0 4px;
+ background-position: left 5px center;
+ background-size: 12px 12px;
+}
+
+/* Make it display:block when it follows a label */
+label select,
+label + select {
+ display: block;
+ min-width: 200px;
+}
+
+label select.is-compact,
+label + select.is-compact {
+ display: inline-block;
+ min-width: 0;
+}
+
+/* IE: Remove the default arrow */
+select::-ms-expand {
+ display: none;
+}
+
+/* IE: Remove default background and color styles on focus */
+select::-ms-value {
+ background: none;
+ color: #2e4453;
+}
+
+/* Firefox: Remove the focus outline, see http://stackoverflow.com/questions/3773430/remove-outline-from-select-box-in-ff/18853002#18853002 */
+select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #2e4453;
+}
+
+
+/* ==========================================================================
+** Buttons
+** ======================================================================== */
+
+input[type="submit"] {
+ padding: 0;
+ font-size: 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ vertical-align: baseline;
+ background: white;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 24px 0 0;
+ outline: 0;
+ overflow: hidden;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ box-sizing: border-box;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 4px;
+ padding: 7px 14px 9px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+input[type="submit"]:hover {
+ border-color: #a8bece;
+ color: #2e4453;
+}
+
+input[type="submit"]:active {
+ border-width: 2px 1px 1px;
+}
+
+input[type="submit"]:visited {
+ color: #2e4453;
+}
+
+input[type="submit"][disabled],
+input[type="submit"]:disabled {
+ color: #e9eff3;
+ background: white;
+ border-color: #e9eff3;
+ cursor: default;
+}
+
+input[type="submit"][disabled]:active,
+input[type="submit"]:disabled:active {
+ border-width: 1px 1px 2px;
+}
+
+input[type="submit"]:focus {
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="submit"].hidden {
+ display: none;
+}
+
+input[type="submit"] .gridicon {
+ position: relative;
+ top: 4px;
+ margin-top: -2px;
+ width: 18px;
+ height: 18px;
+}
+
+input[type="submit"].button-primary {
+ background: #00aadc;
+ border-color: #008ab3;
+ color: white;
+}
+
+input[type="submit"].button-primary:hover,
+input[type="submit"].button-primary:focus {
+ border-color: #005082;
+ color: white;
+}
+
+input[type="submit"].button-primary[disabled],
+input[type="submit"].button-primary:disabled {
+ background: #bceefd;
+ border-color: #8cc9e2;
+ color: white;
+}
+
+input[type="submit"].button-primary {
+ color: white;
+}
+
+
+/* ==========================================================================
+** Inline editor styles
+** ======================================================================== */
+
+
+.ui-sortable-handle {
+ cursor: move;
+}
+
+.grunion-section-header {
+ font-size: 21px;
+ margin-top: 32px;
+ font-weight: 600;
+}
+
+.grunion-form-settings:hover {
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+.grunion-section-header:first-child {
+ margin-top: 0;
+}
+
+.grunion-type-options {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.grunion-type {
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.grunion-type select {
+ -webkit-appearance: none;
+ width: 100%;
+}
+
+.grunion-required {
+ padding: 27px 16px 0 0;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.grunion-options {
+ padding-top: 16px;
+}
+
+.grunion-options ol {
+ list-style: none;
+ padding: 0;
+ margin: 8px 0 0;
+}
+
+.grunion-options li {
+ display: flex;
+ margin-bottom: 16px;
+}
+
+.grunion-field-edit .grunion-options {
+ display: none;
+}
+
+.delete-option,
+.delete-field {
+ color: #0087be;
+ text-decoration: none;
+ width: 40px;
+ line-height: 40px;
+ font-size: 21px;
+ text-align: center;
+ font-weight: 600;
+}
+
+.delete-field {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.grunion-controls {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.grunion-update-controls {
+ text-align: left;
+ flex-grow: 1;
+}
+
+#add-field {
+ flex-grow: 0;
+}
+
+.delete-option:before,
+.delete-field:before {
+ font-family: Dashicons;
+/* content: "\f158"; /* This is the bolder X */
+ content: "\f335"; /* This is the thinner X */
+ display: inline-block;
+ speak: none;
+}
+
+.grunion-field-edit.grunion-field-checkbox-multiple .grunion-options,
+.grunion-field-edit.grunion-field-radio .grunion-options,
+.grunion-field-edit.grunion-field-select .grunion-options {
+ display: block;
+}
+
+.screen-reader-text {
+ position: absolute;
+ margin: -1px;
+ padding: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ border: 0;
+ word-wrap: normal !important; /* many screen reader and browser combinations announce broken words as they would appear visually */
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.min.css b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.min.css
new file mode 100644
index 00000000..9b7a27c2
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style-rtl.min.css
@@ -0,0 +1 @@
+html{direction:rtl}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:1.4em;margin:0}a,a:visited{color:#0087be;text-decoration:none}a:active,a:focus,a:hover{color:$link-highlight}.card,body{display:block;position:relative;margin:0 auto 10px auto;padding:16px;box-sizing:border-box;background:#fff;box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}body{margin:0;background:#f5f5f5}.card:after{content:".";display:block;height:0;clear:both;visibility:hidden}.card:focus,.card:hover{box-shadow:0 0 0 1px #999,0 1px 2px #e9eff3}.card .delete-field{display:block;float:left}@media (min-width:481px){.card{margin-bottom:16px;padding:24px}body{padding:24px}}.card.is-compact{margin-bottom:1px}@media (min-width:481px){.card.is-compact{margin-bottom:1px;padding:16px 24px}}.card>div{margin-top:24px}.card>div:first-child{margin-top:0}label{display:block;font-size:14px;font-weight:600;margin-bottom:5px;margin-top:8px}label:first-of-type{margin-top:4px}input[type=email],input[type=tel],input[type=text],input[type=url]{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}input[type=email]:-ms-input-placeholder,input[type=tel]:-ms-input-placeholder,input[type=text]:-ms-input-placeholder,input[type=url]:-ms-input-placeholder{color:#87a6bc}input[type=email]::-ms-input-placeholder,input[type=tel]::-ms-input-placeholder,input[type=text]::-ms-input-placeholder,input[type=url]::-ms-input-placeholder{color:#87a6bc}input[type=email]::placeholder,input[type=tel]::placeholder,input[type=text]::placeholder,input[type=url]::placeholder{color:#87a6bc}input[type=email]:hover,input[type=tel]:hover,input[type=text]:hover,input[type=url]:hover{border-color:#a8bece}input[type=email]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=email]:focus::-ms-clear,input[type=tel]:focus::-ms-clear,input[type=text]:focus::-ms-clear,input[type=url]:focus::-ms-clear{display:none}input[type=email]:disabled,input[type=tel]:disabled,input[type=text]:disabled,input[type=url]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}input[type=email]:disabled:hover,input[type=tel]:disabled:hover,input[type=text]:disabled:hover,input[type=url]:disabled:hover{cursor:default}input[type=email]:disabled:-ms-input-placeholder,input[type=tel]:disabled:-ms-input-placeholder,input[type=text]:disabled:-ms-input-placeholder,input[type=url]:disabled:-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::-ms-input-placeholder,input[type=tel]:disabled::-ms-input-placeholder,input[type=text]:disabled::-ms-input-placeholder,input[type=url]:disabled::-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::placeholder,input[type=tel]:disabled::placeholder,input[type=text]:disabled::placeholder,input[type=url]:disabled::placeholder{color:#a8bece}textarea{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;height:92px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}textarea:-ms-input-placeholder{color:#87a6bc}textarea::-ms-input-placeholder{color:#87a6bc}textarea::placeholder{color:#87a6bc}textarea:hover{border-color:#a8bece}textarea:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}textarea:focus::-ms-clear{display:none}textarea:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}textarea:disabled:hover{cursor:default}textarea:disabled:-ms-input-placeholder{color:#a8bece}textarea:disabled::-ms-input-placeholder{color:#a8bece}textarea:disabled::placeholder{color:#a8bece}.checkbox,input[type=checkbox]{-webkit-appearance:none;display:inline-block;box-sizing:border-box;margin:2px 0 0;padding:7px 14px;width:16px;height:16px;float:right;outline:0;padding:0;box-shadow:none;background-color:#fff;border:1px solid #c8d7e1;color:#2e4453;font-size:16px;line-height:0;text-align:center;vertical-align:middle;-moz-appearance:none;appearance:none;transition:all .15s ease-in-out;clear:none;cursor:pointer}.checkbox:checked:before,input[type=checkbox]:checked:before{content:'\f147';font-family:Dashicons;margin:-3px -4px 0 0;float:right;display:inline-block;vertical-align:middle;width:16px;font-size:20px;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;speak:none;color:#00aadc}.checkbox:disabled:checked:before,input[type=checkbox]:disabled:checked:before{color:#a8bece}.checkbox:hover,input[type=checkbox]:hover{border-color:#a8bece}.checkbox:focus,input[type=checkbox]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}.checkbox:disabled,input[type=checkbox]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1}.checkbox:disabled:hover,input[type=checkbox]:disabled:hover{cursor:default}.checkbox+span,input[type=checkbox]+span{display:block;font-weight:400;margin-right:24px}.radio-button,input[type=radio]{color:#2e4453;font-size:16px;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-sizing:border-box;-webkit-appearance:none;clear:none;cursor:pointer;display:inline-block;line-height:0;height:16px;margin:2px 0 0 4px;float:right;outline:0;padding:0;text-align:center;vertical-align:middle;width:16px;min-width:16px;-moz-appearance:none;appearance:none;border-radius:50%;line-height:10px}.radio-button:hover,input[type=radio]:hover{border-color:#a8bece}.radio-button:focus,input[type=radio]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}.radio-button:focus::-ms-clear,input[type=radio]:focus::-ms-clear{display:none}.radio-button:checked:before,input[type=radio]:checked:before{float:right;display:inline-block;content:'\2022';margin:3px;width:8px;height:8px;text-indent:-9999px;background:#00aadc;vertical-align:middle;border-radius:50%;animation:grow .2s ease-in-out}.radio-button:disabled,input[type=radio]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1;-webkit-text-fill-color:#a8bece}.radio-button:disabled:hover,input[type=radio]:disabled:hover{cursor:default}.radio-button:disabled:-ms-input-placeholder,input[type=radio]:disabled:-ms-input-placeholder{color:#a8bece}.radio-button:disabled::-ms-input-placeholder,input[type=radio]:disabled::-ms-input-placeholder{color:#a8bece}.radio-button:disabled::placeholder,input[type=radio]:disabled::placeholder{color:#a8bece}.radio-button:disabled:checked::before,input[type=radio]:disabled:checked:before{background:#e9eff3}.radio-button+span,input[type=radio]+span{display:block;font-weight:400;margin-right:24px}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}select{background:#fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;border-color:#c8d7e1;border-style:solid;border-radius:4px;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:0;outline:0;overflow:hidden;font-size:14px;line-height:21px;font-weight:600;text-overflow:ellipsis;text-decoration:none;vertical-align:top;white-space:nowrap;box-sizing:border-box;padding:7px 14px 9px 32px;-webkit-appearance:none;-moz-appearance:none;appearance:none}select:hover{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==)}select:focus{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa;outline:0;-moz-outline:none;-moz-user-focus:ignore}select:disabled,select:hover:disabled{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center}select.is-compact{min-width:0;padding:0 6px 2px 20px;margin:0 4px;background-position:left 5px center;background-size:12px 12px}label select,label+select{display:block;min-width:200px}label select.is-compact,label+select.is-compact{display:inline-block;min-width:0}select::-ms-expand{display:none}select::-ms-value{background:0 0;color:#2e4453}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #2e4453}input[type=submit]{padding:0;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;vertical-align:baseline;background:#fff;border-color:#c8d7e1;border-style:solid;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:24px 0 0;outline:0;overflow:hidden;font-weight:500;text-overflow:ellipsis;text-decoration:none;vertical-align:top;box-sizing:border-box;font-size:14px;line-height:21px;border-radius:4px;padding:7px 14px 9px;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=submit]:hover{border-color:#a8bece;color:#2e4453}input[type=submit]:active{border-width:2px 1px 1px}input[type=submit]:visited{color:#2e4453}input[type=submit]:disabled,input[type=submit][disabled]{color:#e9eff3;background:#fff;border-color:#e9eff3;cursor:default}input[type=submit]:disabled:active,input[type=submit][disabled]:active{border-width:1px 1px 2px}input[type=submit]:focus{border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa}input[type=submit].hidden{display:none}input[type=submit] .gridicon{position:relative;top:4px;margin-top:-2px;width:18px;height:18px}input[type=submit].button-primary{background:#00aadc;border-color:#008ab3;color:#fff}input[type=submit].button-primary:focus,input[type=submit].button-primary:hover{border-color:#005082;color:#fff}input[type=submit].button-primary:disabled,input[type=submit].button-primary[disabled]{background:#bceefd;border-color:#8cc9e2;color:#fff}input[type=submit].button-primary{color:#fff}.ui-sortable-handle{cursor:move}.grunion-section-header{font-size:21px;margin-top:32px;font-weight:600}.grunion-form-settings:hover{box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}.grunion-section-header:first-child{margin-top:0}.grunion-type-options{display:flex;flex-wrap:wrap}.grunion-type{flex-grow:0;flex-shrink:0}.grunion-type select{-webkit-appearance:none;width:100%}.grunion-required{padding:27px 16px 0 0;flex-grow:0;flex-shrink:0}.grunion-options{padding-top:16px}.grunion-options ol{list-style:none;padding:0;margin:8px 0 0}.grunion-options li{display:flex;margin-bottom:16px}.grunion-field-edit .grunion-options{display:none}.delete-field,.delete-option{color:#0087be;text-decoration:none;width:40px;line-height:40px;font-size:21px;text-align:center;font-weight:600}.delete-field{position:absolute;top:0;left:0}.grunion-controls{display:flex;flex-wrap:wrap}.grunion-update-controls{text-align:left;flex-grow:1}#add-field{flex-grow:0}.delete-field:before,.delete-option:before{font-family:Dashicons;content:"\f335";display:inline-block;speak:none}.grunion-field-edit.grunion-field-checkbox-multiple .grunion-options,.grunion-field-edit.grunion-field-radio .grunion-options,.grunion-field-edit.grunion-field-select .grunion-options{display:block}.screen-reader-text{position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip:rect(0 0 0 0);border:0;word-wrap:normal!important} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.css b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.css
new file mode 100644
index 00000000..13db02fd
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.css
@@ -0,0 +1,764 @@
+/* ==========================================================================
+** Normalize
+** ======================================================================== */
+
+html {
+ direction: ltr;
+}
+
+body {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
+ font-size: 16px;
+ line-height: 1.4em;
+ margin: 0;
+}
+
+/* Links */
+a,
+a:visited {
+ color: #0087be;
+ text-decoration: none;
+}
+
+a:hover,
+a:focus,
+a:active {
+ color: $link-highlight;
+}
+
+/* ==========================================================================
+** Card
+** ======================================================================= */
+
+.card,
+body {
+ display: block;
+ position: relative;
+ margin: 0 auto 10px auto;
+ padding: 16px;
+ box-sizing: border-box;
+ background: white;
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+body {
+ margin: 0;
+ background: #f5f5f5;
+}
+
+.card:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+.card:hover,
+.card:focus {
+ box-shadow: 0 0 0 1px #999, 0 1px 2px #e9eff3;
+}
+
+.card .delete-field {
+ display: block;
+ float: right;
+}
+
+@media ( min-width: 481px ) {
+ .card {
+ margin-bottom: 16px;
+ padding: 24px;
+ }
+ body {
+ padding: 24px;
+ }
+}
+
+.card.is-compact {
+ margin-bottom: 1px;
+}
+
+@media ( min-width: 481px ) {
+ .card.is-compact {
+ margin-bottom: 1px;
+ padding: 16px 24px;
+ }
+}
+
+.card > div {
+ margin-top: 24px;
+}
+
+.card > div:first-child {
+ margin-top: 0;
+}
+
+
+/* ==========================================================================
+** Labels
+** ======================================================================= */
+
+label {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 5px;
+ margin-top: 8px;
+}
+
+label:first-of-type {
+ margin-top: 4px;
+}
+
+
+/* ==========================================================================
+** Text Inputs
+** ======================================================================= */
+
+input[type="text"],
+input[type="tel"],
+input[type="email"],
+input[type="url"] {
+ border-radius: 0;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+input[type="text"]::placeholder,
+input[type="tel"]::placeholder,
+input[type="email"]::placeholder,
+input[type="url"]::placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]:hover,
+input[type="tel"]:hover,
+input[type="email"]:hover,
+input[type="url"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="text"]:focus,
+input[type="tel"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="text"]:focus::-ms-clear,
+input[type="tel"]:focus::-ms-clear,
+input[type="email"]:focus::-ms-clear,
+input[type="url"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="text"]:disabled,
+input[type="tel"]:disabled,
+input[type="email"]:disabled,
+input[type="url"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="text"]:disabled:hover,
+input[type="tel"]:disabled:hover,
+input[type="email"]:disabled:hover,
+input[type="url"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="text"]:disabled::placeholder,
+input[type="tel"]:disabled::placeholder,
+input[type="email"]:disabled::placeholder,
+input[type="url"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Textareas
+** ======================================================================= */
+
+textarea {
+ border-radius: 0;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ height: 92px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+textarea::placeholder {
+ color: #87a6bc;
+}
+
+textarea:hover {
+ border-color: #a8bece;
+}
+
+textarea:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+textarea:focus::-ms-clear {
+ display: none;
+}
+
+textarea:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+textarea:disabled:hover {
+ cursor: default;
+}
+
+textarea:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Checkboxes
+** ======================================================================= */
+
+.checkbox,
+input[type="checkbox"] {
+ -webkit-appearance: none;
+ display: inline-block;
+ box-sizing: border-box;
+ margin: 2px 0 0;
+ padding: 7px 14px;
+ width: 16px;
+ height: 16px;
+ float: left;
+ outline: 0;
+ padding: 0;
+ box-shadow: none;
+ background-color: #fff;
+ border: 1px solid #c8d7e1;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 0;
+ text-align: center;
+ vertical-align: middle;
+ appearance: none;
+ transition: all .15s ease-in-out;
+ clear: none;
+ cursor: pointer;
+}
+
+.checkbox:checked:before,
+input[type="checkbox"]:checked:before {
+ content: '\f147';
+ font-family: Dashicons;
+ margin: -3px 0 0 -4px;
+ float: left;
+ display: inline-block;
+ vertical-align: middle;
+ width: 16px;
+ font-size: 20px;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ speak: none;
+ color: #00aadc;
+}
+
+.checkbox:disabled:checked:before,
+input[type="checkbox"]:disabled:checked:before {
+ color: #a8bece;
+}
+
+.checkbox:hover,
+input[type="checkbox"]:hover {
+ border-color: #a8bece;
+}
+
+.checkbox:focus,
+input[type="checkbox"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+.checkbox:disabled,
+input[type="checkbox"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+}
+
+.checkbox:disabled:hover,
+input[type="checkbox"]:disabled:hover {
+ cursor: default;
+}
+
+.checkbox + span,
+input[type="checkbox"] + span {
+ display: block;
+ font-weight: normal;
+ margin-left: 24px;
+}
+
+
+/* ==========================================================================
+** Radio buttons
+** ======================================================================== */
+
+.radio-button,
+input[type=radio] {
+ color: #2e4453;
+ font-size: 16px;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ clear: none;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 0;
+ height: 16px;
+ margin: 2px 4px 0 0;
+ float: left;
+ outline: 0;
+ padding: 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+ min-width: 16px;
+ appearance: none;
+ border-radius: 50%;
+ line-height: 10px;
+}
+
+.radio-button:hover,
+input[type="radio"]:hover {
+ border-color: #a8bece;
+}
+
+.radio-button:focus,
+input[type="radio"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+.radio-button:focus::-ms-clear,
+input[type="radio"]:focus::-ms-clear {
+ display: none;
+}
+
+.radio-button:checked:before,
+input[type="radio"]:checked:before {
+ float: left;
+ display: inline-block;
+ content: '\2022';
+ margin: 3px;
+ width: 8px;
+ height: 8px;
+ text-indent: -9999px;
+ background: #00aadc;
+ vertical-align: middle;
+ border-radius: 50%;
+ animation: grow .2s ease-in-out;
+}
+
+.radio-button:disabled,
+input[type="radio"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+ -webkit-text-fill-color: #a8bece;
+}
+
+.radio-button:disabled:hover,
+input[type="radio"]:disabled:hover {
+ cursor: default;
+}
+
+.radio-button:disabled::placeholder,
+input[type="radio"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+.radio-button:disabled:checked::before,
+input[type="radio"]:disabled:checked:before {
+ background: #e9eff3;
+}
+
+.radio-button + span,
+input[type="radio"] + span {
+ display: block;
+ font-weight: normal;
+ margin-left: 24px;
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+
+/* ==========================================================================
+** Selects
+** ======================================================================== */
+
+select {
+ background: #fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-radius: 4px;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0;
+ outline: 0;
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 21px;
+ font-weight: 600;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ white-space: nowrap;
+ box-sizing: border-box;
+ /* Aligns the text to the 8px baseline grid and adds padding on right to allow for the arrow. */
+ padding: 7px 32px 9px 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+select:hover {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==);
+}
+
+select:focus {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+ outline: 0;
+ -moz-outline:none;
+ -moz-user-focus:ignore;
+}
+
+select:disabled,
+select:hover:disabled {
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;;
+}
+
+select.is-compact {
+ min-width: 0;
+ padding: 0 20px 2px 6px;
+ margin: 0 4px;
+ background-position: right 5px center;
+ background-size: 12px 12px;
+}
+
+/* Make it display:block when it follows a label */
+label select,
+label + select {
+ display: block;
+ min-width: 200px;
+}
+
+label select.is-compact,
+label + select.is-compact {
+ display: inline-block;
+ min-width: 0;
+}
+
+/* IE: Remove the default arrow */
+select::-ms-expand {
+ display: none;
+}
+
+/* IE: Remove default background and color styles on focus */
+select::-ms-value {
+ background: none;
+ color: #2e4453;
+}
+
+/* Firefox: Remove the focus outline, see http://stackoverflow.com/questions/3773430/remove-outline-from-select-box-in-ff/18853002#18853002 */
+select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #2e4453;
+}
+
+
+/* ==========================================================================
+** Buttons
+** ======================================================================== */
+
+input[type="submit"] {
+ padding: 0;
+ font-size: 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ vertical-align: baseline;
+ background: white;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 24px 0 0;
+ outline: 0;
+ overflow: hidden;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ box-sizing: border-box;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 4px;
+ padding: 7px 14px 9px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+input[type="submit"]:hover {
+ border-color: #a8bece;
+ color: #2e4453;
+}
+
+input[type="submit"]:active {
+ border-width: 2px 1px 1px;
+}
+
+input[type="submit"]:visited {
+ color: #2e4453;
+}
+
+input[type="submit"][disabled],
+input[type="submit"]:disabled {
+ color: #e9eff3;
+ background: white;
+ border-color: #e9eff3;
+ cursor: default;
+}
+
+input[type="submit"][disabled]:active,
+input[type="submit"]:disabled:active {
+ border-width: 1px 1px 2px;
+}
+
+input[type="submit"]:focus {
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="submit"].hidden {
+ display: none;
+}
+
+input[type="submit"] .gridicon {
+ position: relative;
+ top: 4px;
+ margin-top: -2px;
+ width: 18px;
+ height: 18px;
+}
+
+input[type="submit"].button-primary {
+ background: #00aadc;
+ border-color: #008ab3;
+ color: white;
+}
+
+input[type="submit"].button-primary:hover,
+input[type="submit"].button-primary:focus {
+ border-color: #005082;
+ color: white;
+}
+
+input[type="submit"].button-primary[disabled],
+input[type="submit"].button-primary:disabled {
+ background: #bceefd;
+ border-color: #8cc9e2;
+ color: white;
+}
+
+input[type="submit"].button-primary {
+ color: white;
+}
+
+
+/* ==========================================================================
+** Inline editor styles
+** ======================================================================== */
+
+
+.ui-sortable-handle {
+ cursor: move;
+}
+
+.grunion-section-header {
+ font-size: 21px;
+ margin-top: 32px;
+ font-weight: 600;
+}
+
+.grunion-form-settings:hover {
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+.grunion-section-header:first-child {
+ margin-top: 0;
+}
+
+.grunion-type-options {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.grunion-type {
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.grunion-type select {
+ -webkit-appearance: none;
+ width: 100%;
+}
+
+.grunion-required {
+ padding: 27px 0 0 16px;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.grunion-options {
+ padding-top: 16px;
+}
+
+.grunion-options ol {
+ list-style: none;
+ padding: 0;
+ margin: 8px 0 0;
+}
+
+.grunion-options li {
+ display: flex;
+ margin-bottom: 16px;
+}
+
+.grunion-field-edit .grunion-options {
+ display: none;
+}
+
+.delete-option,
+.delete-field {
+ color: #0087be;
+ text-decoration: none;
+ width: 40px;
+ line-height: 40px;
+ font-size: 21px;
+ text-align: center;
+ font-weight: 600;
+}
+
+.delete-field {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.grunion-controls {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.grunion-update-controls {
+ text-align: right;
+ flex-grow: 1;
+}
+
+#add-field {
+ flex-grow: 0;
+}
+
+.delete-option:before,
+.delete-field:before {
+ font-family: Dashicons;
+/* content: "\f158"; /* This is the bolder X */
+ content: "\f335"; /* This is the thinner X */
+ display: inline-block;
+ speak: none;
+}
+
+.grunion-field-edit.grunion-field-checkbox-multiple .grunion-options,
+.grunion-field-edit.grunion-field-radio .grunion-options,
+.grunion-field-edit.grunion-field-select .grunion-options {
+ display: block;
+}
+
+.screen-reader-text {
+ position: absolute;
+ margin: -1px;
+ padding: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ border: 0;
+ word-wrap: normal !important; /* many screen reader and browser combinations announce broken words as they would appear visually */
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.min.css b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.min.css
new file mode 100644
index 00000000..7d3a0ca0
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-inline-editing-style.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+html{direction:ltr}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:1.4em;margin:0}a,a:visited{color:#0087be;text-decoration:none}a:active,a:focus,a:hover{color:$link-highlight}.card,body{display:block;position:relative;margin:0 auto 10px auto;padding:16px;box-sizing:border-box;background:#fff;box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}body{margin:0;background:#f5f5f5}.card:after{content:".";display:block;height:0;clear:both;visibility:hidden}.card:focus,.card:hover{box-shadow:0 0 0 1px #999,0 1px 2px #e9eff3}.card .delete-field{display:block;float:right}@media (min-width:481px){.card{margin-bottom:16px;padding:24px}body{padding:24px}}.card.is-compact{margin-bottom:1px}@media (min-width:481px){.card.is-compact{margin-bottom:1px;padding:16px 24px}}.card>div{margin-top:24px}.card>div:first-child{margin-top:0}label{display:block;font-size:14px;font-weight:600;margin-bottom:5px;margin-top:8px}label:first-of-type{margin-top:4px}input[type=email],input[type=tel],input[type=text],input[type=url]{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}input[type=email]:-ms-input-placeholder,input[type=tel]:-ms-input-placeholder,input[type=text]:-ms-input-placeholder,input[type=url]:-ms-input-placeholder{color:#87a6bc}input[type=email]::-ms-input-placeholder,input[type=tel]::-ms-input-placeholder,input[type=text]::-ms-input-placeholder,input[type=url]::-ms-input-placeholder{color:#87a6bc}input[type=email]::placeholder,input[type=tel]::placeholder,input[type=text]::placeholder,input[type=url]::placeholder{color:#87a6bc}input[type=email]:hover,input[type=tel]:hover,input[type=text]:hover,input[type=url]:hover{border-color:#a8bece}input[type=email]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=email]:focus::-ms-clear,input[type=tel]:focus::-ms-clear,input[type=text]:focus::-ms-clear,input[type=url]:focus::-ms-clear{display:none}input[type=email]:disabled,input[type=tel]:disabled,input[type=text]:disabled,input[type=url]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}input[type=email]:disabled:hover,input[type=tel]:disabled:hover,input[type=text]:disabled:hover,input[type=url]:disabled:hover{cursor:default}input[type=email]:disabled:-ms-input-placeholder,input[type=tel]:disabled:-ms-input-placeholder,input[type=text]:disabled:-ms-input-placeholder,input[type=url]:disabled:-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::-ms-input-placeholder,input[type=tel]:disabled::-ms-input-placeholder,input[type=text]:disabled::-ms-input-placeholder,input[type=url]:disabled::-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::placeholder,input[type=tel]:disabled::placeholder,input[type=text]:disabled::placeholder,input[type=url]:disabled::placeholder{color:#a8bece}textarea{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;height:92px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}textarea:-ms-input-placeholder{color:#87a6bc}textarea::-ms-input-placeholder{color:#87a6bc}textarea::placeholder{color:#87a6bc}textarea:hover{border-color:#a8bece}textarea:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}textarea:focus::-ms-clear{display:none}textarea:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}textarea:disabled:hover{cursor:default}textarea:disabled:-ms-input-placeholder{color:#a8bece}textarea:disabled::-ms-input-placeholder{color:#a8bece}textarea:disabled::placeholder{color:#a8bece}.checkbox,input[type=checkbox]{-webkit-appearance:none;display:inline-block;box-sizing:border-box;margin:2px 0 0;padding:7px 14px;width:16px;height:16px;float:left;outline:0;padding:0;box-shadow:none;background-color:#fff;border:1px solid #c8d7e1;color:#2e4453;font-size:16px;line-height:0;text-align:center;vertical-align:middle;-moz-appearance:none;appearance:none;transition:all .15s ease-in-out;clear:none;cursor:pointer}.checkbox:checked:before,input[type=checkbox]:checked:before{content:'\f147';font-family:Dashicons;margin:-3px 0 0 -4px;float:left;display:inline-block;vertical-align:middle;width:16px;font-size:20px;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;speak:none;color:#00aadc}.checkbox:disabled:checked:before,input[type=checkbox]:disabled:checked:before{color:#a8bece}.checkbox:hover,input[type=checkbox]:hover{border-color:#a8bece}.checkbox:focus,input[type=checkbox]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}.checkbox:disabled,input[type=checkbox]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1}.checkbox:disabled:hover,input[type=checkbox]:disabled:hover{cursor:default}.checkbox+span,input[type=checkbox]+span{display:block;font-weight:400;margin-left:24px}.radio-button,input[type=radio]{color:#2e4453;font-size:16px;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-sizing:border-box;-webkit-appearance:none;clear:none;cursor:pointer;display:inline-block;line-height:0;height:16px;margin:2px 4px 0 0;float:left;outline:0;padding:0;text-align:center;vertical-align:middle;width:16px;min-width:16px;-moz-appearance:none;appearance:none;border-radius:50%;line-height:10px}.radio-button:hover,input[type=radio]:hover{border-color:#a8bece}.radio-button:focus,input[type=radio]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}.radio-button:focus::-ms-clear,input[type=radio]:focus::-ms-clear{display:none}.radio-button:checked:before,input[type=radio]:checked:before{float:left;display:inline-block;content:'\2022';margin:3px;width:8px;height:8px;text-indent:-9999px;background:#00aadc;vertical-align:middle;border-radius:50%;animation:grow .2s ease-in-out}.radio-button:disabled,input[type=radio]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1;-webkit-text-fill-color:#a8bece}.radio-button:disabled:hover,input[type=radio]:disabled:hover{cursor:default}.radio-button:disabled:-ms-input-placeholder,input[type=radio]:disabled:-ms-input-placeholder{color:#a8bece}.radio-button:disabled::-ms-input-placeholder,input[type=radio]:disabled::-ms-input-placeholder{color:#a8bece}.radio-button:disabled::placeholder,input[type=radio]:disabled::placeholder{color:#a8bece}.radio-button:disabled:checked::before,input[type=radio]:disabled:checked:before{background:#e9eff3}.radio-button+span,input[type=radio]+span{display:block;font-weight:400;margin-left:24px}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}select{background:#fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;border-color:#c8d7e1;border-style:solid;border-radius:4px;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:0;outline:0;overflow:hidden;font-size:14px;line-height:21px;font-weight:600;text-overflow:ellipsis;text-decoration:none;vertical-align:top;white-space:nowrap;box-sizing:border-box;padding:7px 32px 9px 14px;-webkit-appearance:none;-moz-appearance:none;appearance:none}select:hover{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==)}select:focus{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa;outline:0;-moz-outline:none;-moz-user-focus:ignore}select:disabled,select:hover:disabled{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center}select.is-compact{min-width:0;padding:0 20px 2px 6px;margin:0 4px;background-position:right 5px center;background-size:12px 12px}label select,label+select{display:block;min-width:200px}label select.is-compact,label+select.is-compact{display:inline-block;min-width:0}select::-ms-expand{display:none}select::-ms-value{background:0 0;color:#2e4453}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #2e4453}input[type=submit]{padding:0;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;vertical-align:baseline;background:#fff;border-color:#c8d7e1;border-style:solid;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:24px 0 0;outline:0;overflow:hidden;font-weight:500;text-overflow:ellipsis;text-decoration:none;vertical-align:top;box-sizing:border-box;font-size:14px;line-height:21px;border-radius:4px;padding:7px 14px 9px;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=submit]:hover{border-color:#a8bece;color:#2e4453}input[type=submit]:active{border-width:2px 1px 1px}input[type=submit]:visited{color:#2e4453}input[type=submit]:disabled,input[type=submit][disabled]{color:#e9eff3;background:#fff;border-color:#e9eff3;cursor:default}input[type=submit]:disabled:active,input[type=submit][disabled]:active{border-width:1px 1px 2px}input[type=submit]:focus{border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa}input[type=submit].hidden{display:none}input[type=submit] .gridicon{position:relative;top:4px;margin-top:-2px;width:18px;height:18px}input[type=submit].button-primary{background:#00aadc;border-color:#008ab3;color:#fff}input[type=submit].button-primary:focus,input[type=submit].button-primary:hover{border-color:#005082;color:#fff}input[type=submit].button-primary:disabled,input[type=submit].button-primary[disabled]{background:#bceefd;border-color:#8cc9e2;color:#fff}input[type=submit].button-primary{color:#fff}.ui-sortable-handle{cursor:move}.grunion-section-header{font-size:21px;margin-top:32px;font-weight:600}.grunion-form-settings:hover{box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}.grunion-section-header:first-child{margin-top:0}.grunion-type-options{display:flex;flex-wrap:wrap}.grunion-type{flex-grow:0;flex-shrink:0}.grunion-type select{-webkit-appearance:none;width:100%}.grunion-required{padding:27px 0 0 16px;flex-grow:0;flex-shrink:0}.grunion-options{padding-top:16px}.grunion-options ol{list-style:none;padding:0;margin:8px 0 0}.grunion-options li{display:flex;margin-bottom:16px}.grunion-field-edit .grunion-options{display:none}.delete-field,.delete-option{color:#0087be;text-decoration:none;width:40px;line-height:40px;font-size:21px;text-align:center;font-weight:600}.delete-field{position:absolute;top:0;right:0}.grunion-controls{display:flex;flex-wrap:wrap}.grunion-update-controls{text-align:right;flex-grow:1}#add-field{flex-grow:0}.delete-field:before,.delete-option:before{font-family:Dashicons;content:"\f335";display:inline-block;speak:none}.grunion-field-edit.grunion-field-checkbox-multiple .grunion-options,.grunion-field-edit.grunion-field-radio .grunion-options,.grunion-field-edit.grunion-field-select .grunion-options{display:block}.screen-reader-text{position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip:rect(0 0 0 0);border:0;word-wrap:normal!important} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/editor-style-rtl.css b/plugins/jetpack/modules/contact-form/css/editor-style-rtl.css
new file mode 100644
index 00000000..e6b43f3c
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-style-rtl.css
@@ -0,0 +1,613 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+/* ==========================================================================
+** Normalize
+** ======================================================================== */
+
+body,
+label {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
+ font-size: 16px;
+ line-height: 1.4em;
+}
+
+/* ==========================================================================
+** Card
+** ======================================================================= */
+
+.card {
+ display: block;
+ position: relative;
+ margin: 0 auto;
+ padding: 16px;
+ box-sizing: border-box;
+ background: white;
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+.card:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+@media ( min-width: 481px ) {
+ .card {
+ padding: 24px;
+ }
+}
+
+.card > div {
+ margin-top: 24px;
+}
+
+.card > div:first-child {
+ margin-top: 0;
+}
+
+
+/* ==========================================================================
+** Labels
+** ======================================================================= */
+
+label {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 5px;
+}
+
+
+/* ==========================================================================
+** Text Inputs
+** ======================================================================= */
+
+input[type="text"],
+input[type="tel"],
+input[type="email"],
+input[type="url"] {
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+input[type="text"]:-ms-input-placeholder,
+input[type="tel"]:-ms-input-placeholder,
+input[type="email"]:-ms-input-placeholder,
+input[type="url"]:-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]::-ms-input-placeholder,
+input[type="tel"]::-ms-input-placeholder,
+input[type="email"]::-ms-input-placeholder,
+input[type="url"]::-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]::placeholder,
+input[type="tel"]::placeholder,
+input[type="email"]::placeholder,
+input[type="url"]::placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]:hover,
+input[type="tel"]:hover,
+input[type="email"]:hover,
+input[type="url"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="text"]:focus,
+input[type="tel"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="text"]:focus::-ms-clear,
+input[type="tel"]:focus::-ms-clear,
+input[type="email"]:focus::-ms-clear,
+input[type="url"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="text"]:disabled,
+input[type="tel"]:disabled,
+input[type="email"]:disabled,
+input[type="url"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="text"]:disabled:hover,
+input[type="tel"]:disabled:hover,
+input[type="email"]:disabled:hover,
+input[type="url"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="text"]:disabled:-ms-input-placeholder,
+input[type="tel"]:disabled:-ms-input-placeholder,
+input[type="email"]:disabled:-ms-input-placeholder,
+input[type="url"]:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="text"]:disabled::-ms-input-placeholder,
+input[type="tel"]:disabled::-ms-input-placeholder,
+input[type="email"]:disabled::-ms-input-placeholder,
+input[type="url"]:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="text"]:disabled::placeholder,
+input[type="tel"]:disabled::placeholder,
+input[type="email"]:disabled::placeholder,
+input[type="url"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Textareas
+** ======================================================================= */
+
+textarea {
+ border-radius: 0;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ height: 92px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+textarea:-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+textarea::-ms-input-placeholder {
+ color: #87a6bc;
+}
+
+textarea::placeholder {
+ color: #87a6bc;
+}
+
+textarea:hover {
+ border-color: #a8bece;
+}
+
+textarea:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+textarea:focus::-ms-clear {
+ display: none;
+}
+
+textarea:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+textarea:disabled:hover {
+ cursor: default;
+}
+
+textarea:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+textarea:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+textarea:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Checkboxes
+** ======================================================================= */
+
+input[type="checkbox"] {
+ -webkit-appearance: none;
+ display: inline-block;
+ box-sizing: border-box;
+ margin: 2px 0 0;
+ padding: 7px 14px;
+ width: 16px;
+ height: 16px;
+ float: right;
+ outline: 0;
+ padding: 0;
+ box-shadow: none;
+ background-color: #fff;
+ border: 1px solid #c8d7e1;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 0;
+ text-align: center;
+ vertical-align: middle;
+ -moz-appearance: none;
+ appearance: none;
+ transition: all .15s ease-in-out;
+ clear: none;
+ cursor: pointer;
+}
+
+input[type="checkbox"]:checked:before {
+ content: '\f147';
+ font-family: Dashicons;
+ margin: -3px -4px 0 0;
+ float: right;
+ display: inline-block;
+ vertical-align: middle;
+ width: 16px;
+ font-size: 20px;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ speak: none;
+ color: #00aadc;
+}
+
+input[type="checkbox"]:disabled:checked:before {
+ color: #a8bece;
+}
+
+input[type="checkbox"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="checkbox"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="checkbox"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+}
+
+input[type="checkbox"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="checkbox"] + span {
+ display: block;
+ font-weight: normal;
+ margin-right: 24px;
+}
+
+
+/* ==========================================================================
+** Radio buttons
+** ======================================================================== */
+
+input[type=radio] {
+ color: #2e4453;
+ font-size: 16px;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ clear: none;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 0;
+ height: 16px;
+ margin: 2px 0 0 4px;
+ float: right;
+ outline: 0;
+ padding: 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+ min-width: 16px;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 50%;
+ line-height: 10px;
+}
+
+input[type="radio"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="radio"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="radio"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="radio"]:checked:before {
+ float: right;
+ display: inline-block;
+ content: '\2022';
+ margin: 3px;
+ width: 8px;
+ height: 8px;
+ text-indent: -9999px;
+ background: #00aadc;
+ vertical-align: middle;
+ border-radius: 50%;
+ animation: grow .2s ease-in-out;
+}
+
+input[type="radio"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="radio"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="radio"]:disabled:-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="radio"]:disabled::-ms-input-placeholder {
+ color: #a8bece;
+}
+
+input[type="radio"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+input[type="radio"]:disabled:checked:before {
+ background: #e9eff3;
+}
+
+input[type="radio"] + span {
+ display: block;
+ font-weight: normal;
+ margin-right: 24px;
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+
+/* ==========================================================================
+** Selects
+** ======================================================================== */
+
+select {
+ background: #fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-radius: 4px;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0;
+ outline: 0;
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 21px;
+ font-weight: 600;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ white-space: nowrap;
+ box-sizing: border-box;
+ /* Aligns the text to the 8px baseline grid and adds padding on right to allow for the arrow. */
+ padding: 7px 14px 9px 32px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+select:hover {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==);
+}
+
+select:focus {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+ outline: 0;
+ -moz-outline:none;
+ -moz-user-focus:ignore;
+}
+
+select:disabled,
+select:hover:disabled {
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;;
+}
+
+select.is-compact {
+ min-width: 0;
+ padding: 0 6px 2px 20px;
+ margin: 0 4px;
+ background-position: left 5px center;
+ background-size: 12px 12px;
+}
+
+/* Make it display:block when it follows a label */
+label select,
+label + select {
+ display: block;
+ min-width: 200px;
+}
+
+label select.is-compact,
+label + select.is-compact {
+ display: inline-block;
+ min-width: 0;
+}
+
+/* IE: Remove the default arrow */
+select::-ms-expand {
+ display: none;
+}
+
+/* IE: Remove default background and color styles on focus */
+select::-ms-value {
+ background: none;
+ color: #2e4453;
+}
+
+/* Firefox: Remove the focus outline, see http://stackoverflow.com/questions/3773430/remove-outline-from-select-box-in-ff/18853002#18853002 */
+select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #2e4453;
+}
+
+
+/* ==========================================================================
+** Buttons
+** ======================================================================== */
+
+input[type="submit"] {
+ padding: 0;
+ font-size: 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ vertical-align: baseline;
+ background: white;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 24px 0 0;
+ outline: 0;
+ overflow: hidden;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ box-sizing: border-box;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 4px;
+ padding: 7px 14px 9px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+input[type="submit"]:hover {
+ border-color: #a8bece;
+ color: #2e4453;
+}
+
+input[type="submit"]:active {
+ border-width: 2px 1px 1px;
+}
+
+input[type="submit"]:visited {
+ color: #2e4453;
+}
+
+input[type="submit"][disabled],
+input[type="submit"]:disabled {
+ color: #e9eff3;
+ background: white;
+ border-color: #e9eff3;
+ cursor: default;
+}
+
+input[type="submit"][disabled]:active,
+input[type="submit"]:disabled:active {
+ border-width: 1px 1px 2px;
+}
+
+input[type="submit"]:focus {
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+/* ==========================================================================
+** Preview styles
+** ======================================================================== */
+
+.wpview.wpview-wrap[data-wpview-type=contact-form] iframe.inline-edit-contact-form {
+ width: 100%;
+ min-height: 500px;
+ border: 0;
+ overflow: hidden;
+ margin-bottom: 0;
+ display: block;
+}
+
+.contact-submit.contact-submit {
+ margin-top: 0;
+ margin-bottom: 0;
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-style-rtl.min.css b/plugins/jetpack/modules/contact-form/css/editor-style-rtl.min.css
new file mode 100644
index 00000000..4562abc2
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-style-rtl.min.css
@@ -0,0 +1 @@
+body,label{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:1.4em}.card{display:block;position:relative;margin:0 auto;padding:16px;box-sizing:border-box;background:#fff;box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}.card:after{content:".";display:block;height:0;clear:both;visibility:hidden}@media (min-width:481px){.card{padding:24px}}.card>div{margin-top:24px}.card>div:first-child{margin-top:0}label{display:block;font-size:14px;font-weight:600;margin-bottom:5px}input[type=email],input[type=tel],input[type=text],input[type=url]{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}input[type=email]:-ms-input-placeholder,input[type=tel]:-ms-input-placeholder,input[type=text]:-ms-input-placeholder,input[type=url]:-ms-input-placeholder{color:#87a6bc}input[type=email]::-ms-input-placeholder,input[type=tel]::-ms-input-placeholder,input[type=text]::-ms-input-placeholder,input[type=url]::-ms-input-placeholder{color:#87a6bc}input[type=email]::placeholder,input[type=tel]::placeholder,input[type=text]::placeholder,input[type=url]::placeholder{color:#87a6bc}input[type=email]:hover,input[type=tel]:hover,input[type=text]:hover,input[type=url]:hover{border-color:#a8bece}input[type=email]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=email]:focus::-ms-clear,input[type=tel]:focus::-ms-clear,input[type=text]:focus::-ms-clear,input[type=url]:focus::-ms-clear{display:none}input[type=email]:disabled,input[type=tel]:disabled,input[type=text]:disabled,input[type=url]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}input[type=email]:disabled:hover,input[type=tel]:disabled:hover,input[type=text]:disabled:hover,input[type=url]:disabled:hover{cursor:default}input[type=email]:disabled:-ms-input-placeholder,input[type=tel]:disabled:-ms-input-placeholder,input[type=text]:disabled:-ms-input-placeholder,input[type=url]:disabled:-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::-ms-input-placeholder,input[type=tel]:disabled::-ms-input-placeholder,input[type=text]:disabled::-ms-input-placeholder,input[type=url]:disabled::-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::placeholder,input[type=tel]:disabled::placeholder,input[type=text]:disabled::placeholder,input[type=url]:disabled::placeholder{color:#a8bece}textarea{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;height:92px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}textarea:-ms-input-placeholder{color:#87a6bc}textarea::-ms-input-placeholder{color:#87a6bc}textarea::placeholder{color:#87a6bc}textarea:hover{border-color:#a8bece}textarea:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}textarea:focus::-ms-clear{display:none}textarea:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}textarea:disabled:hover{cursor:default}textarea:disabled:-ms-input-placeholder{color:#a8bece}textarea:disabled::-ms-input-placeholder{color:#a8bece}textarea:disabled::placeholder{color:#a8bece}input[type=checkbox]{-webkit-appearance:none;display:inline-block;box-sizing:border-box;margin:2px 0 0;padding:7px 14px;width:16px;height:16px;float:right;outline:0;padding:0;box-shadow:none;background-color:#fff;border:1px solid #c8d7e1;color:#2e4453;font-size:16px;line-height:0;text-align:center;vertical-align:middle;-moz-appearance:none;appearance:none;transition:all .15s ease-in-out;clear:none;cursor:pointer}input[type=checkbox]:checked:before{content:'\f147';font-family:Dashicons;margin:-3px -4px 0 0;float:right;display:inline-block;vertical-align:middle;width:16px;font-size:20px;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;speak:none;color:#00aadc}input[type=checkbox]:disabled:checked:before{color:#a8bece}input[type=checkbox]:hover{border-color:#a8bece}input[type=checkbox]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=checkbox]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1}input[type=checkbox]:disabled:hover{cursor:default}input[type=checkbox]+span{display:block;font-weight:400;margin-right:24px}input[type=radio]{color:#2e4453;font-size:16px;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-sizing:border-box;-webkit-appearance:none;clear:none;cursor:pointer;display:inline-block;line-height:0;height:16px;margin:2px 0 0 4px;float:right;outline:0;padding:0;text-align:center;vertical-align:middle;width:16px;min-width:16px;-moz-appearance:none;appearance:none;border-radius:50%;line-height:10px}input[type=radio]:hover{border-color:#a8bece}input[type=radio]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=radio]:focus::-ms-clear{display:none}input[type=radio]:checked:before{float:right;display:inline-block;content:'\2022';margin:3px;width:8px;height:8px;text-indent:-9999px;background:#00aadc;vertical-align:middle;border-radius:50%;animation:grow .2s ease-in-out}input[type=radio]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1;-webkit-text-fill-color:#a8bece}input[type=radio]:disabled:hover{cursor:default}input[type=radio]:disabled:-ms-input-placeholder{color:#a8bece}input[type=radio]:disabled::-ms-input-placeholder{color:#a8bece}input[type=radio]:disabled::placeholder{color:#a8bece}input[type=radio]:disabled:checked:before{background:#e9eff3}input[type=radio]+span{display:block;font-weight:400;margin-right:24px}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}select{background:#fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center;border-color:#c8d7e1;border-style:solid;border-radius:4px;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:0;outline:0;overflow:hidden;font-size:14px;line-height:21px;font-weight:600;text-overflow:ellipsis;text-decoration:none;vertical-align:top;white-space:nowrap;box-sizing:border-box;padding:7px 14px 9px 32px;-webkit-appearance:none;-moz-appearance:none;appearance:none}select:hover{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==)}select:focus{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa;outline:0;-moz-outline:none;-moz-user-focus:ignore}select:disabled,select:hover:disabled{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat left 10px center}select.is-compact{min-width:0;padding:0 6px 2px 20px;margin:0 4px;background-position:left 5px center;background-size:12px 12px}label select,label+select{display:block;min-width:200px}label select.is-compact,label+select.is-compact{display:inline-block;min-width:0}select::-ms-expand{display:none}select::-ms-value{background:0 0;color:#2e4453}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #2e4453}input[type=submit]{padding:0;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;vertical-align:baseline;background:#fff;border-color:#c8d7e1;border-style:solid;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:24px 0 0;outline:0;overflow:hidden;font-weight:500;text-overflow:ellipsis;text-decoration:none;vertical-align:top;box-sizing:border-box;font-size:14px;line-height:21px;border-radius:4px;padding:7px 14px 9px;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=submit]:hover{border-color:#a8bece;color:#2e4453}input[type=submit]:active{border-width:2px 1px 1px}input[type=submit]:visited{color:#2e4453}input[type=submit]:disabled,input[type=submit][disabled]{color:#e9eff3;background:#fff;border-color:#e9eff3;cursor:default}input[type=submit]:disabled:active,input[type=submit][disabled]:active{border-width:1px 1px 2px}input[type=submit]:focus{border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa}.wpview.wpview-wrap[data-wpview-type=contact-form] iframe.inline-edit-contact-form{width:100%;min-height:500px;border:0;overflow:hidden;margin-bottom:0;display:block}.contact-submit.contact-submit{margin-top:0;margin-bottom:0} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/editor-style.css b/plugins/jetpack/modules/contact-form/css/editor-style.css
new file mode 100644
index 00000000..e060f69a
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-style.css
@@ -0,0 +1,554 @@
+/* ==========================================================================
+** Normalize
+** ======================================================================== */
+
+body,
+label {
+ font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
+ font-size: 16px;
+ line-height: 1.4em;
+}
+
+/* ==========================================================================
+** Card
+** ======================================================================= */
+
+.card {
+ display: block;
+ position: relative;
+ margin: 0 auto;
+ padding: 16px;
+ box-sizing: border-box;
+ background: white;
+ box-shadow: 0 0 0 1px rgba(200, 215, 225, 0.5), 0 1px 2px #e9eff3;
+}
+
+.card:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+
+@media ( min-width: 481px ) {
+ .card {
+ padding: 24px;
+ }
+}
+
+.card > div {
+ margin-top: 24px;
+}
+
+.card > div:first-child {
+ margin-top: 0;
+}
+
+
+/* ==========================================================================
+** Labels
+** ======================================================================= */
+
+label {
+ display: block;
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 5px;
+}
+
+
+/* ==========================================================================
+** Text Inputs
+** ======================================================================= */
+
+input[type="text"],
+input[type="tel"],
+input[type="email"],
+input[type="url"] {
+ border-radius: 0;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+input[type="text"]::placeholder,
+input[type="tel"]::placeholder,
+input[type="email"]::placeholder,
+input[type="url"]::placeholder {
+ color: #87a6bc;
+}
+
+input[type="text"]:hover,
+input[type="tel"]:hover,
+input[type="email"]:hover,
+input[type="url"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="text"]:focus,
+input[type="tel"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="text"]:focus::-ms-clear,
+input[type="tel"]:focus::-ms-clear,
+input[type="email"]:focus::-ms-clear,
+input[type="url"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="text"]:disabled,
+input[type="tel"]:disabled,
+input[type="email"]:disabled,
+input[type="url"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="text"]:disabled:hover,
+input[type="tel"]:disabled:hover,
+input[type="email"]:disabled:hover,
+input[type="url"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="text"]:disabled::placeholder,
+input[type="tel"]:disabled::placeholder,
+input[type="email"]:disabled::placeholder,
+input[type="url"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Textareas
+** ======================================================================= */
+
+textarea {
+ border-radius: 0;
+ appearance: none;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 7px 14px;
+ height: 92px;
+ width: 100%;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 1.5;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-shadow: none;
+}
+
+textarea::placeholder {
+ color: #87a6bc;
+}
+
+textarea:hover {
+ border-color: #a8bece;
+}
+
+textarea:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+textarea:focus::-ms-clear {
+ display: none;
+}
+
+textarea:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ -webkit-text-fill-color: #a8bece;
+}
+
+textarea:disabled:hover {
+ cursor: default;
+}
+
+textarea:disabled::placeholder {
+ color: #a8bece;
+}
+
+
+/* ==========================================================================
+** Checkboxes
+** ======================================================================= */
+
+input[type="checkbox"] {
+ -webkit-appearance: none;
+ display: inline-block;
+ box-sizing: border-box;
+ margin: 2px 0 0;
+ padding: 7px 14px;
+ width: 16px;
+ height: 16px;
+ float: left;
+ outline: 0;
+ padding: 0;
+ box-shadow: none;
+ background-color: #fff;
+ border: 1px solid #c8d7e1;
+ color: #2e4453;
+ font-size: 16px;
+ line-height: 0;
+ text-align: center;
+ vertical-align: middle;
+ appearance: none;
+ transition: all .15s ease-in-out;
+ clear: none;
+ cursor: pointer;
+}
+
+input[type="checkbox"]:checked:before {
+ content: '\f147';
+ font-family: Dashicons;
+ margin: -3px 0 0 -4px;
+ float: left;
+ display: inline-block;
+ vertical-align: middle;
+ width: 16px;
+ font-size: 20px;
+ line-height: 1;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ speak: none;
+ color: #00aadc;
+}
+
+input[type="checkbox"]:disabled:checked:before {
+ color: #a8bece;
+}
+
+input[type="checkbox"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="checkbox"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="checkbox"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+}
+
+input[type="checkbox"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="checkbox"] + span {
+ display: block;
+ font-weight: normal;
+ margin-left: 24px;
+}
+
+
+/* ==========================================================================
+** Radio buttons
+** ======================================================================== */
+
+input[type=radio] {
+ color: #2e4453;
+ font-size: 16px;
+ border: 1px solid #c8d7e1;
+ background-color: #fff;
+ transition: all .15s ease-in-out;
+ box-sizing: border-box;
+ -webkit-appearance: none;
+ clear: none;
+ cursor: pointer;
+ display: inline-block;
+ line-height: 0;
+ height: 16px;
+ margin: 2px 4px 0 0;
+ float: left;
+ outline: 0;
+ padding: 0;
+ text-align: center;
+ vertical-align: middle;
+ width: 16px;
+ min-width: 16px;
+ appearance: none;
+ border-radius: 50%;
+ line-height: 10px;
+}
+
+input[type="radio"]:hover {
+ border-color: #a8bece;
+}
+
+input[type="radio"]:focus {
+ border-color: #0087be;
+ outline: none;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+input[type="radio"]:focus::-ms-clear {
+ display: none;
+}
+
+input[type="radio"]:checked:before {
+ float: left;
+ display: inline-block;
+ content: '\2022';
+ margin: 3px;
+ width: 8px;
+ height: 8px;
+ text-indent: -9999px;
+ background: #00aadc;
+ vertical-align: middle;
+ border-radius: 50%;
+ animation: grow .2s ease-in-out;
+}
+
+input[type="radio"]:disabled {
+ background: #f3f6f8;
+ border-color: #e9eff3;
+ color: #a8bece;
+ opacity: 1;
+ -webkit-text-fill-color: #a8bece;
+}
+
+input[type="radio"]:disabled:hover {
+ cursor: default;
+}
+
+input[type="radio"]:disabled::placeholder {
+ color: #a8bece;
+}
+
+input[type="radio"]:disabled:checked:before {
+ background: #e9eff3;
+}
+
+input[type="radio"] + span {
+ display: block;
+ font-weight: normal;
+ margin-left: 24px;
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@keyframes grow {
+ 0% {
+ transform: scale(0.3);
+ }
+
+ 60% {
+ transform: scale(1.15);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+
+/* ==========================================================================
+** Selects
+** ======================================================================== */
+
+select {
+ background: #fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-radius: 4px;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 0;
+ outline: 0;
+ overflow: hidden;
+ font-size: 14px;
+ line-height: 21px;
+ font-weight: 600;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ white-space: nowrap;
+ box-sizing: border-box;
+ /* Aligns the text to the 8px baseline grid and adds padding on right to allow for the arrow. */
+ padding: 7px 32px 9px 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+select:hover {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==);
+}
+
+select:focus {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+ outline: 0;
+ -moz-outline:none;
+ -moz-user-focus:ignore;
+}
+
+select:disabled,
+select:hover:disabled {
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;;
+}
+
+select.is-compact {
+ min-width: 0;
+ padding: 0 20px 2px 6px;
+ margin: 0 4px;
+ background-position: right 5px center;
+ background-size: 12px 12px;
+}
+
+/* Make it display:block when it follows a label */
+label select,
+label + select {
+ display: block;
+ min-width: 200px;
+}
+
+label select.is-compact,
+label + select.is-compact {
+ display: inline-block;
+ min-width: 0;
+}
+
+/* IE: Remove the default arrow */
+select::-ms-expand {
+ display: none;
+}
+
+/* IE: Remove default background and color styles on focus */
+select::-ms-value {
+ background: none;
+ color: #2e4453;
+}
+
+/* Firefox: Remove the focus outline, see http://stackoverflow.com/questions/3773430/remove-outline-from-select-box-in-ff/18853002#18853002 */
+select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 #2e4453;
+}
+
+
+/* ==========================================================================
+** Buttons
+** ======================================================================== */
+
+input[type="submit"] {
+ padding: 0;
+ font-size: 14px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+ vertical-align: baseline;
+ background: white;
+ border-color: #c8d7e1;
+ border-style: solid;
+ border-width: 1px 1px 2px;
+ color: #2e4453;
+ cursor: pointer;
+ display: inline-block;
+ margin: 24px 0 0;
+ outline: 0;
+ overflow: hidden;
+ font-weight: 500;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ vertical-align: top;
+ box-sizing: border-box;
+ font-size: 14px;
+ line-height: 21px;
+ border-radius: 4px;
+ padding: 7px 14px 9px;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+input[type="submit"]:hover {
+ border-color: #a8bece;
+ color: #2e4453;
+}
+
+input[type="submit"]:active {
+ border-width: 2px 1px 1px;
+}
+
+input[type="submit"]:visited {
+ color: #2e4453;
+}
+
+input[type="submit"][disabled],
+input[type="submit"]:disabled {
+ color: #e9eff3;
+ background: white;
+ border-color: #e9eff3;
+ cursor: default;
+}
+
+input[type="submit"][disabled]:active,
+input[type="submit"]:disabled:active {
+ border-width: 1px 1px 2px;
+}
+
+input[type="submit"]:focus {
+ border-color: #00aadc;
+ box-shadow: 0 0 0 2px #78dcfa;
+}
+
+/* ==========================================================================
+** Preview styles
+** ======================================================================== */
+
+.wpview.wpview-wrap[data-wpview-type=contact-form] iframe.inline-edit-contact-form {
+ width: 100%;
+ min-height: 500px;
+ border: 0;
+ overflow: hidden;
+ margin-bottom: 0;
+ display: block;
+}
+
+.contact-submit.contact-submit {
+ margin-top: 0;
+ margin-bottom: 0;
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-style.min.css b/plugins/jetpack/modules/contact-form/css/editor-style.min.css
new file mode 100644
index 00000000..fdef6b83
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-style.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+body,label{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;font-size:16px;line-height:1.4em}.card{display:block;position:relative;margin:0 auto;padding:16px;box-sizing:border-box;background:#fff;box-shadow:0 0 0 1px rgba(200,215,225,.5),0 1px 2px #e9eff3}.card:after{content:".";display:block;height:0;clear:both;visibility:hidden}@media (min-width:481px){.card{padding:24px}}.card>div{margin-top:24px}.card>div:first-child{margin-top:0}label{display:block;font-size:14px;font-weight:600;margin-bottom:5px}input[type=email],input[type=tel],input[type=text],input[type=url]{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}input[type=email]:-ms-input-placeholder,input[type=tel]:-ms-input-placeholder,input[type=text]:-ms-input-placeholder,input[type=url]:-ms-input-placeholder{color:#87a6bc}input[type=email]::-ms-input-placeholder,input[type=tel]::-ms-input-placeholder,input[type=text]::-ms-input-placeholder,input[type=url]::-ms-input-placeholder{color:#87a6bc}input[type=email]::placeholder,input[type=tel]::placeholder,input[type=text]::placeholder,input[type=url]::placeholder{color:#87a6bc}input[type=email]:hover,input[type=tel]:hover,input[type=text]:hover,input[type=url]:hover{border-color:#a8bece}input[type=email]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=url]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=email]:focus::-ms-clear,input[type=tel]:focus::-ms-clear,input[type=text]:focus::-ms-clear,input[type=url]:focus::-ms-clear{display:none}input[type=email]:disabled,input[type=tel]:disabled,input[type=text]:disabled,input[type=url]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}input[type=email]:disabled:hover,input[type=tel]:disabled:hover,input[type=text]:disabled:hover,input[type=url]:disabled:hover{cursor:default}input[type=email]:disabled:-ms-input-placeholder,input[type=tel]:disabled:-ms-input-placeholder,input[type=text]:disabled:-ms-input-placeholder,input[type=url]:disabled:-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::-ms-input-placeholder,input[type=tel]:disabled::-ms-input-placeholder,input[type=text]:disabled::-ms-input-placeholder,input[type=url]:disabled::-ms-input-placeholder{color:#a8bece}input[type=email]:disabled::placeholder,input[type=tel]:disabled::placeholder,input[type=text]:disabled::placeholder,input[type=url]:disabled::placeholder{color:#a8bece}textarea{border-radius:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;box-sizing:border-box;margin:0;padding:7px 14px;height:92px;width:100%;color:#2e4453;font-size:16px;line-height:1.5;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-shadow:none}textarea:-ms-input-placeholder{color:#87a6bc}textarea::-ms-input-placeholder{color:#87a6bc}textarea::placeholder{color:#87a6bc}textarea:hover{border-color:#a8bece}textarea:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}textarea:focus::-ms-clear{display:none}textarea:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;-webkit-text-fill-color:#a8bece}textarea:disabled:hover{cursor:default}textarea:disabled:-ms-input-placeholder{color:#a8bece}textarea:disabled::-ms-input-placeholder{color:#a8bece}textarea:disabled::placeholder{color:#a8bece}input[type=checkbox]{-webkit-appearance:none;display:inline-block;box-sizing:border-box;margin:2px 0 0;padding:7px 14px;width:16px;height:16px;float:left;outline:0;padding:0;box-shadow:none;background-color:#fff;border:1px solid #c8d7e1;color:#2e4453;font-size:16px;line-height:0;text-align:center;vertical-align:middle;-moz-appearance:none;appearance:none;transition:all .15s ease-in-out;clear:none;cursor:pointer}input[type=checkbox]:checked:before{content:'\f147';font-family:Dashicons;margin:-3px 0 0 -4px;float:left;display:inline-block;vertical-align:middle;width:16px;font-size:20px;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;speak:none;color:#00aadc}input[type=checkbox]:disabled:checked:before{color:#a8bece}input[type=checkbox]:hover{border-color:#a8bece}input[type=checkbox]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=checkbox]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1}input[type=checkbox]:disabled:hover{cursor:default}input[type=checkbox]+span{display:block;font-weight:400;margin-left:24px}input[type=radio]{color:#2e4453;font-size:16px;border:1px solid #c8d7e1;background-color:#fff;transition:all .15s ease-in-out;box-sizing:border-box;-webkit-appearance:none;clear:none;cursor:pointer;display:inline-block;line-height:0;height:16px;margin:2px 4px 0 0;float:left;outline:0;padding:0;text-align:center;vertical-align:middle;width:16px;min-width:16px;-moz-appearance:none;appearance:none;border-radius:50%;line-height:10px}input[type=radio]:hover{border-color:#a8bece}input[type=radio]:focus{border-color:#0087be;outline:0;box-shadow:0 0 0 2px #78dcfa}input[type=radio]:focus::-ms-clear{display:none}input[type=radio]:checked:before{float:left;display:inline-block;content:'\2022';margin:3px;width:8px;height:8px;text-indent:-9999px;background:#00aadc;vertical-align:middle;border-radius:50%;animation:grow .2s ease-in-out}input[type=radio]:disabled{background:#f3f6f8;border-color:#e9eff3;color:#a8bece;opacity:1;-webkit-text-fill-color:#a8bece}input[type=radio]:disabled:hover{cursor:default}input[type=radio]:disabled:-ms-input-placeholder{color:#a8bece}input[type=radio]:disabled::-ms-input-placeholder{color:#a8bece}input[type=radio]:disabled::placeholder{color:#a8bece}input[type=radio]:disabled:checked:before{background:#e9eff3}input[type=radio]+span{display:block;font-weight:400;margin-left:24px}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}@keyframes grow{0%{transform:scale(.3)}60%{transform:scale(1.15)}100%{transform:scale(1)}}select{background:#fff url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjQzhEN0UxIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center;border-color:#c8d7e1;border-style:solid;border-radius:4px;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:0;outline:0;overflow:hidden;font-size:14px;line-height:21px;font-weight:600;text-overflow:ellipsis;text-decoration:none;vertical-align:top;white-space:nowrap;box-sizing:border-box;padding:7px 32px 9px 14px;-webkit-appearance:none;-moz-appearance:none;appearance:none}select:hover{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjYThiZWNlIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==)}select:focus{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiA8dGl0bGU+YXJyb3ctZG93bjwvdGl0bGU+IDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPiA8ZGVmcz48L2RlZnM+IDxnIGlkPSJQYWdlLTEiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHNrZXRjaDp0eXBlPSJNU1BhZ2UiPiA8ZyBpZD0iYXJyb3ctZG93biIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgZmlsbD0iIzJlNDQ1MyI+IDxwYXRoIGQ9Ik0xNS41LDYgTDE3LDcuNSBMMTAuMjUsMTQuMjUgTDMuNSw3LjUgTDUsNiBMMTAuMjUsMTEuMjUgTDE1LjUsNiBaIiBpZD0iRG93bi1BcnJvdyIgc2tldGNoOnR5cGU9Ik1TU2hhcGVHcm91cCI+PC9wYXRoPiA8L2c+IDwvZz48L3N2Zz4=);border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa;outline:0;-moz-outline:none;-moz-user-focus:ignore}select:disabled,select:hover:disabled{background:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c2tldGNoPSJodHRwOi8vd3d3LmJvaGVtaWFuY29kaW5nLmNvbS9za2V0Y2gvbnMiPiAgICAgICAgPHRpdGxlPmFycm93LWRvd248L3RpdGxlPiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4gICAgPGRlZnM+PC9kZWZzPiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4gICAgICAgIDxnIGlkPSJhcnJvdy1kb3duIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjZTllZmYzIj4gICAgICAgICAgICA8cGF0aCBkPSJNMTUuNSw2IEwxNyw3LjUgTDEwLjI1LDE0LjI1IEwzLjUsNy41IEw1LDYgTDEwLjI1LDExLjI1IEwxNS41LDYgWiIgaWQ9IkRvd24tQXJyb3ciIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD4gICAgICAgIDwvZz4gICAgPC9nPjwvc3ZnPg==) no-repeat right 10px center}select.is-compact{min-width:0;padding:0 20px 2px 6px;margin:0 4px;background-position:right 5px center;background-size:12px 12px}label select,label+select{display:block;min-width:200px}label select.is-compact,label+select.is-compact{display:inline-block;min-width:0}select::-ms-expand{display:none}select::-ms-value{background:0 0;color:#2e4453}select:-moz-focusring{color:transparent;text-shadow:0 0 0 #2e4453}input[type=submit]{padding:0;font-size:14px;-webkit-appearance:none;-moz-appearance:none;appearance:none;vertical-align:baseline;background:#fff;border-color:#c8d7e1;border-style:solid;border-width:1px 1px 2px;color:#2e4453;cursor:pointer;display:inline-block;margin:24px 0 0;outline:0;overflow:hidden;font-weight:500;text-overflow:ellipsis;text-decoration:none;vertical-align:top;box-sizing:border-box;font-size:14px;line-height:21px;border-radius:4px;padding:7px 14px 9px;-webkit-appearance:none;-moz-appearance:none;appearance:none}input[type=submit]:hover{border-color:#a8bece;color:#2e4453}input[type=submit]:active{border-width:2px 1px 1px}input[type=submit]:visited{color:#2e4453}input[type=submit]:disabled,input[type=submit][disabled]{color:#e9eff3;background:#fff;border-color:#e9eff3;cursor:default}input[type=submit]:disabled:active,input[type=submit][disabled]:active{border-width:1px 1px 2px}input[type=submit]:focus{border-color:#00aadc;box-shadow:0 0 0 2px #78dcfa}.wpview.wpview-wrap[data-wpview-type=contact-form] iframe.inline-edit-contact-form{width:100%;min-height:500px;border:0;overflow:hidden;margin-bottom:0;display:block}.contact-submit.contact-submit{margin-top:0;margin-bottom:0} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.css b/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.css
new file mode 100644
index 00000000..df4c059d
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.css
@@ -0,0 +1,27 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+i.mce-i-grunion {
+ font-size: 20px;
+}
+
+i.mce-i-grunion:before,
+.jetpack-contact-form-icon:before {
+ width: 24px;
+ vertical-align: top;
+ content: '';
+ display: block;
+ height: 24px;
+ background-size: 24px;
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="rgb(85, 93, 102)" 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>');
+ margin-top: -4px;
+}
+i.mce-i-grunion:before {
+ margin-top: -2px;
+ margin-right: -2px;
+}
+
+.jetpack-contact-form-icon {
+ opacity: 0.7;
+ vertical-align: text-top;
+ display: inline-block;
+ height: 18px;
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.min.css b/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.min.css
new file mode 100644
index 00000000..3d7d4a17
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-ui-rtl.min.css
@@ -0,0 +1 @@
+i.mce-i-grunion{font-size:20px}.jetpack-contact-form-icon:before,i.mce-i-grunion:before{width:24px;vertical-align:top;content:'';display:block;height:24px;background-size:24px;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="rgb(85, 93, 102)" 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>');margin-top:-4px}i.mce-i-grunion:before{margin-top:-2px;margin-right:-2px}.jetpack-contact-form-icon{opacity:.7;vertical-align:text-top;display:inline-block;height:18px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/editor-ui.css b/plugins/jetpack/modules/contact-form/css/editor-ui.css
new file mode 100644
index 00000000..b68f810b
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-ui.css
@@ -0,0 +1,26 @@
+i.mce-i-grunion {
+ font-size: 20px;
+}
+
+i.mce-i-grunion:before,
+.jetpack-contact-form-icon:before {
+ width: 24px;
+ vertical-align: top;
+ content: '';
+ display: block;
+ height: 24px;
+ background-size: 24px;
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="rgb(85, 93, 102)" 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>');
+ margin-top: -4px;
+}
+i.mce-i-grunion:before {
+ margin-top: -2px;
+ margin-left: -2px;
+}
+
+.jetpack-contact-form-icon {
+ opacity: 0.7;
+ vertical-align: text-top;
+ display: inline-block;
+ height: 18px;
+}
diff --git a/plugins/jetpack/modules/contact-form/css/editor-ui.min.css b/plugins/jetpack/modules/contact-form/css/editor-ui.min.css
new file mode 100644
index 00000000..f7c96dfb
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/editor-ui.min.css
@@ -0,0 +1,2 @@
+/* Do not modify this file directly. It is concatenated from individual module CSS files. */
+i.mce-i-grunion{font-size:20px}.jetpack-contact-form-icon:before,i.mce-i-grunion:before{width:24px;vertical-align:top;content:'';display:block;height:24px;background-size:24px;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="rgb(85, 93, 102)" 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>');margin-top:-4px}i.mce-i-grunion:before{margin-top:-2px;margin-left:-2px}.jetpack-contact-form-icon{opacity:.7;vertical-align:text-top;display:inline-block;height:18px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/grunion-rtl.css b/plugins/jetpack/modules/contact-form/css/grunion-rtl.css
new file mode 100644
index 00000000..952b25fa
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/grunion-rtl.css
@@ -0,0 +1 @@
+.contact-form .clear-form{clear:both}.contact-form input:-ms-input-placeholder{transition:opacity .3s ease-out}.contact-form input::-ms-input-placeholder{transition:opacity .3s ease-out}.contact-form input::placeholder{transition:opacity .3s ease-out}.contact-form input:hover:-ms-input-placeholder{opacity:.5}.contact-form input:hover::-ms-input-placeholder{opacity:.5}.contact-form input:hover::placeholder{opacity:.5}.contact-form input:focus:-ms-input-placeholder{opacity:.3}.contact-form input:focus::-ms-input-placeholder{opacity:.3}.contact-form input:focus::placeholder{opacity:.3}.contact-form input[type=email],.contact-form input[type=text],.contact-form input[type=url]{width:300px;max-width:98%;margin-bottom:13px}.contact-form select{margin-bottom:13px}.contact-form textarea{height:200px;width:80%;float:none;margin-bottom:13px}.contact-form input[type=checkbox],.contact-form input[type=radio]{float:none;margin-bottom:13px}.contact-form label{margin-bottom:3px;float:none;font-weight:700;display:block}.contact-form label.checkbox,.contact-form label.radio{margin-bottom:3px;float:none;font-weight:700;display:inline-block}.contact-form label span{color:#aaa;margin-right:4px;font-weight:400}.contact-form-submission{margin-bottom:4em;padding:1.5em 1em}.contact-form-submission p{margin:0 auto;word-wrap:break-word}.form-errors .form-error-message{color:red}.textwidget .contact-form input[type=email],.textwidget .contact-form input[type=text],.textwidget .contact-form input[type=url],.textwidget .contact-form textarea{width:250px;max-width:100%;box-sizing:border-box}#jetpack-check-feedback-spam{margin:1px 0 0 8px}.jetpack-check-feedback-spam-spinner{display:inline-block;margin-top:7px} \ No newline at end of file
diff --git a/plugins/jetpack/modules/contact-form/css/grunion.css b/plugins/jetpack/modules/contact-form/css/grunion.css
new file mode 100644
index 00000000..1089188d
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/grunion.css
@@ -0,0 +1,91 @@
+.contact-form .clear-form {
+ clear: both;
+}
+
+.contact-form input::placeholder {
+ transition: opacity .3s ease-out;
+}
+.contact-form input:hover::placeholder {
+ opacity: 0.5;
+}
+.contact-form input:focus::placeholder {
+ opacity: 0.3;
+}
+
+.contact-form input[type='text'],
+.contact-form input[type='email'],
+.contact-form input[type='url'] {
+ width: 300px;
+ max-width: 98%;
+ margin-bottom: 13px;
+}
+
+.contact-form select {
+ margin-bottom: 13px;
+}
+
+.contact-form textarea {
+ height: 200px;
+ width: 80%;
+ float: none;
+ margin-bottom: 13px;
+}
+
+.contact-form input[type='radio'],
+.contact-form input[type='checkbox'] {
+ float: none;
+ margin-bottom: 13px;
+}
+
+.contact-form label {
+ margin-bottom: 3px;
+ float: none;
+ font-weight: bold;
+ display: block;
+}
+
+.contact-form label.checkbox,
+.contact-form label.radio {
+ margin-bottom: 3px;
+ float: none;
+ font-weight: bold;
+ display: inline-block;
+}
+
+.contact-form label span {
+ color: #AAA;
+ margin-left: 4px;
+ font-weight: normal;
+}
+
+.contact-form-submission {
+ margin-bottom: 4em;
+ padding: 1.5em 1em;
+}
+
+.contact-form-submission p {
+ margin: 0 auto;
+ word-wrap: break-word;
+}
+
+.form-errors .form-error-message {
+ color: red;
+}
+
+.textwidget .contact-form input[type='text'],
+.textwidget .contact-form input[type='email'],
+.textwidget .contact-form input[type='url'],
+.textwidget .contact-form textarea {
+ width: 250px;
+ max-width: 100%;
+ box-sizing: border-box;
+}
+
+#jetpack-check-feedback-spam {
+ margin: 1px 8px 0px 0px;
+}
+
+.jetpack-check-feedback-spam-spinner {
+ display: inline-block;
+ margin-top: 7px;
+}
diff --git a/plugins/jetpack/modules/contact-form/css/jquery-ui-datepicker.css b/plugins/jetpack/modules/contact-form/css/jquery-ui-datepicker.css
new file mode 100644
index 00000000..e82283eb
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/css/jquery-ui-datepicker.css
@@ -0,0 +1,160 @@
+.ui-datepicker {
+ padding: 0;
+ margin: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ background-color: #fff;
+ border: 1px solid #dfdfdf;
+ border-top: none;
+ -webkit-box-shadow: 0 3px 6px rgba(0, 0, 0, 0.075);
+ box-shadow: 0 3px 6px rgba(0, 0, 0, 0.075);
+ width: auto;
+}
+
+.ui-datepicker * {
+ padding: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+}
+
+.ui-datepicker table {
+ width: auto;
+ margin: 0;
+ border: none;
+ border-collapse: collapse;
+}
+
+.ui-datepicker .ui-widget-header,
+.ui-datepicker .ui-datepicker-header {
+ background-image: none;
+ border: none;
+ font-weight: normal;
+}
+
+.ui-datepicker .ui-datepicker-header .ui-state-hover {
+ background: transparent;
+ border-color: transparent;
+ cursor: pointer;
+}
+
+.ui-datepicker .ui-datepicker-title {
+ margin: 0;
+ padding: 10px 0;
+ font-size: 14px;
+ line-height: 14px;
+ text-align: center;
+}
+
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: relative;
+ top: 0;
+ height: 34px;
+ width: 34px;
+}
+
+.ui-datepicker .ui-state-hover.ui-datepicker-prev,
+.ui-datepicker .ui-state-hover.ui-datepicker-next {
+ border: none;
+}
+
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-prev-hover {
+ left: 0;
+}
+
+.ui-datepicker .ui-datepicker-next,
+.ui-datepicker .ui-datepicker-next-hover {
+ right: 0;
+}
+
+.ui-datepicker .ui-datepicker-next span,
+.ui-datepicker .ui-datepicker-prev span {
+ display: none;
+}
+
+.ui-datepicker .ui-datepicker-prev {
+ float: left;
+}
+
+.ui-datepicker .ui-datepicker-next {
+ float: right;
+}
+
+.ui-datepicker .ui-datepicker-prev:before,
+.ui-datepicker .ui-datepicker-next:before {
+ font: normal 20px/34px 'dashicons';
+ padding-left: 7px;
+ speak: none;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ width: 34px;
+ height: 34px;
+}
+
+.ui-datepicker .ui-datepicker-prev:before {
+ content: '\f341';
+}
+
+.ui-datepicker .ui-datepicker-next:before {
+ content: '\f345';
+}
+
+.ui-datepicker .ui-datepicker-prev-hover:before,
+.ui-datepicker .ui-datepicker-next-hover:before {
+ opacity: 0.7;
+}
+
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+ width: 33%;
+}
+
+.ui-datepicker thead {
+ font-weight: 600;
+}
+
+.ui-datepicker th {
+ padding: 10px;
+ border-width: 1px;
+}
+
+.ui-datepicker td {
+ padding: 0;
+ border: 1px solid #f4f4f4;
+}
+
+.ui-datepicker td.ui-datepicker-other-month {
+ border: transparent;
+}
+
+.ui-datepicker td.ui-datepicker-week-end {
+ background-color: #f4f4f4;
+ border: 1px solid #f4f4f4;
+}
+
+.ui-datepicker td.ui-datepicker-today {
+ background-color: #f0f0c0;
+}
+
+.ui-datepicker td.ui-datepicker-current-day {
+ background: #bbdd88;
+}
+
+.ui-datepicker td .ui-state-default {
+ background: transparent;
+ border: none;
+ text-align: center;
+ text-decoration: none;
+ width: auto;
+ display: block;
+ padding: 5px 10px;
+ font-weight: normal;
+ color: #444;
+}
+
+.ui-datepicker td.ui-state-disabled .ui-state-default {
+ opacity: 0.5;
+}
diff --git a/plugins/jetpack/modules/contact-form/grunion-contact-form.php b/plugins/jetpack/modules/contact-form/grunion-contact-form.php
new file mode 100644
index 00000000..a26dc20c
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/grunion-contact-form.php
@@ -0,0 +1,3501 @@
+<?php
+
+/*
+Plugin Name: Grunion Contact Form
+Description: 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. As seen on WordPress.com.
+Plugin URI: http://automattic.com/#
+AUthor: Automattic, Inc.
+Author URI: http://automattic.com/
+Version: 2.4
+License: GPLv2 or later
+*/
+
+define( 'GRUNION_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
+define( 'GRUNION_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
+
+if ( is_admin() ) {
+ require_once GRUNION_PLUGIN_DIR . 'admin.php';
+}
+
+add_action( 'rest_api_init', 'grunion_contact_form_require_endpoint' );
+function grunion_contact_form_require_endpoint() {
+ require_once GRUNION_PLUGIN_DIR . 'class-grunion-contact-form-endpoint.php';
+}
+
+/**
+ * Sets up various actions, filters, post types, post statuses, shortcodes.
+ */
+class Grunion_Contact_Form_Plugin {
+
+ /**
+ * @var string The Widget ID of the widget currently being processed. Used to build the unique contact-form ID for forms embedded in widgets.
+ */
+ public $current_widget_id;
+
+ static $using_contact_form_field = false;
+
+ /**
+ * @var int The last Feedback Post ID Erased as part of the Personal Data Eraser.
+ * Helps with pagination.
+ */
+ private $pde_last_post_id_erased = 0;
+
+ /**
+ * @var string The email address for which we are deleting/exporting all feedbacks
+ * as part of a Personal Data Eraser or Personal Data Exporter request.
+ */
+ private $pde_email_address = '';
+
+ static function init() {
+ static $instance = false;
+
+ if ( ! $instance ) {
+ $instance = new Grunion_Contact_Form_Plugin;
+
+ // Schedule our daily cleanup
+ add_action( 'wp_scheduled_delete', array( $instance, 'daily_akismet_meta_cleanup' ) );
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Runs daily to clean up spam detection metadata after 15 days. Keeps your DB squeaky clean.
+ */
+ public function daily_akismet_meta_cleanup() {
+ global $wpdb;
+
+ $feedback_ids = $wpdb->get_col( "SELECT p.ID FROM {$wpdb->posts} as p INNER JOIN {$wpdb->postmeta} as m on m.post_id = p.ID WHERE p.post_type = 'feedback' AND m.meta_key = '_feedback_akismet_values' AND DATE_SUB(NOW(), INTERVAL 15 DAY) > p.post_date_gmt LIMIT 10000" );
+
+ if ( empty( $feedback_ids ) ) {
+ return;
+ }
+
+ /**
+ * Fires right before deleting the _feedback_akismet_values post meta on $feedback_ids
+ *
+ * @module contact-form
+ *
+ * @since 6.1.0
+ *
+ * @param array $feedback_ids list of feedback post ID
+ */
+ do_action( 'jetpack_daily_akismet_meta_cleanup_before', $feedback_ids );
+ foreach ( $feedback_ids as $feedback_id ) {
+ delete_post_meta( $feedback_id, '_feedback_akismet_values' );
+ }
+
+ /**
+ * Fires right after deleting the _feedback_akismet_values post meta on $feedback_ids
+ *
+ * @module contact-form
+ *
+ * @since 6.1.0
+ *
+ * @param array $feedback_ids list of feedback post ID
+ */
+ do_action( 'jetpack_daily_akismet_meta_cleanup_after', $feedback_ids );
+ }
+
+ /**
+ * Strips HTML tags from input. Output is NOT HTML safe.
+ *
+ * @param mixed $data_with_tags
+ * @return mixed
+ */
+ 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() );
+ $value = str_replace( '&amp;', '&', $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 = str_replace( '&amp;', '&', $data_without_tags ); // undo damage done by wp_kses_normalize_entities()
+ }
+
+ return $data_without_tags;
+ }
+
+ /**
+ * Class uses singleton pattern; use Grunion_Contact_Form_Plugin::init() to initialize.
+ */
+ protected function __construct() {
+ $this->add_shortcode();
+
+ // While generating the output of a text widget with a contact-form shortcode, we need to know its widget ID.
+ add_action( 'dynamic_sidebar', array( $this, 'track_current_widget' ) );
+
+ // Add a "widget" shortcode attribute to all contact-form shortcodes embedded in widgets
+ add_filter( 'widget_text', array( $this, 'widget_atts' ), 0 );
+
+ // If Text Widgets don't get shortcode processed, hack ours into place.
+ if (
+ version_compare( get_bloginfo( 'version' ), '4.9-z', '<=' )
+ && ! has_filter( 'widget_text', 'do_shortcode' )
+ ) {
+ add_filter( 'widget_text', array( $this, 'widget_shortcode_hack' ), 5 );
+ }
+
+ add_filter( 'jetpack_contact_form_is_spam', array( $this, 'is_spam_blacklist' ), 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 );
+ add_action( 'contact_form_akismet', array( $this, 'akismet_submit' ), 10, 2 );
+ }
+
+ add_action( 'loop_start', 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' ) );
+
+ // GDPR: personal data exporter & eraser.
+ add_filter( 'wp_privacy_personal_data_exporters', array( $this, 'register_personal_data_exporter' ) );
+ add_filter( 'wp_privacy_personal_data_erasers', array( $this, 'register_personal_data_eraser' ) );
+
+ // Export to CSV feature
+ 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' ) );
+ }
+
+ // 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' ),
+ ),
+ // 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>'),
+ 'show_ui' => true,
+ 'show_in_admin_bar' => false,
+ 'public' => false,
+ 'rewrite' => false,
+ 'query_var' => false,
+ 'capability_type' => 'page',
+ 'show_in_rest' => true,
+ 'rest_controller_class' => 'Grunion_Contact_Form_Endpoint',
+ 'capabilities' => array(
+ 'create_posts' => false,
+ 'publish_posts' => 'publish_pages',
+ 'edit_posts' => 'edit_pages',
+ 'edit_others_posts' => 'edit_others_pages',
+ 'delete_posts' => 'delete_pages',
+ 'delete_others_posts' => 'delete_others_pages',
+ 'read_private_posts' => 'read_private_pages',
+ 'edit_post' => 'edit_page',
+ 'delete_post' => 'delete_page',
+ 'read_post' => 'read_page',
+ ),
+ 'map_meta_cap' => true,
+ )
+ );
+
+ // Add to REST API post type whitelist
+ add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_feedback_rest_api_type' ) );
+
+ // Add "spam" as a post status
+ register_post_status(
+ 'spam', array(
+ 'label' => 'Spam',
+ 'public' => false,
+ 'exclude_from_search' => true,
+ 'show_in_admin_all_list' => false,
+ 'label_count' => _n_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'jetpack' ),
+ 'protected' => true,
+ '_builtin' => false,
+ )
+ );
+
+ // POST handler
+ if (
+ isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] )
+ &&
+ isset( $_POST['action'] ) && 'grunion-contact-form' == $_POST['action']
+ &&
+ isset( $_POST['contact-form-id'] )
+ ) {
+ add_action( 'template_redirect', array( $this, 'process_form_submission' ) );
+ }
+
+ /*
+ Can be dequeued by placing the following in wp-content/themes/yourtheme/functions.php
+ *
+ * function remove_grunion_style() {
+ * wp_deregister_style('grunion.css');
+ * }
+ * add_action('wp_print_styles', 'remove_grunion_style');
+ */
+ wp_register_style( 'grunion.css', GRUNION_PLUGIN_URL . 'css/grunion.css', array(), JETPACK__VERSION );
+ wp_style_add_data( 'grunion.css', 'rtl', 'replace' );
+
+ self::register_contact_form_blocks();
+ }
+
+ private static function register_contact_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' ),
+ ) );
+ }
+
+ public static function gutenblock_render_form( $atts, $content ) {
+ return Grunion_Contact_Form::parse( $atts, do_blocks( $content ) );
+ }
+
+ public static function block_attributes_to_shortcode_attributes( $atts, $type ) {
+ $atts['type'] = $type;
+ if ( isset( $atts['className'] ) ) {
+ $atts['class'] = $atts['className'];
+ unset( $atts['className'] );
+ }
+
+ if ( isset( $atts['defaultValue'] ) ) {
+ $atts['default'] = $atts['defaultValue'];
+ unset( $atts['defaultValue'] );
+ }
+
+ return $atts;
+ }
+
+ public static function gutenblock_render_field_text( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'text' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_name( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'name' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_email( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'email' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_url( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'url' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_date( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'date' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_telephone( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'telephone' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_textarea( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'textarea' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_checkbox( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'checkbox' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_checkbox_multiple( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'checkbox-multiple' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_radio( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'radio' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+ public static function gutenblock_render_field_select( $atts, $content ) {
+ $atts = self::block_attributes_to_shortcode_attributes( $atts, 'select' );
+ return Grunion_Contact_Form::parse_contact_field( $atts, $content );
+ }
+
+ /**
+ * Add the 'Export' menu item as a submenu of Feedback.
+ */
+ public function admin_menu() {
+ add_submenu_page(
+ 'edit.php?post_type=feedback',
+ __( 'Export feedback as CSV', 'jetpack' ),
+ __( 'Export CSV', 'jetpack' ),
+ 'export',
+ 'feedback-export',
+ array( $this, 'export_form' )
+ );
+ }
+
+ /**
+ * Add to REST API post type whitelist
+ */
+ function allow_feedback_rest_api_type( $post_types ) {
+ $post_types[] = 'feedback';
+ return $post_types;
+ }
+
+ /**
+ * Display the count of new feedback entries received. It's reset when user visits the Feedback screen.
+ *
+ * @since 4.1.0
+ *
+ * @param object $screen Information about the current screen.
+ */
+ function unread_count( $screen ) {
+ 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 ) {
+ 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;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles all contact-form POST submissions
+ *
+ * Conditionally attached to `template_redirect`
+ */
+ function process_form_submission() {
+ // Add a filter to replace tokens in the subject field with sanitized field values
+ 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 = preg_replace( '/[^\da-f]/i', '', $hash );
+
+ if ( is_user_logged_in() ) {
+ check_admin_referer( "contact-form_{$id}" );
+ }
+
+ $is_widget = 0 === strpos( $id, 'widget-' );
+
+ $form = false;
+
+ if ( $is_widget ) {
+ // It's a form embedded in a text widget
+ $this->current_widget_id = substr( $id, 7 ); // remove "widget-"
+ $widget_type = implode( '-', array_slice( explode( '-', $this->current_widget_id ), 0, -1 ) ); // Remove trailing -#
+
+ // Is the widget active?
+ $sidebar = is_active_widget( false, $this->current_widget_id, $widget_type );
+
+ // This is lame - no core API for getting a widget by ID
+ $widget = isset( $GLOBALS['wp_registered_widgets'][ $this->current_widget_id ] ) ? $GLOBALS['wp_registered_widgets'][ $this->current_widget_id ] : false;
+
+ if ( $sidebar && $widget && isset( $widget['callback'] ) ) {
+ // prevent PHP notices by populating widget args
+ $widget_args = array(
+ 'before_widget' => '',
+ 'after_widget' => '',
+ 'before_title' => '',
+ 'after_title' => '',
+ );
+ // This is lamer - no API for outputting a given widget by ID
+ ob_start();
+ // Process the widget to populate Grunion_Contact_Form::$last
+ call_user_func( $widget['callback'], $widget_args, $widget['params'][0] );
+ ob_end_clean();
+ }
+ } else {
+ // It's a form embedded in a post
+ $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 );
+ }
+
+ $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 ) {
+
+ // Get shortcode from post meta
+ $shortcode = get_post_meta( $_POST['contact-form-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 );
+ if ( ! empty( $attributes ) && is_array( $attributes ) ) {
+ foreach ( array_filter( $attributes ) as $param => $value ) {
+ $parameters .= " $param=\"$value\"";
+ }
+ }
+
+ $shortcode = '[contact-form' . $parameters . ']' . $shortcode . '[/contact-form]';
+ do_shortcode( $shortcode );
+
+ // Recreate form
+ $form = Grunion_Contact_Form::$last;
+ }
+
+ if ( ! $form ) {
+ return false;
+ }
+ }
+
+ if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) {
+ return $form->errors;
+ }
+
+ // Process the form
+ return $form->process_submission();
+ }
+
+ function ajax_request() {
+ $submission_result = self::process_form_submission();
+
+ if ( ! $submission_result ) {
+ header( 'HTTP/1.1 500 Server Error', 500, true );
+ echo '<div class="form-error"><ul class="form-errors"><li class="form-error-message">';
+ esc_html_e( 'An error occurred. Please try again later.', 'jetpack' );
+ echo '</li></ul></div>';
+ } elseif ( is_wp_error( $submission_result ) ) {
+ header( 'HTTP/1.1 400 Bad Request', 403, true );
+ echo '<div class="form-error"><ul class="form-errors"><li class="form-error-message">';
+ echo esc_html( $submission_result->get_error_message() );
+ echo '</li></ul></div>';
+ } else {
+ echo '<h3>' . esc_html__( 'Message Sent', 'jetpack' ) . '</h3>' . $submission_result;
+ }
+
+ die;
+ }
+
+ /**
+ * Ensure the post author is always zero for contact-form feedbacks
+ * Attached to `wp_insert_post_data`
+ *
+ * @see Grunion_Contact_Form::process_submission()
+ *
+ * @param array $data the data to insert
+ * @param array $postarr the data sent to wp_insert_post()
+ * @return array The filtered $data to insert
+ */
+ function insert_feedback_filter( $data, $postarr ) {
+ if ( $data['post_type'] == 'feedback' && $postarr['post_type'] == 'feedback' ) {
+ $data['post_author'] = 0;
+ }
+
+ return $data;
+ }
+ /*
+ * Adds our contact-form shortcode
+ * The "child" contact-field shortcode is enabled as needed by the contact-form shortcode handler
+ */
+ function add_shortcode() {
+ add_shortcode( 'contact-form', array( 'Grunion_Contact_Form', 'parse' ) );
+ add_shortcode( 'contact-field', array( 'Grunion_Contact_Form', 'parse_contact_field' ) );
+ }
+
+ static function tokenize_label( $label ) {
+ return '{' . trim( preg_replace( '#^\d+_#', '', $label ) ) . '}';
+ }
+
+ static function sanitize_value( $value ) {
+ return preg_replace( '=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $value );
+ }
+
+ /**
+ * Replaces tokens like {city} or {City} (case insensitive) with the value
+ * of an input field of that name
+ *
+ * @param string $subject
+ * @param array $field_values Array with field label => field value associations
+ *
+ * @return string The filtered $subject with the tokens replaced
+ */
+ function replace_tokens_with_input( $subject, $field_values ) {
+ // Wrap labels into tokens (inside {})
+ $wrapped_labels = array_map( array( 'Grunion_Contact_Form_Plugin', 'tokenize_label' ), array_keys( $field_values ) );
+ // Sanitize all values
+ $sanitized_values = array_map( array( 'Grunion_Contact_Form_Plugin', 'sanitize_value' ), array_values( $field_values ) );
+
+ foreach ( $sanitized_values as $k => $sanitized_value ) {
+ if ( is_array( $sanitized_value ) ) {
+ $sanitized_values[ $k ] = implode( ', ', $sanitized_value );
+ }
+ }
+
+ // Search for all valid tokens (based on existing fields) and replace with the field's value
+ $subject = str_ireplace( $wrapped_labels, $sanitized_values, $subject );
+ return $subject;
+ }
+
+ /**
+ * Tracks the widget currently being processed.
+ * Attached to `dynamic_sidebar`
+ *
+ * @see $current_widget_id
+ *
+ * @param array $widget The widget data
+ */
+ function track_current_widget( $widget ) {
+ $this->current_widget_id = $widget['id'];
+ }
+
+ /**
+ * Adds a "widget" attribute to every contact-form embedded in a text widget.
+ * Used to tell the difference between post-embedded contact-forms and widget-embedded contact-forms
+ * Attached to `widget_text`
+ *
+ * @param string $text The widget text
+ * @return string The filtered widget text
+ */
+ function widget_atts( $text ) {
+ Grunion_Contact_Form::style( true );
+
+ return preg_replace( '/\[contact-form([^a-zA-Z_-])/', '[contact-form widget="' . $this->current_widget_id . '"\\1', $text );
+ }
+
+ /**
+ * For sites where text widgets are not processed for shortcodes, we add this hack to process just our shortcode
+ * Attached to `widget_text`
+ *
+ * @param string $text The widget text
+ * @return string The contact-form filtered widget text
+ */
+ function widget_shortcode_hack( $text ) {
+ if ( ! preg_match( '/\[contact-form([^a-zA-Z_-])/', $text ) ) {
+ return $text;
+ }
+
+ $old = $GLOBALS['shortcode_tags'];
+ remove_all_shortcodes();
+ Grunion_Contact_Form_Plugin::$using_contact_form_field = true;
+ $this->add_shortcode();
+
+ $text = do_shortcode( $text );
+
+ Grunion_Contact_Form_Plugin::$using_contact_form_field = false;
+ $GLOBALS['shortcode_tags'] = $old;
+
+ return $text;
+ }
+
+ /**
+ * Check if a submission matches the Comment Blacklist.
+ * The Comment Blacklist 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`
+ *
+ * @param bool $is_spam
+ * @param array $form
+ * @return bool TRUE => spam, FALSE => not spam
+ */
+ function is_spam_blacklist( $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 true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Populate an array with all values necessary to submit a NEW contact-form feedback to Akismet.
+ * Note that this includes the current user_ip etc, so this should only be called when accepting a new item via $_POST
+ *
+ * @param array $form Contact form feedback array
+ * @return array feedback array with additional data ready for submission to Akismet
+ */
+ 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['blog'] = get_option( 'home' );
+
+ foreach ( $_SERVER as $key => $value ) {
+ if ( ! is_string( $value ) ) {
+ continue;
+ }
+ if ( in_array( $key, array( 'HTTP_COOKIE', 'HTTP_COOKIE2', 'HTTP_USER_AGENT', 'HTTP_REFERER' ) ) ) {
+ // We don't care about cookies, and the UA and Referrer were caught above.
+ continue;
+ } elseif ( in_array( $key, array( 'REMOTE_ADDR', 'REQUEST_URI', 'DOCUMENT_URI' ) ) ) {
+ // All three of these are relevant indicators and should be passed along.
+ $form[ $key ] = $value;
+ } elseif ( wp_startswith( $key, 'HTTP_' ) ) {
+ // Any other HTTP header indicators.
+ // `wp_startswith()` is a wpcom helper function and is included in Jetpack via `functions.compat.php`
+ $form[ $key ] = $value;
+ }
+ }
+
+ return $form;
+ }
+
+ /**
+ * Submit contact-form data to Akismet to check for spam.
+ * If you're accepting a new item via $_POST, run it Grunion_Contact_Form_Plugin::prepare_for_akismet() first
+ * Attached to `jetpack_contact_form_is_spam`
+ *
+ * @param bool $is_spam
+ * @param array $form
+ * @return bool|WP_Error TRUE => spam, FALSE => not spam, WP_Error => stop processing entirely
+ */
+ function is_spam_akismet( $is_spam, $form = array() ) {
+ global $akismet_api_host, $akismet_api_port;
+
+ // The signature of this function changed from accepting just $form.
+ // If something only sends an array, assume it's still using the old
+ // signature and work around it.
+ if ( empty( $form ) && is_array( $is_spam ) ) {
+ $form = $is_spam;
+ $is_spam = false;
+ }
+
+ // If a previous filter has alrady marked this as spam, trust that and move on.
+ if ( $is_spam ) {
+ return $is_spam;
+ }
+
+ if ( ! function_exists( 'akismet_http_post' ) && ! defined( 'AKISMET_VERSION' ) ) {
+ return false;
+ }
+
+ $query_string = http_build_query( $form );
+
+ if ( method_exists( 'Akismet', 'http_post' ) ) {
+ $response = Akismet::http_post( $query_string, 'comment-check' );
+ } else {
+ $response = akismet_http_post( $query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port );
+ }
+
+ $result = false;
+
+ if ( isset( $response[0]['x-akismet-pro-tip'] ) && 'discard' === trim( $response[0]['x-akismet-pro-tip'] ) && get_option( 'akismet_strictness' ) === '1' ) {
+ $result = new WP_Error( 'feedback-discarded', __( 'Feedback discarded.', 'jetpack' ) );
+ } elseif ( isset( $response[1] ) && 'true' == trim( $response[1] ) ) { // 'true' is spam
+ $result = true;
+ }
+
+ /**
+ * Filter the results returned by Akismet for each submitted contact form.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param WP_Error|bool $result Is the submitted feedback spam.
+ * @param array|bool $form Submitted feedback.
+ */
+ return apply_filters( 'contact_form_is_spam_akismet', $result, $form );
+ }
+
+ /**
+ * Submit a feedback as either spam or ham
+ *
+ * @param string $as Either 'spam' or 'ham'.
+ * @param array $form the contact-form data
+ */
+ function akismet_submit( $as, $form ) {
+ global $akismet_api_host, $akismet_api_port;
+
+ if ( ! in_array( $as, array( 'ham', 'spam' ) ) ) {
+ return false;
+ }
+
+ $query_string = '';
+ if ( is_array( $form ) ) {
+ $query_string = http_build_query( $form );
+ }
+ if ( method_exists( 'Akismet', 'http_post' ) ) {
+ $response = Akismet::http_post( $query_string, "submit-{$as}" );
+ } else {
+ $response = akismet_http_post( $query_string, $akismet_api_host, "/1.1/submit-{$as}", $akismet_api_port );
+ }
+
+ return trim( $response[1] );
+ }
+
+ /**
+ * Prints the menu
+ */
+ function export_form() {
+ $current_screen = get_current_screen();
+ if ( ! in_array( $current_screen->id, array( 'edit-feedback', 'feedback_page_feedback-export' ) ) ) {
+ return;
+ }
+
+ if ( ! current_user_can( 'export' ) ) {
+ return;
+ }
+
+ // if there aren't any feedbacks, bail out
+ if ( ! (int) wp_count_posts( 'feedback' )->publish ) {
+ return;
+ }
+ ?>
+
+ <div id="feedback-export" style="display:none">
+ <h2><?php _e( 'Export feedback 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>
+ <select name="post">
+ <option value="all"><?php esc_html_e( 'All posts', 'jetpack' ); ?></option>
+ <?php echo $this->get_feedbacks_as_options(); ?>
+ </select>
+
+ <br><br>
+ <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Download', 'jetpack' ); ?>">
+ </form>
+ </div>
+
+ <?php
+ // There aren't any usable actions in core to output the "export feedback" form in the correct place,
+ // so this inline JS moves it from the top of the page to the bottom.
+ ?>
+ <script type='text/javascript'>
+ var menu = document.getElementById( 'feedback-export' ),
+ wrapper = document.getElementsByClassName( 'wrap' )[0];
+ <?php if ( 'edit-feedback' === $current_screen->id ) : ?>
+ wrapper.appendChild(menu);
+ <?php endif; ?>
+ menu.style.display = 'block';
+ </script>
+ <?php
+ }
+
+ /**
+ * Fetch post content for a post and extract just the comment.
+ *
+ * @param int $post_id The post id to fetch the content for.
+ *
+ * @return string Trimmed post comment.
+ *
+ * @codeCoverageIgnore
+ */
+ public function get_post_content_for_csv_export( $post_id ) {
+ $post_content = get_post_field( 'post_content', $post_id );
+ $content = explode( '<!--more-->', $post_content );
+
+ return trim( $content[0] );
+ }
+
+ /**
+ * Get `_feedback_extra_fields` field from post meta data.
+ *
+ * @param int $post_id Id of the post to fetch meta data for.
+ *
+ * @return mixed
+ *
+ * @codeCoverageIgnore - No need to be covered.
+ */
+ public function get_post_meta_for_csv_export( $post_id ) {
+ return get_post_meta( $post_id, '_feedback_extra_fields', true );
+ }
+
+ /**
+ * Get parsed feedback post fields.
+ *
+ * @param int $post_id Id of the post to fetch parsed contents for.
+ *
+ * @return array
+ *
+ * @codeCoverageIgnore - No need to be covered.
+ */
+ public function get_parsed_field_contents_of_post( $post_id ) {
+ return self::parse_fields_from_content( $post_id );
+ }
+
+ /**
+ * Properly maps fields that are missing from the post meta data
+ * to names, that are similar to those of the post meta.
+ *
+ * @param array $parsed_post_content Parsed post content
+ *
+ * @see parse_fields_from_content for how the input data is generated.
+ *
+ * @return array Mapped fields.
+ */
+ public function map_parsed_field_contents_of_post_to_field_names( $parsed_post_content ) {
+
+ $mapped_fields = array();
+
+ $field_mapping = array(
+ '_feedback_subject' => __( 'Contact Form', 'jetpack' ),
+ '_feedback_author' => '1_Name',
+ '_feedback_author_email' => '2_Email',
+ '_feedback_author_url' => '3_Website',
+ '_feedback_main_comment' => '4_Comment',
+ );
+
+ foreach ( $field_mapping as $parsed_field_name => $field_name ) {
+ if (
+ isset( $parsed_post_content[ $parsed_field_name ] )
+ && ! empty( $parsed_post_content[ $parsed_field_name ] )
+ ) {
+ $mapped_fields[ $field_name ] = $parsed_post_content[ $parsed_field_name ];
+ }
+ }
+
+ return $mapped_fields;
+ }
+
+ /**
+ * Registers the personal data exporter.
+ *
+ * @since 6.1.1
+ *
+ * @param array $exporters An array of personal data exporters.
+ *
+ * @return array $exporters An array of personal data exporters.
+ */
+ public function register_personal_data_exporter( $exporters ) {
+ $exporters['jetpack-feedback'] = array(
+ 'exporter_friendly_name' => __( 'Feedback', 'jetpack' ),
+ 'callback' => array( $this, 'personal_data_exporter' ),
+ );
+
+ return $exporters;
+ }
+
+ /**
+ * Registers the personal data eraser.
+ *
+ * @since 6.1.1
+ *
+ * @param array $erasers An array of personal data erasers.
+ *
+ * @return array $erasers An array of personal data erasers.
+ */
+ public function register_personal_data_eraser( $erasers ) {
+ $erasers['jetpack-feedback'] = array(
+ 'eraser_friendly_name' => __( 'Feedback', 'jetpack' ),
+ 'callback' => array( $this, 'personal_data_eraser' ),
+ );
+
+ return $erasers;
+ }
+
+ /**
+ * Exports personal data.
+ *
+ * @since 6.1.1
+ *
+ * @param string $email Email address.
+ * @param int $page Page to export.
+ *
+ * @return array $return Associative array with keys expected by core.
+ */
+ public function personal_data_exporter( $email, $page = 1 ) {
+ return $this->_internal_personal_data_exporter( $email, $page );
+ }
+
+ /**
+ * Internal method for exporting personal data.
+ *
+ * Allows us to have a different signature than core expects
+ * while protecting against future core API changes.
+ *
+ * @internal
+ * @since 6.5
+ *
+ * @param string $email Email address.
+ * @param int $page Page to export.
+ * @param int $per_page Number of feedbacks to process per page. Internal use only (testing)
+ *
+ * @return array Associative array with keys expected by core.
+ */
+ public function _internal_personal_data_exporter( $email, $page = 1, $per_page = 250 ) {
+ $export_data = array();
+ $post_ids = $this->personal_data_post_ids_by_email( $email, $per_page, $page );
+
+ foreach ( $post_ids as $post_id ) {
+ $post_fields = $this->get_parsed_field_contents_of_post( $post_id );
+
+ if ( ! is_array( $post_fields ) || empty( $post_fields['_feedback_subject'] ) ) {
+ continue; // Corrupt data.
+ }
+
+ $post_fields['_feedback_main_comment'] = $this->get_post_content_for_csv_export( $post_id );
+ $post_fields = $this->map_parsed_field_contents_of_post_to_field_names( $post_fields );
+
+ if ( ! is_array( $post_fields ) || empty( $post_fields ) ) {
+ continue; // No fields to export.
+ }
+
+ $post_meta = $this->get_post_meta_for_csv_export( $post_id );
+ $post_meta = is_array( $post_meta ) ? $post_meta : array();
+
+ $post_export_data = array();
+ $post_data = array_merge( $post_fields, $post_meta );
+ ksort( $post_data );
+
+ foreach ( $post_data as $post_data_key => $post_data_value ) {
+ $post_export_data[] = array(
+ 'name' => preg_replace( '/^[0-9]+_/', '', $post_data_key ),
+ 'value' => $post_data_value,
+ );
+ }
+
+ $export_data[] = array(
+ 'group_id' => 'feedback',
+ 'group_label' => __( 'Feedback', 'jetpack' ),
+ 'item_id' => 'feedback-' . $post_id,
+ 'data' => $post_export_data,
+ );
+ }
+
+ return array(
+ 'data' => $export_data,
+ 'done' => count( $post_ids ) < $per_page,
+ );
+ }
+
+ /**
+ * Erases personal data.
+ *
+ * @since 6.1.1
+ *
+ * @param string $email Email address.
+ * @param int $page Page to erase.
+ *
+ * @return array Associative array with keys expected by core.
+ */
+ public function personal_data_eraser( $email, $page = 1 ) {
+ return $this->_internal_personal_data_eraser( $email, $page );
+ }
+
+ /**
+ * Internal method for erasing personal data.
+ *
+ * Allows us to have a different signature than core expects
+ * while protecting against future core API changes.
+ *
+ * @internal
+ * @since 6.5
+ *
+ * @param string $email Email address.
+ * @param int $page Page to erase.
+ * @param int $per_page Number of feedbacks to process per page. Internal use only (testing)
+ *
+ * @return array Associative array with keys expected by core.
+ */
+ public function _internal_personal_data_eraser( $email, $page = 1, $per_page = 250 ) {
+ $removed = false;
+ $retained = false;
+ $messages = array();
+ $option_name = sprintf( '_jetpack_pde_feedback_%s', md5( $email ) );
+ $last_post_id = 1 === $page ? 0 : get_option( $option_name, 0 );
+ $post_ids = $this->personal_data_post_ids_by_email( $email, $per_page, $page, $last_post_id );
+
+ foreach ( $post_ids as $post_id ) {
+ /**
+ * Filters whether to erase a particular Feedback post.
+ *
+ * @since 6.3.0
+ *
+ * @param bool|string $prevention_message Whether to apply erase the Feedback post (bool).
+ * Custom prevention message (string). Default true.
+ * @param int $post_id Feedback post ID.
+ */
+ $prevention_message = apply_filters( 'grunion_contact_form_delete_feedback_post', true, $post_id );
+
+ if ( true !== $prevention_message ) {
+ if ( $prevention_message && is_string( $prevention_message ) ) {
+ $messages[] = esc_html( $prevention_message );
+ } else {
+ $messages[] = sprintf(
+ // translators: %d: Post ID.
+ __( 'Feedback ID %d could not be removed at this time.', 'jetpack' ),
+ $post_id
+ );
+ }
+
+ $retained = true;
+
+ continue;
+ }
+
+ if ( wp_delete_post( $post_id, true ) ) {
+ $removed = true;
+ } else {
+ $retained = true;
+ $messages[] = sprintf(
+ // translators: %d: Post ID.
+ __( 'Feedback ID %d could not be removed at this time.', 'jetpack' ),
+ $post_id
+ );
+ }
+ }
+
+ $done = count( $post_ids ) < $per_page;
+
+ if ( $done ) {
+ delete_option( $option_name );
+ } else {
+ update_option( $option_name, (int) $post_id );
+ }
+
+ return array(
+ 'items_removed' => $removed,
+ 'items_retained' => $retained,
+ 'messages' => $messages,
+ 'done' => $done,
+ );
+ }
+
+ /**
+ * Queries personal data by email address.
+ *
+ * @since 6.1.1
+ *
+ * @param string $email Email address.
+ * @param int $per_page Post IDs per page. Default is `250`.
+ * @param int $page Page to query. Default is `1`.
+ * @param int $last_post_id Page to query. Default is `0`. If non-zero, used instead of $page.
+ *
+ * @return array An array of post IDs.
+ */
+ public function personal_data_post_ids_by_email( $email, $per_page = 250, $page = 1, $last_post_id = 0 ) {
+ add_filter( 'posts_search', array( $this, 'personal_data_search_filter' ) );
+
+ $this->pde_last_post_id_erased = $last_post_id;
+ $this->pde_email_address = $email;
+
+ $post_ids = get_posts(
+ array(
+ 'post_type' => 'feedback',
+ 'post_status' => 'publish',
+ // This search parameter gets overwritten in ->personal_data_search_filter()
+ 's' => '..PDE..AUTHOR EMAIL:..PDE..',
+ 'sentence' => true,
+ 'order' => 'ASC',
+ 'orderby' => 'ID',
+ 'fields' => 'ids',
+ 'posts_per_page' => $per_page,
+ 'paged' => $last_post_id ? 1 : $page,
+ 'suppress_filters' => false,
+ )
+ );
+
+ $this->pde_last_post_id_erased = 0;
+ $this->pde_email_address = '';
+
+ remove_filter( 'posts_search', array( $this, 'personal_data_search_filter' ) );
+
+ return $post_ids;
+ }
+
+ /**
+ * Filters searches by email address.
+ *
+ * @since 6.1.1
+ *
+ * @param string $search SQL where clause.
+ *
+ * @return array Filtered SQL where clause.
+ */
+ public function personal_data_search_filter( $search ) {
+ global $wpdb;
+
+ /*
+ * Limits search to `post_content` only, and we only match the
+ * author's email address whenever it's on a line by itself.
+ */
+ if ( $this->pde_email_address && false !== strpos( $search, '..PDE..AUTHOR EMAIL:..PDE..' ) ) {
+ $search = $wpdb->prepare(
+ " AND (
+ {$wpdb->posts}.post_content LIKE %s
+ OR {$wpdb->posts}.post_content LIKE %s
+ )",
+ // `chr( 10 )` = `\n`, `chr( 13 )` = `\r`
+ '%' . $wpdb->esc_like( chr( 10 ) . 'AUTHOR EMAIL: ' . $this->pde_email_address . chr( 10 ) ) . '%',
+ '%' . $wpdb->esc_like( chr( 13 ) . 'AUTHOR EMAIL: ' . $this->pde_email_address . chr( 13 ) ) . '%'
+ );
+
+ if ( $this->pde_last_post_id_erased ) {
+ $search .= $wpdb->prepare( " AND {$wpdb->posts}.ID > %d", $this->pde_last_post_id_erased );
+ }
+ }
+
+ return $search;
+ }
+
+ /**
+ * Prepares feedback post data for CSV export.
+ *
+ * @param array $post_ids Post IDs to fetch the data for. These need to be Feedback posts.
+ *
+ * @return array
+ */
+ public function get_export_data_for_posts( $post_ids ) {
+
+ $posts_data = array();
+ $field_names = array();
+ $result = array();
+
+ /**
+ * Fetch posts and get the possible field names for later use
+ */
+ foreach ( $post_ids as $post_id ) {
+
+ /**
+ * Fetch post main data, because we need the subject and author data for the feedback form.
+ */
+ $post_real_data = $this->get_parsed_field_contents_of_post( $post_id );
+
+ /**
+ * If `$post_real_data` is not an array or there is no `_feedback_subject` set,
+ * then something must be wrong with the feedback post. Skip it.
+ */
+ if ( ! is_array( $post_real_data ) || ! isset( $post_real_data['_feedback_subject'] ) ) {
+ continue;
+ }
+
+ /**
+ * Fetch main post comment. This is from the default textarea fields.
+ * If it is non-empty, then we add it to data, otherwise skip it.
+ */
+ $post_comment_content = $this->get_post_content_for_csv_export( $post_id );
+ if ( ! empty( $post_comment_content ) ) {
+ $post_real_data['_feedback_main_comment'] = $post_comment_content;
+ }
+
+ /**
+ * Map parsed fields to proper field names
+ */
+ $mapped_fields = $this->map_parsed_field_contents_of_post_to_field_names( $post_real_data );
+
+ /**
+ * Fetch post meta data.
+ */
+ $post_meta_data = $this->get_post_meta_for_csv_export( $post_id );
+
+ /**
+ * If `$post_meta_data` is not an array or if it is empty, then there is no
+ * extra feedback to work with. Create an empty array.
+ */
+ if ( ! is_array( $post_meta_data ) || empty( $post_meta_data ) ) {
+ $post_meta_data = array();
+ }
+
+ /**
+ * Prepend the feedback subject to the list of fields.
+ */
+ $post_meta_data = array_merge(
+ $mapped_fields,
+ $post_meta_data
+ );
+
+ /**
+ * Save post metadata for later usage.
+ */
+ $posts_data[ $post_id ] = $post_meta_data;
+
+ /**
+ * Save field names, so we can use them as header fields later in the CSV.
+ */
+ $field_names = array_merge( $field_names, array_keys( $post_meta_data ) );
+ }
+
+ /**
+ * Make sure the field names are unique, because we don't want duplicate data.
+ */
+ $field_names = array_unique( $field_names );
+
+ /**
+ * Sort the field names by the field id number
+ */
+ sort( $field_names, SORT_NUMERIC );
+
+ /**
+ * Loop through every post, which is essentially CSV row.
+ */
+ foreach ( $posts_data as $post_id => $single_post_data ) {
+
+ /**
+ * Go through all the possible fields and check if the field is available
+ * in the current post.
+ *
+ * If it is - add the data as a value.
+ * If it is not - add an empty string, which is just a placeholder in the CSV.
+ */
+ foreach ( $field_names as $single_field_name ) {
+ if (
+ isset( $single_post_data[ $single_field_name ] )
+ && ! empty( $single_post_data[ $single_field_name ] )
+ ) {
+ $result[ $single_field_name ][] = trim( $single_post_data[ $single_field_name ] );
+ } else {
+ $result[ $single_field_name ][] = '';
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * download as a csv a contact form or all of them in a csv file
+ */
+ function download_feedback_as_csv() {
+ if ( empty( $_POST['feedback_export_nonce'] ) ) {
+ return;
+ }
+
+ check_admin_referer( 'feedback_export', 'feedback_export_nonce' );
+
+ if ( ! current_user_can( 'export' ) ) {
+ return;
+ }
+
+ $args = array(
+ 'posts_per_page' => -1,
+ 'post_type' => 'feedback',
+ 'post_status' => 'publish',
+ 'order' => 'ASC',
+ 'fields' => 'ids',
+ 'suppress_filters' => false,
+ );
+
+ $filename = date( 'Y-m-d' ) . '-feedback-export.csv';
+
+ // Check if we want to download all the feedbacks or just a certain contact form
+ if ( ! empty( $_POST['post'] ) && $_POST['post'] !== 'all' ) {
+ $args['post_parent'] = (int) $_POST['post'];
+ $filename = date( 'Y-m-d' ) . '-' . str_replace( '&nbsp;', '-', get_the_title( (int) $_POST['post'] ) ) . '.csv';
+ }
+
+ $feedbacks = get_posts( $args );
+
+ if ( empty( $feedbacks ) ) {
+ return;
+ }
+
+ $filename = sanitize_file_name( $filename );
+
+ /**
+ * Prepare data for export.
+ */
+ $data = $this->get_export_data_for_posts( $feedbacks );
+
+ /**
+ * If `$data` is empty, there's nothing we can do below.
+ */
+ if ( ! is_array( $data ) || empty( $data ) ) {
+ return;
+ }
+
+ /**
+ * Extract field names from `$data` for later use.
+ */
+ $fields = array_keys( $data );
+
+ /**
+ * Count how many rows will be exported.
+ */
+ $row_count = count( reset( $data ) );
+
+ // Forces the download of the CSV instead of echoing
+ header( 'Content-Disposition: attachment; filename=' . $filename );
+ header( 'Pragma: no-cache' );
+ header( 'Expires: 0' );
+ header( 'Content-Type: text/csv; charset=utf-8' );
+
+ $output = fopen( 'php://output', 'w' );
+
+ /**
+ * Print CSV headers
+ */
+ fputcsv( $output, $fields );
+
+ /**
+ * Print rows to the output.
+ */
+ for ( $i = 0; $i < $row_count; $i ++ ) {
+
+ $current_row = array();
+
+ /**
+ * Put all the fields in `$current_row` array.
+ */
+ foreach ( $fields as $single_field_name ) {
+ $current_row[] = $this->esc_csv( $data[ $single_field_name ][ $i ] );
+ }
+
+ /**
+ * Output the complete CSV row
+ */
+ fputcsv( $output, $current_row );
+ }
+
+ fclose( $output );
+ }
+
+ /**
+ * Escape a string to be used in a CSV context
+ *
+ * Malicious input can inject formulas into CSV files, opening up the possibility for phishing attacks and
+ * disclosure of sensitive information.
+ *
+ * Additionally, Excel exposes the ability to launch arbitrary commands through the DDE protocol.
+ *
+ * @see http://www.contextis.com/resources/blog/comma-separated-vulnerabilities/
+ *
+ * @param string $field
+ *
+ * @return string
+ */
+ public function esc_csv( $field ) {
+ $active_content_triggers = array( '=', '+', '-', '@' );
+
+ if ( in_array( mb_substr( $field, 0, 1 ), $active_content_triggers, true ) ) {
+ $field = "'" . $field;
+ }
+
+ return $field;
+ }
+
+ /**
+ * Returns a string of HTML <option> items from an array of posts
+ *
+ * @return string a string of HTML <option> items
+ */
+ protected function get_feedbacks_as_options() {
+ $options = '';
+
+ // Get the feedbacks' parents' post IDs
+ $feedbacks = get_posts(
+ array(
+ 'fields' => 'id=>parent',
+ 'posts_per_page' => 100000,
+ 'post_type' => 'feedback',
+ 'post_status' => 'publish',
+ 'suppress_filters' => false,
+ )
+ );
+ $parents = array_unique( array_values( $feedbacks ) );
+
+ $posts = get_posts(
+ array(
+ 'orderby' => 'ID',
+ 'posts_per_page' => 1000,
+ 'post_type' => 'any',
+ 'post__in' => array_values( $parents ),
+ 'suppress_filters' => false,
+ )
+ );
+
+ // creates the string of <option> elements
+ foreach ( $posts as $post ) {
+ $options .= sprintf( '<option value="%s">%s</option>', esc_attr( $post->ID ), esc_html( $post->post_title ) );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Get the names of all the form's fields
+ *
+ * @param array|int $posts the post we want the fields of
+ *
+ * @return array the array of fields
+ *
+ * @deprecated As this is no longer necessary as of the CSV export rewrite. - 2015-12-29
+ */
+ protected function get_field_names( $posts ) {
+ $posts = (array) $posts;
+ $all_fields = array();
+
+ foreach ( $posts as $post ) {
+ $fields = self::parse_fields_from_content( $post );
+
+ if ( isset( $fields['_feedback_all_fields'] ) ) {
+ $extra_fields = array_keys( $fields['_feedback_all_fields'] );
+ $all_fields = array_merge( $all_fields, $extra_fields );
+ }
+ }
+
+ $all_fields = array_unique( $all_fields );
+ return $all_fields;
+ }
+
+ public static function parse_fields_from_content( $post_id ) {
+ static $post_fields;
+
+ if ( ! is_array( $post_fields ) ) {
+ $post_fields = array();
+ }
+
+ if ( isset( $post_fields[ $post_id ] ) ) {
+ return $post_fields[ $post_id ];
+ }
+
+ $all_values = array();
+ $post_content = get_post_field( 'post_content', $post_id );
+ $content = explode( '<!--more-->', $post_content );
+ $lines = array();
+
+ if ( count( $content ) > 1 ) {
+ $content = str_ireplace( array( '<br />', ')</p>' ), '', $content[1] );
+ $one_line = preg_replace( '/\s+/', ' ', $content );
+ $one_line = preg_replace( '/.*Array \( (.*)\)/', '$1', $one_line );
+
+ preg_match_all( '/\[([^\]]+)\] =\&gt\; ([^\[]+)/', $one_line, $matches );
+
+ if ( count( $matches ) > 1 ) {
+ $all_values = array_combine( array_map( 'trim', $matches[1] ), array_map( 'trim', $matches[2] ) );
+ }
+
+ $lines = array_filter( explode( "\n", $content ) );
+ }
+
+ $var_map = array(
+ 'AUTHOR' => '_feedback_author',
+ 'AUTHOR EMAIL' => '_feedback_author_email',
+ 'AUTHOR URL' => '_feedback_author_url',
+ 'SUBJECT' => '_feedback_subject',
+ 'IP' => '_feedback_ip',
+ );
+
+ $fields = array();
+
+ foreach ( $lines as $line ) {
+ $vars = explode( ': ', $line, 2 );
+ if ( ! empty( $vars ) ) {
+ if ( isset( $var_map[ $vars[0] ] ) ) {
+ $fields[ $var_map[ $vars[0] ] ] = self::strip_tags( trim( $vars[1] ) );
+ }
+ }
+ }
+
+ $fields['_feedback_all_fields'] = $all_values;
+
+ $post_fields[ $post_id ] = $fields;
+
+ return $fields;
+ }
+
+ /**
+ * Creates a valid csv row from a post id
+ *
+ * @param int $post_id The id of the post
+ * @param array $fields An array containing the names of all the fields of the csv
+ * @return String The csv row
+ *
+ * @deprecated This is no longer needed, as of the CSV export rewrite.
+ */
+ protected static function make_csv_row_from_feedback( $post_id, $fields ) {
+ $content_fields = self::parse_fields_from_content( $post_id );
+ $all_fields = array();
+
+ if ( isset( $content_fields['_feedback_all_fields'] ) ) {
+ $all_fields = $content_fields['_feedback_all_fields'];
+ }
+
+ // Overwrite the parsed content with the content we stored in post_meta in a better format.
+ $extra_fields = get_post_meta( $post_id, '_feedback_extra_fields', true );
+ foreach ( $extra_fields as $extra_field => $extra_value ) {
+ $all_fields[ $extra_field ] = $extra_value;
+ }
+
+ // The first element in all of the exports will be the subject
+ $row_items[] = $content_fields['_feedback_subject'];
+
+ // Loop the fields array in order to fill the $row_items array correctly
+ foreach ( $fields as $field ) {
+ if ( $field === __( 'Contact Form', 'jetpack' ) ) { // the first field will ever be the contact form, so we can continue
+ continue;
+ } elseif ( array_key_exists( $field, $all_fields ) ) {
+ $row_items[] = $all_fields[ $field ];
+ } else {
+ $row_items[] = '';
+ }
+ }
+
+ return $row_items;
+ }
+
+ public static function get_ip_address() {
+ return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null;
+ }
+}
+
+/**
+ * Generic shortcode class.
+ * Does nothing other than store structured data and output the shortcode as a string
+ *
+ * Not very general - specific to Grunion.
+ */
+class Crunion_Contact_Form_Shortcode {
+ /**
+ * @var string the name of the shortcode: [$shortcode_name /]
+ */
+ public $shortcode_name;
+
+ /**
+ * @var array key => value pairs for the shortcode's attributes: [$shortcode_name key="value" ... /]
+ */
+ public $attributes;
+
+ /**
+ * @var array key => value pair for attribute defaults
+ */
+ public $defaults = array();
+
+ /**
+ * @var null|string Null for selfclosing shortcodes. Hhe inner content of otherwise: [$shortcode_name]$content[/$shortcode_name]
+ */
+ public $content;
+
+ /**
+ * @var array Associative array of inner "child" shortcodes equivalent to the $content: [$shortcode_name][child 1/][child 2/][/$shortcode_name]
+ */
+ public $fields;
+
+ /**
+ * @var null|string The HTML of the parsed inner "child" shortcodes". Null for selfclosing shortcodes.
+ */
+ public $body;
+
+ /**
+ * @param array $attributes An associative array of shortcode attributes. @see shortcode_atts()
+ * @param null|string $content Null for selfclosing shortcodes. The inner content otherwise.
+ */
+ function __construct( $attributes, $content = null ) {
+ $this->attributes = $this->unesc_attr( $attributes );
+ if ( is_array( $content ) ) {
+ $string_content = '';
+ foreach ( $content as $field ) {
+ $string_content .= (string) $field;
+ }
+
+ $this->content = $string_content;
+ } else {
+ $this->content = $content;
+ }
+
+ $this->parse_content( $this->content );
+ }
+
+ /**
+ * Processes the shortcode's inner content for "child" shortcodes
+ *
+ * @param string $content The shortcode's inner content: [shortcode]$content[/shortcode]
+ */
+ function parse_content( $content ) {
+ if ( is_null( $content ) ) {
+ $this->body = null;
+ }
+
+ $this->body = do_shortcode( $content );
+ }
+
+ /**
+ * Returns the value of the requested attribute.
+ *
+ * @param string $key The attribute to retrieve
+ * @return mixed
+ */
+ function get_attribute( $key ) {
+ return isset( $this->attributes[ $key ] ) ? $this->attributes[ $key ] : null;
+ }
+
+ function esc_attr( $value ) {
+ if ( is_array( $value ) ) {
+ return array_map( array( $this, 'esc_attr' ), $value );
+ }
+
+ $value = Grunion_Contact_Form_Plugin::strip_tags( $value );
+ $value = _wp_specialchars( $value, ENT_QUOTES, false, true );
+
+ // Shortcode attributes can't contain "]"
+ $value = str_replace( ']', '', $value );
+ $value = str_replace( ',', '&#x002c;', $value ); // store commas encoded
+ $value = strtr(
+ $value, array(
+ '%' => '%25',
+ '&' => '%26',
+ )
+ );
+
+ // shortcode_parse_atts() does stripcslashes()
+ $value = addslashes( $value );
+ return $value;
+ }
+
+ function unesc_attr( $value ) {
+ if ( is_array( $value ) ) {
+ return array_map( array( $this, 'unesc_attr' ), $value );
+ }
+
+ // For back-compat with old Grunion encoding
+ // Also, unencode commas
+ $value = strtr(
+ $value, array(
+ '%26' => '&',
+ '%25' => '%',
+ )
+ );
+ $value = preg_replace( array( '/&#x0*22;/i', '/&#x0*27;/i', '/&#x0*26;/i', '/&#x0*2c;/i' ), array( '"', "'", '&', ',' ), $value );
+ $value = htmlspecialchars_decode( $value, ENT_QUOTES );
+ $value = Grunion_Contact_Form_Plugin::strip_tags( $value );
+
+ return $value;
+ }
+
+ /**
+ * Generates the shortcode
+ */
+ function __toString() {
+ $r = "[{$this->shortcode_name} ";
+
+ foreach ( $this->attributes as $key => $value ) {
+ if ( ! $value ) {
+ continue;
+ }
+
+ if ( isset( $this->defaults[ $key ] ) && $this->defaults[ $key ] == $value ) {
+ continue;
+ }
+
+ if ( 'id' == $key ) {
+ continue;
+ }
+
+ $value = $this->esc_attr( $value );
+
+ if ( is_array( $value ) ) {
+ $value = join( ',', $value );
+ }
+
+ if ( false === strpos( $value, "'" ) ) {
+ $value = "'$value'";
+ } elseif ( false === strpos( $value, '"' ) ) {
+ $value = '"' . $value . '"';
+ } else {
+ // Shortcodes can't contain both '"' and "'". Strip one.
+ $value = str_replace( "'", '', $value );
+ $value = "'$value'";
+ }
+
+ $r .= "{$key}={$value} ";
+ }
+
+ $r = rtrim( $r );
+
+ if ( $this->fields ) {
+ $r .= ']';
+
+ foreach ( $this->fields as $field ) {
+ $r .= (string) $field;
+ }
+
+ $r .= "[/{$this->shortcode_name}]";
+ } else {
+ $r .= '/]';
+ }
+
+ return $r;
+ }
+}
+
+/**
+ * Class for the contact-form shortcode.
+ * Parses shortcode to output the contact form as HTML
+ * Sends email and stores the contact form response (a.k.a. "feedback")
+ */
+class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode {
+ public $shortcode_name = 'contact-form';
+
+ /**
+ * @var WP_Error stores form submission errors
+ */
+ public $errors;
+
+ /**
+ * @var string The SHA1 hash of the attributes that comprise the form.
+ */
+ public $hash;
+
+ /**
+ * @var Grunion_Contact_Form The most recent (inclusive) contact-form shortcode processed
+ */
+ static $last;
+
+ /**
+ * @var Whatever form we are currently looking at. If processed, will become $last
+ */
+ static $current_form;
+
+ /**
+ * @var array All found forms, indexed by hash.
+ */
+ static $forms = array();
+
+ /**
+ * @var bool Whether to print the grunion.css style when processing the contact-form shortcode
+ */
+ static $style = false;
+
+ /**
+ * @var array When printing the submit button, what tags are allowed
+ */
+ static $allowed_html_tags_for_submit_button = array( 'br' => array() );
+
+ function __construct( $attributes, $content = null ) {
+ global $post;
+
+ $this->hash = sha1( json_encode( $attributes ) . $content );
+ self::$forms[ $this->hash ] = $this;
+
+ // Set up the default subject and recipient for this form
+ $default_to = '';
+ $default_subject = '[' . get_option( 'blogname' ) . ']';
+
+ if ( ! isset( $attributes ) || ! is_array( $attributes ) ) {
+ $attributes = array();
+ }
+
+ if ( ! empty( $attributes['widget'] ) && $attributes['widget'] ) {
+ $default_to .= get_option( 'admin_email' );
+ $attributes['id'] = 'widget-' . $attributes['widget'];
+ $default_subject = sprintf( _x( '%1$s Sidebar', '%1$s = blog name', 'jetpack' ), $default_subject );
+ } elseif ( $post ) {
+ $attributes['id'] = $post->ID;
+ $default_subject = sprintf( _x( '%1$s %2$s', '%1$s = blog name, %2$s = post title', 'jetpack' ), $default_subject, Grunion_Contact_Form_Plugin::strip_tags( $post->post_title ) );
+ $post_author = get_userdata( $post->post_author );
+ $default_to .= $post_author->user_email;
+ }
+
+ // Keep reference to $this for parsing form fields
+ self::$current_form = $this;
+
+ $this->defaults = array(
+ 'to' => $default_to,
+ 'subject' => $default_subject,
+ 'show_subject' => 'no', // only used in back-compat mode
+ 'widget' => 0, // Not exposed to the user. Works with Grunion_Contact_Form_Plugin::widget_atts()
+ 'id' => null, // Not exposed to the user. Set above.
+ 'submit_button_text' => __( 'Submit', 'jetpack' ),
+ );
+
+ $attributes = shortcode_atts( $this->defaults, $attributes, 'contact-form' );
+
+ // 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
+ $default_form = '
+ [contact-field label="' . __( 'Name', 'jetpack' ) . '" type="name" required="true" /]
+ [contact-field label="' . __( 'Email', 'jetpack' ) . '" type="email" required="true" /]
+ [contact-field label="' . __( 'Website', 'jetpack' ) . '" type="url" /]';
+
+ if ( 'yes' == strtolower( $this->get_attribute( 'show_subject' ) ) ) {
+ $default_form .= '
+ [contact-field label="' . __( 'Subject', 'jetpack' ) . '" type="subject" /]';
+ }
+
+ $default_form .= '
+ [contact-field label="' . __( 'Message', 'jetpack' ) . '" type="textarea" /]';
+
+ $this->parse_content( $default_form );
+
+ // Store the shortcode
+ $this->store_shortcode( $default_form, $attributes, $this->hash );
+ } else {
+ // Store the shortcode
+ $this->store_shortcode( $content, $attributes, $this->hash );
+ }
+
+ // $this->body and $this->fields have been setup. We no longer need the contact-field shortcode.
+ Grunion_Contact_Form_Plugin::$using_contact_form_field = false;
+ }
+
+ /**
+ * Store shortcode content for recall later
+ * - used to receate shortcode when user uses do_shortcode
+ *
+ * @param string $content
+ * @param array $attributes
+ * @param string $hash
+ */
+ static function store_shortcode( $content = null, $attributes = null, $hash = null ) {
+
+ if ( $content != null and isset( $attributes['id'] ) ) {
+
+ if ( empty( $hash ) ) {
+ $hash = sha1( json_encode( $attributes ) . $content );
+ }
+
+ $shortcode_meta = get_post_meta( $attributes['id'], "_g_feedback_shortcode_{$hash}", true );
+
+ if ( $shortcode_meta != '' or $shortcode_meta != $content ) {
+ update_post_meta( $attributes['id'], "_g_feedback_shortcode_{$hash}", $content );
+
+ // Save attributes to post_meta for later use. They're not available later in do_shortcode situations.
+ update_post_meta( $attributes['id'], "_g_feedback_shortcode_atts_{$hash}", $attributes );
+ }
+ }
+ }
+
+ /**
+ * Toggle for printing the grunion.css stylesheet
+ *
+ * @param bool $style
+ */
+ static function style( $style ) {
+ $previous_style = self::$style;
+ self::$style = (bool) $style;
+ return $previous_style;
+ }
+
+ /**
+ * Turn on printing of grunion.css stylesheet
+ *
+ * @see ::style()
+ * @internal
+ * @param bool $style
+ */
+ static function _style_on() {
+ return self::style( true );
+ }
+
+ /**
+ * The contact-form shortcode processor
+ *
+ * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts()
+ * @param string|null $content The shortcode's inner content: [contact-form]$content[/contact-form]
+ * @return string HTML for the concat form.
+ */
+ static function parse( $attributes, $content ) {
+ require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
+ if ( Jetpack_Sync_Settings::is_syncing() ) {
+ return '';
+ }
+ // Create a new Grunion_Contact_Form object (this class)
+ $form = new Grunion_Contact_Form( $attributes, $content );
+
+ $id = $form->get_attribute( 'id' );
+
+ if ( ! $id ) { // something terrible has happened
+ return '[contact-form]';
+ }
+
+ if ( is_feed() ) {
+ return '[contact-form]';
+ }
+
+ self::$last = $form;
+
+ // Enqueue the grunion.css stylesheet if self::$style allows it
+ if ( self::$style && ( empty( $_REQUEST['action'] ) || $_REQUEST['action'] != 'grunion_shortcode_to_json' ) ) {
+ // Enqueue the style here instead of printing it, because if some other plugin has run the_post()+rewind_posts(),
+ // (like VideoPress does), the style tag gets "printed" the first time and discarded, leaving the contact form unstyled.
+ // when WordPress does the real loop.
+ wp_enqueue_style( 'grunion.css' );
+ }
+
+ $r = '';
+ $r .= "<div id='contact-form-$id'>\n";
+
+ if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) {
+ // There are errors. Display them
+ $r .= "<div class='form-error'>\n<h3>" . __( 'Error!', 'jetpack' ) . "</h3>\n<ul class='form-errors'>\n";
+ foreach ( $form->errors->get_error_messages() as $message ) {
+ $r .= "\t<li class='form-error-message'>" . esc_html( $message ) . "</li>\n";
+ }
+ $r .= "</ul>\n</div>\n\n";
+ }
+
+ 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
+ $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' ) .
+ ' (<a href="' . esc_url( $back_url ) . '">' . esc_html__( 'go back', 'jetpack' ) . '</a>)' .
+ "</h3>\n\n";
+
+ // Don't show the feedback details unless the nonce matches
+ if ( $feedback_id && wp_verify_nonce( stripslashes( $_GET['_wpnonce'] ), "contact-form-sent-{$feedback_id}" ) ) {
+ $r_success_message .= self::success_message( $feedback_id, $form );
+ }
+
+ /**
+ * Filter the message returned after a successful contact form submission.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param string $r_success_message Success message.
+ */
+ $r .= apply_filters( 'grunion_contact_form_success_message', $r_success_message );
+ } else {
+ // Nothing special - show the normal contact form
+ if ( $form->get_attribute( 'widget' ) ) {
+ // Submit form to the current URL
+ $url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', 'action', '_wpnonce' ) );
+ } else {
+ // Submit form to the post permalink
+ $url = get_permalink();
+ }
+
+ // For SSL/TLS page. See RFC 3986 Section 4.2
+ $url = set_url_scheme( $url );
+
+ // May eventually want to send this to admin-post.php...
+ /**
+ * Filter the contact form action URL.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param string $contact_form_id Contact form post URL.
+ * @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 );
+
+ $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'];
+ }
+
+ /**
+ * 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 .= ">";
+ $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
+ }
+ $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";
+ $r .= "</form>\n";
+ }
+
+ $r .= '</div>';
+
+ return $r;
+ }
+
+ /**
+ * Returns a success message to be returned if the form is sent via AJAX.
+ *
+ * @param int $feedback_id
+ * @param object Grunion_Contact_Form $form
+ *
+ * @return string $message
+ */
+ static function success_message( $feedback_id, $form ) {
+ return wp_kses(
+ '<blockquote class="contact-form-submission">'
+ . '<p>' . join( self::get_compiled_form( $feedback_id, $form ), '</p><p>' ) . '</p>'
+ . '</blockquote>',
+ array(
+ 'br' => array(),
+ 'blockquote' => array( 'class' => array() ),
+ 'p' => array(),
+ )
+ );
+ }
+
+ /**
+ * Returns a compiled form with labels and values in a form of an array
+ * of lines.
+ *
+ * @param int $feedback_id
+ * @param object Grunion_Contact_Form $form
+ *
+ * @return array $lines
+ */
+ static function get_compiled_form( $feedback_id, $form ) {
+ $feedback = get_post( $feedback_id );
+ $field_ids = $form->get_field_ids();
+ $content_fields = Grunion_Contact_Form_Plugin::parse_fields_from_content( $feedback_id );
+
+ // Maps field_ids to post_meta keys
+ $field_value_map = array(
+ 'name' => 'author',
+ 'email' => 'author_email',
+ 'url' => 'author_url',
+ 'subject' => 'subject',
+ 'textarea' => false, // not a post_meta key. This is stored in post_content
+ );
+
+ $compiled_form = array();
+
+ // "Standard" field whitelist
+ foreach ( $field_value_map as $type => $meta_key ) {
+ if ( isset( $field_ids[ $type ] ) ) {
+ $field = $form->fields[ $field_ids[ $type ] ];
+
+ if ( $meta_key ) {
+ if ( isset( $content_fields[ "_feedback_{$meta_key}" ] ) ) {
+ $value = $content_fields[ "_feedback_{$meta_key}" ];
+ }
+ } else {
+ // The feedback content is stored as the first "half" of post_content
+ $value = $feedback->post_content;
+ list( $value ) = explode( '<!--more-->', $value );
+ $value = trim( $value );
+ }
+
+ $field_index = array_search( $field_ids[ $type ], $field_ids['all'] );
+ $compiled_form[ $field_index ] = sprintf(
+ '<b>%1$s:</b> %2$s<br /><br />',
+ wp_kses( $field->get_attribute( 'label' ), array() ),
+ self::escape_and_sanitize_field_value( $value )
+ );
+ }
+ }
+
+ // "Non-standard" fields
+ if ( $field_ids['extra'] ) {
+ // array indexed by field label (not field id)
+ $extra_fields = get_post_meta( $feedback_id, '_feedback_extra_fields', true );
+
+ /**
+ * Only get data for the compiled form if `$extra_fields` is a valid and non-empty array.
+ */
+ if ( is_array( $extra_fields ) && ! empty( $extra_fields ) ) {
+
+ $extra_field_keys = array_keys( $extra_fields );
+
+ $i = 0;
+ foreach ( $field_ids['extra'] as $field_id ) {
+ $field = $form->fields[ $field_id ];
+ $field_index = array_search( $field_id, $field_ids['all'] );
+
+ $label = $field->get_attribute( 'label' );
+
+ $compiled_form[ $field_index ] = sprintf(
+ '<b>%1$s:</b> %2$s<br /><br />',
+ wp_kses( $label, array() ),
+ self::escape_and_sanitize_field_value( $extra_fields[ $extra_field_keys[ $i ] ] )
+ );
+
+ $i++;
+ }
+ }
+ }
+
+ // Sorting lines by the field index
+ ksort( $compiled_form );
+
+ return $compiled_form;
+ }
+
+ static function escape_and_sanitize_field_value( $value ) {
+ $value = str_replace( array( '[' , ']' ) , array( '&#91;' , '&#93;' ) , $value );
+ return nl2br( wp_kses( $value, array() ) );
+ }
+
+ /**
+ * Only strip out empty string values and keep all the other values as they are.
+ *
+ * @param $single_value
+ *
+ * @return bool
+ */
+ static function remove_empty( $single_value ) {
+ return ( $single_value !== '' );
+ }
+
+ /**
+ * 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.
+ *
+ * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts()
+ * @param string|null $content The shortcode's inner content: [contact-field]$content[/contact-field]
+ * @return HTML for the contact form field
+ */
+ static function parse_contact_field( $attributes, $content ) {
+ // Don't try to parse contact form fields if not inside a contact form
+ if ( ! Grunion_Contact_Form_Plugin::$using_contact_form_field ) {
+ $att_strs = array();
+ if ( ! isset( $attributes['label'] ) ) {
+ $type = isset( $attributes['type'] ) ? $attributes['type'] : null;
+ $attributes['label'] = self::get_default_label_from_type( $type );
+ }
+ foreach ( $attributes as $att => $val ) {
+ if ( is_numeric( $att ) ) { // Is a valueless attribute
+ $att_strs[] = esc_html( $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 ) ) {
+ $val = array_filter( $val, array( __CLASS__, 'remove_empty' ) ); // removes any empty strings
+ $att_strs[] = esc_html( $att ) . '="' . implode( ',', array_map( 'esc_html', $val ) ) . '"';
+ } elseif ( is_bool( $val ) ) {
+ $att_strs[] = esc_html( $att ) . '="' . esc_html( $val ? '1' : '' ) . '"';
+ } else {
+ $att_strs[] = esc_html( $att ) . '="' . esc_html( $val ) . '"';
+ }
+ }
+ }
+
+ $html = '[contact-field ' . implode( ' ', $att_strs );
+
+ if ( isset( $content ) && ! empty( $content ) ) { // If there is content, let's add a closing tag
+ $html .= ']' . esc_html( $content ) . '[/contact-field]';
+ } else { // Otherwise let's add a closing slash in the first tag
+ $html .= '/]';
+ }
+
+ return $html;
+ }
+
+ $form = Grunion_Contact_Form::$current_form;
+
+ $field = new Grunion_Contact_Form_Field( $attributes, $content, $form );
+
+ $field_id = $field->get_attribute( 'id' );
+ if ( $field_id ) {
+ $form->fields[ $field_id ] = $field;
+ } else {
+ $form->fields[] = $field;
+ }
+
+ if (
+ isset( $_POST['action'] ) && 'grunion-contact-form' === $_POST['action']
+ &&
+ isset( $_POST['contact-form-id'] ) && $form->get_attribute( 'id' ) == $_POST['contact-form-id']
+ &&
+ isset( $_POST['contact-form-hash'] ) && hash_equals( $form->hash, $_POST['contact-form-hash'] )
+ ) {
+ // If we're processing a POST submission for this contact form, validate the field value so we can show errors as necessary.
+ $field->validate();
+ }
+
+ // Output HTML
+ return $field->render();
+ }
+
+ static function get_default_label_from_type( $type ) {
+ switch ( $type ) {
+ case 'text':
+ return __( 'Text', 'jetpack' );
+ case 'name':
+ return __( 'Name', 'jetpack' );
+ case 'email':
+ return __( 'Email', 'jetpack' );
+ case 'url':
+ return __( 'Website', 'jetpack' );
+ case 'date':
+ return __( 'Date', 'jetpack' );
+ case 'telephone':
+ return __( 'Phone', 'jetpack' );
+ case 'textarea':
+ return __( 'Message', 'jetpack' );
+ case 'checkbox':
+ return __( 'Checkbox', 'jetpack' );
+ case 'checkbox-multiple':
+ return __( 'Choose several', 'jetpack' );
+ case 'radio':
+ return __( 'Choose one', 'jetpack' );
+ case 'select':
+ return __( 'Select one', 'jetpack' );
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Loops through $this->fields to generate a (structured) list of field IDs.
+ *
+ * Important: Currently the whitelisted 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.
+ *
+ * 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
+ * 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
+ * `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
+ * 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.
+ *
+ * - Grunion_Contact_Form::process_submission - validation and logic.
+ *
+ * - Grunion_Contact_Form::process_submission - add the field as an additional
+ * field in the `post_content` when saving the feedback content.
+ *
+ * - Grunion_Contact_Form_Plugin::parse_fields_from_content - add mapping
+ * for the field, defined in the above method.
+ *
+ * - Grunion_Contact_Form_Plugin::map_parsed_field_contents_of_post_to_field_names -
+ * add mapping of the field for the CSV Export. Otherwise it will be missing
+ * from the exported data.
+ *
+ * - admin.php / grunion_manage_post_columns - add the field to the render logic.
+ * Otherwise it will be missing from the admin Feedback view.
+ *
+ * @return array
+ */
+ function get_field_ids() {
+ $field_ids = array(
+ 'all' => array(), // array of all field_ids
+ 'extra' => array(), // array of all non-whitelisted field IDs
+
+ // Whitelisted "standard" field IDs:
+ // 'email' => field_id,
+ // 'name' => field_id,
+ // 'url' => field_id,
+ // 'subject' => field_id,
+ // 'textarea' => field_id,
+ );
+
+ foreach ( $this->fields as $id => $field ) {
+ $field_ids['all'][] = $id;
+
+ $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
+ // Put it in extra
+ $field_ids['extra'][] = $id;
+ continue;
+ }
+
+ /**
+ * See method description before modifying the switch cases.
+ */
+ switch ( $type ) {
+ case 'email':
+ case 'name':
+ case 'url':
+ case 'subject':
+ case 'textarea':
+ $field_ids[ $type ] = $id;
+ break;
+ default:
+ // Put everything else in extra
+ $field_ids['extra'][] = $id;
+ }
+ }
+
+ return $field_ids;
+ }
+
+ /**
+ * Process the contact form's POST submission
+ * Stores feedback. Sends email.
+ */
+ function process_submission() {
+ global $post;
+
+ $plugin = Grunion_Contact_Form_Plugin::init();
+
+ $id = $this->get_attribute( 'id' );
+ $to = $this->get_attribute( 'to' );
+ $widget = $this->get_attribute( 'widget' );
+
+ $contact_form_subject = $this->get_attribute( 'subject' );
+
+ $to = str_replace( ' ', '', $to );
+ $emails = explode( ',', $to );
+
+ $valid_emails = array();
+
+ foreach ( (array) $emails as $email ) {
+ if ( ! is_email( $email ) ) {
+ continue;
+ }
+
+ if ( function_exists( 'is_email_address_unsafe' ) && is_email_address_unsafe( $email ) ) {
+ continue;
+ }
+
+ $valid_emails[] = $email;
+ }
+
+ // No one to send it to, which means none of the "to" attributes are valid emails.
+ // Use default email instead.
+ if ( ! $valid_emails ) {
+ $valid_emails = $this->defaults['to'];
+ }
+
+ $to = $valid_emails;
+
+ // Last ditch effort to set a recipient if somehow none have been set.
+ if ( empty( $to ) ) {
+ $to = get_option( 'admin_email' );
+ }
+
+ // Make sure we're processing the form we think we're processing... probably a redundant check.
+ if ( $widget ) {
+ if ( 'widget-' . $widget != $_POST['contact-form-id'] ) {
+ return false;
+ }
+ } else {
+ if ( $post->ID != $_POST['contact-form-id'] ) {
+ return false;
+ }
+ }
+
+ $field_ids = $this->get_field_ids();
+
+ // Initialize all these "standard" fields to null
+ $comment_author_email = $comment_author_email_label = // v
+ $comment_author = $comment_author_label = // v
+ $comment_author_url = $comment_author_url_label = // v
+ $comment_content = $comment_content_label = null;
+
+ // For each of the "standard" fields, grab their field label and value.
+ if ( isset( $field_ids['name'] ) ) {
+ $field = $this->fields[ $field_ids['name'] ];
+ $comment_author = Grunion_Contact_Form_Plugin::strip_tags(
+ stripslashes(
+ /** This filter is already documented in core/wp-includes/comment-functions.php */
+ apply_filters( 'pre_comment_author_name', addslashes( $field->value ) )
+ )
+ );
+ $comment_author_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) );
+ }
+
+ if ( isset( $field_ids['email'] ) ) {
+ $field = $this->fields[ $field_ids['email'] ];
+ $comment_author_email = Grunion_Contact_Form_Plugin::strip_tags(
+ stripslashes(
+ /** This filter is already documented in core/wp-includes/comment-functions.php */
+ apply_filters( 'pre_comment_author_email', addslashes( $field->value ) )
+ )
+ );
+ $comment_author_email_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) );
+ }
+
+ if ( isset( $field_ids['url'] ) ) {
+ $field = $this->fields[ $field_ids['url'] ];
+ $comment_author_url = Grunion_Contact_Form_Plugin::strip_tags(
+ stripslashes(
+ /** This filter is already documented in core/wp-includes/comment-functions.php */
+ apply_filters( 'pre_comment_author_url', addslashes( $field->value ) )
+ )
+ );
+ if ( 'http://' == $comment_author_url ) {
+ $comment_author_url = '';
+ }
+ $comment_author_url_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) );
+ }
+
+ if ( isset( $field_ids['textarea'] ) ) {
+ $field = $this->fields[ $field_ids['textarea'] ];
+ $comment_content = trim( Grunion_Contact_Form_Plugin::strip_tags( $field->value ) );
+ $comment_content_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) );
+ }
+
+ if ( isset( $field_ids['subject'] ) ) {
+ $field = $this->fields[ $field_ids['subject'] ];
+ if ( $field->value ) {
+ $contact_form_subject = Grunion_Contact_Form_Plugin::strip_tags( $field->value );
+ }
+ }
+
+ $all_values = $extra_values = array();
+ $i = 1; // Prefix counter for stored metadata
+
+ // For all fields, grab label and value
+ foreach ( $field_ids['all'] as $field_id ) {
+ $field = $this->fields[ $field_id ];
+ $label = $i . '_' . $field->get_attribute( 'label' );
+ $value = $field->value;
+
+ $all_values[ $label ] = $value;
+ $i++; // Increment prefix counter for the next field
+ }
+
+ // For the "non-standard" fields, grab label and value
+ // Extra fields have their prefix starting from count( $all_values ) + 1
+ foreach ( $field_ids['extra'] as $field_id ) {
+ $field = $this->fields[ $field_id ];
+ $label = $i . '_' . $field->get_attribute( 'label' );
+ $value = $field->value;
+
+ if ( is_array( $value ) ) {
+ $value = implode( ', ', $value );
+ }
+
+ $extra_values[ $label ] = $value;
+ $i++; // Increment prefix counter for the next extra field
+ }
+
+ $contact_form_subject = trim( $contact_form_subject );
+
+ $comment_author_IP = Grunion_Contact_Form_Plugin::get_ip_address();
+
+ $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 );
+ }
+
+ // Ensure that Akismet gets all of the relevant information from the contact form,
+ // not just the textarea field and predetermined subject.
+ $akismet_vars = compact( $vars );
+ $akismet_vars['comment_content'] = $comment_content;
+
+ foreach ( array_merge( $field_ids['all'], $field_ids['extra'] ) as $field_id ) {
+ $field = $this->fields[ $field_id ];
+
+ // Skip any fields that are just a choice from a pre-defined list. They wouldn't have any value
+ // from a spam-filtering point of view.
+ if ( in_array( $field->get_attribute( 'type' ), array( 'select', 'checkbox', 'checkbox-multiple', 'radio' ) ) ) {
+ continue;
+ }
+
+ // Normalize the label into a slug.
+ $field_slug = trim( // Strip all leading/trailing dashes.
+ preg_replace( // Normalize everything to a-z0-9_-
+ '/[^a-z0-9_]+/',
+ '-',
+ strtolower( $field->get_attribute( 'label' ) ) // Lowercase
+ ),
+ '-'
+ );
+
+ $field_value = ( is_array( $field->value ) ) ? trim( implode( ', ', $field->value ) ) : trim( $field->value );
+
+ // Skip any values that are already in the array we're sending.
+ if ( $field_value && in_array( $field_value, $akismet_vars ) ) {
+ continue;
+ }
+
+ $akismet_vars[ 'contact_form_field_' . $field_slug ] = $field_value;
+ }
+
+ $spam = '';
+ $akismet_values = $plugin->prepare_for_akismet( $akismet_vars );
+
+ // Is it spam?
+ /** This filter is already documented in modules/contact-form/admin.php */
+ $is_spam = apply_filters( 'jetpack_contact_form_is_spam', false, $akismet_values );
+ if ( is_wp_error( $is_spam ) ) { // WP_Error to abort
+ return $is_spam; // abort
+ } elseif ( $is_spam === true ) { // TRUE to flag a spam
+ $spam = '***SPAM*** ';
+ }
+
+ if ( ! $comment_author ) {
+ $comment_author = $comment_author_email;
+ }
+
+ /**
+ * Filter the email where a submitted feedback is sent.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param string|array $to Array of valid email addresses, or single email address.
+ */
+ $to = (array) apply_filters( 'contact_form_to', $to );
+ $reply_to_addr = $to[0]; // get just the address part before the name part is added
+
+ foreach ( $to as $to_key => $to_value ) {
+ $to[ $to_key ] = Grunion_Contact_Form_Plugin::strip_tags( $to_value );
+ $to[ $to_key ] = self::add_name_to_address( $to_value );
+ }
+
+ $blog_url = parse_url( site_url() );
+ $from_email_addr = 'wordpress@' . $blog_url['host'];
+
+ if ( ! empty( $comment_author_email ) ) {
+ $reply_to_addr = $comment_author_email;
+ }
+
+ $headers = 'From: "' . $comment_author . '" <' . $from_email_addr . ">\r\n" .
+ 'Reply-To: "' . $comment_author . '" <' . $reply_to_addr . ">\r\n";
+
+ // 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,
+ )
+ );
+
+ /** 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 );
+
+ $date_time_format = _x( '%1$s \a\t %2$s', '{$date_format} \a\t {$time_format}', 'jetpack' );
+ $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';
+
+ foreach ( (array) $akismet_values as $av_key => $av_value ) {
+ $akismet_values[ $av_key ] = Grunion_Contact_Form_Plugin::strip_tags( $av_value );
+ }
+
+ foreach ( (array) $all_values as $all_key => $all_value ) {
+ $all_values[ $all_key ] = Grunion_Contact_Form_Plugin::strip_tags( $all_value );
+ }
+
+ foreach ( (array) $extra_values as $ev_key => $ev_value ) {
+ $extra_values[ $ev_key ] = Grunion_Contact_Form_Plugin::strip_tags( $ev_value );
+ }
+
+ /*
+ We need to make sure that the post author is always zero for contact
+ * form submissions. This prevents export/import from trying to create
+ * new users based on form submissions from people who were logged in
+ * at the time.
+ *
+ * Unfortunately wp_insert_post() tries very hard to make sure the post
+ * author gets the currently logged in user id. That is how we ended up
+ * with this work around. */
+ add_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10, 2 );
+
+ $post_id = wp_insert_post(
+ array(
+ 'post_date' => addslashes( $feedback_time ),
+ 'post_type' => 'feedback',
+ 'post_status' => addslashes( $feedback_status ),
+ 'post_parent' => (int) $post->ID,
+ 'post_title' => addslashes( wp_kses( $feedback_title, array() ) ),
+ 'post_content' => addslashes( wp_kses( $comment_content . "\n<!--more-->\n" . "AUTHOR: {$comment_author}\nAUTHOR EMAIL: {$comment_author_email}\nAUTHOR URL: {$comment_author_url}\nSUBJECT: {$subject}\nIP: {$comment_author_IP}\n" . @print_r( $all_values, true ), array() ) ), // so that search will pick up this data
+ 'post_name' => $feedback_id,
+ )
+ );
+
+ // once insert has finished we don't need this filter any more
+ remove_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10 );
+
+ update_post_meta( $post_id, '_feedback_extra_fields', $this->addslashes_deep( $extra_values ) );
+
+ if ( 'publish' == $feedback_status ) {
+ // Increase count of unread feedback.
+ $unread = get_option( 'feedback_unread_count', 0 ) + 1;
+ update_option( 'feedback_unread_count', $unread );
+ }
+
+ if ( defined( 'AKISMET_VERSION' ) ) {
+ update_post_meta( $post_id, '_feedback_akismet_values', $this->addslashes_deep( $akismet_values ) );
+ }
+
+ $message = self::get_compiled_form( $post_id, $this );
+
+ array_push(
+ $message,
+ '<br />',
+ '<hr />',
+ __( 'Time:', 'jetpack' ) . ' ' . $time . '<br />',
+ __( 'IP Address:', 'jetpack' ) . ' ' . $comment_author_IP . '<br />',
+ __( 'Contact Form URL:', 'jetpack' ) . ' ' . $url . '<br />'
+ );
+
+ if ( is_user_logged_in() ) {
+ array_push(
+ $message,
+ sprintf(
+ '<p>' . __( 'Sent by a verified %s user.', 'jetpack' ) . '</p>',
+ isset( $GLOBALS['current_site']->site_name ) && $GLOBALS['current_site']->site_name ?
+ $GLOBALS['current_site']->site_name : '"' . get_option( 'blogname' ) . '"'
+ )
+ );
+ } else {
+ array_push( $message, '<p>' . __( 'Sent by an unverified visitor to your site.', 'jetpack' ) . '</p>' );
+ }
+
+ $message = join( $message, '' );
+
+ /**
+ * Filters the message sent via email after a successful form submission.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param string $message Feedback email message.
+ */
+ $message = apply_filters( 'contact_form_message', $message );
+
+ // This is called after `contact_form_message`, in order to preserve back-compat
+ $message = self::wrap_message_in_html_tags( $message );
+
+ update_post_meta( $post_id, '_feedback_email', $this->addslashes_deep( compact( 'to', 'message' ) ) );
+
+ /**
+ * Fires right before the contact form message is sent via email to
+ * the recipient specified in the contact form.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param integer $post_id Post contact form lives on
+ * @param array $all_values Contact form fields
+ * @param array $extra_values Contact form fields not included in $all_values
+ */
+ do_action( 'grunion_pre_message_sent', $post_id, $all_values, $extra_values );
+
+ // schedule deletes of old spam feedbacks
+ if ( ! wp_next_scheduled( 'grunion_scheduled_delete' ) ) {
+ wp_schedule_event( time() + 250, 'daily', 'grunion_scheduled_delete' );
+ }
+
+ if (
+ $is_spam !== true &&
+ /**
+ * Filter to choose whether an email should be sent after each successful contact form submission.
+ *
+ * @module contact-form
+ *
+ * @since 2.6.0
+ *
+ * @param bool true Should an email be sent after a form submission. Default to true.
+ * @param int $post_id Post ID.
+ */
+ true === apply_filters( 'grunion_should_send_email', true, $post_id )
+ ) {
+ self::wp_mail( $to, "{$spam}{$subject}", $message, $headers );
+ } elseif (
+ true === $is_spam &&
+ /**
+ * Choose whether an email should be sent for each spam contact form submission.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ *
+ * @param bool false Should an email be sent after a spam form submission. Default to false.
+ */
+ apply_filters( 'grunion_still_email_spam', false ) == true
+ ) { // don't send spam by default. Filterable.
+ self::wp_mail( $to, "{$spam}{$subject}", $message, $headers );
+ }
+
+ /**
+ * Fires an action hook right after the email(s) have been sent.
+ *
+ * @module contact-form
+ *
+ * @since 7.3.0
+ *
+ * @param int $post_id Post contact form lives on.
+ * @param string|array $to Array of valid email addresses, or single email address.
+ * @param string $subject Feedback email subject.
+ * @param string $message Feedback email message.
+ * @param string|array $headers Optional. Additional headers.
+ * @param array $all_values Contact form fields.
+ * @param array $extra_values Contact form fields not included in $all_values
+ */
+ do_action( 'grunion_after_message_sent', $post_id, $to, $subject, $message, $headers, $all_values, $extra_values );
+
+ if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
+ return self::success_message( $post_id, $this );
+ }
+
+ $redirect = wp_get_referer();
+ if ( ! $redirect ) { // wp_get_referer() returns false if the referer is the same as the current page
+ $redirect = $_SERVER['REQUEST_URI'];
+ }
+
+ $redirect = add_query_arg(
+ urlencode_deep(
+ array(
+ 'contact-form-id' => $id,
+ 'contact-form-sent' => $post_id,
+ 'contact-form-hash' => $this->hash,
+ '_wpnonce' => wp_create_nonce( "contact-form-sent-{$post_id}" ), // wp_nonce_url HTMLencodes :(
+ )
+ ), $redirect
+ );
+
+ /**
+ * Filter the URL where the reader is redirected after submitting a form.
+ *
+ * @module contact-form
+ *
+ * @since 1.9.0
+ *
+ * @param string $redirect Post submission URL.
+ * @param int $id Contact Form ID.
+ * @param int $post_id Post ID.
+ */
+ $redirect = apply_filters( 'grunion_contact_form_redirect_url', $redirect, $id, $post_id );
+
+ wp_safe_redirect( $redirect );
+ exit;
+ }
+
+ /**
+ * Wrapper for wp_mail() that enables HTML messages with text alternatives
+ *
+ * @param string|array $to Array or comma-separated list of email addresses to send message.
+ * @param string $subject Email subject.
+ * @param string $message Message contents.
+ * @param string|array $headers Optional. Additional headers.
+ * @param string|array $attachments Optional. Files to attach.
+ *
+ * @return bool Whether the email contents were sent successfully.
+ */
+ public static function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() ) {
+ add_filter( 'wp_mail_content_type', __CLASS__ . '::get_mail_content_type' );
+ add_action( 'phpmailer_init', __CLASS__ . '::add_plain_text_alternative' );
+
+ $result = wp_mail( $to, $subject, $message, $headers, $attachments );
+
+ remove_filter( 'wp_mail_content_type', __CLASS__ . '::get_mail_content_type' );
+ remove_action( 'phpmailer_init', __CLASS__ . '::add_plain_text_alternative' );
+
+ return $result;
+ }
+
+ /**
+ * 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>`.
+ *
+ * @param string $address
+ *
+ * @return string
+ */
+ function add_name_to_address( $address ) {
+ // 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 );
+ }
+
+ return $address;
+ }
+
+ /**
+ * Get the content type that should be assigned to outbound emails
+ *
+ * @return string
+ */
+ static function get_mail_content_type() {
+ return 'text/html';
+ }
+
+ /**
+ * Wrap a message body with the appropriate in HTML tags
+ *
+ * This helps to ensure correct parsing by clients, and also helps avoid triggering spam filtering rules
+ *
+ * @param string $body
+ *
+ * @return string
+ */
+ static function wrap_message_in_html_tags( $body ) {
+ // Don't do anything if the message was already wrapped in HTML tags
+ // That could have be done by a plugin via filters
+ if ( false !== strpos( $body, '<html' ) ) {
+ return $body;
+ }
+
+ $html_message = sprintf(
+ // The tabs are just here so that the raw code is correctly formatted for developers
+ // They're removed so that they don't affect the final message sent to users
+ str_replace(
+ "\t", '',
+ '<!doctype html>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+
+ %s
+
+ </body>
+ </html>'
+ ),
+ $body
+ );
+
+ return $html_message;
+ }
+
+ /**
+ * Add a plain-text alternative part to an outbound email
+ *
+ * This makes the message more accessible to mail clients that aren't HTML-aware, and decreases the likelihood
+ * that the message will be flagged as spam.
+ *
+ * @param PHPMailer $phpmailer
+ */
+ static function add_plain_text_alternative( $phpmailer ) {
+ // Add an extra break so that the extra space above the <p> is preserved after the <p> is stripped out
+ $alt_body = str_replace( '<p>', '<p><br />', $phpmailer->Body );
+
+ // Convert <br> to \n breaks, to preserve the space between lines that we want to keep
+ $alt_body = str_replace( array( '<br>', '<br />' ), "\n", $alt_body );
+
+ // Convert <hr> to an plain-text equivalent, to preserve the integrity of the message
+ $alt_body = str_replace( array( '<hr>', '<hr />' ), "----\n", $alt_body );
+
+ // Trim the plain text message to remove the \n breaks that were after <doctype>, <html>, and <body>
+ $phpmailer->AltBody = trim( strip_tags( $alt_body ) );
+ }
+
+ function addslashes_deep( $value ) {
+ if ( is_array( $value ) ) {
+ return array_map( array( $this, 'addslashes_deep' ), $value );
+ } elseif ( is_object( $value ) ) {
+ $vars = get_object_vars( $value );
+ foreach ( $vars as $key => $data ) {
+ $value->{$key} = $this->addslashes_deep( $data );
+ }
+ return $value;
+ }
+
+ return addslashes( $value );
+ }
+}
+
+/**
+ * Class for the contact-field shortcode.
+ * Parses shortcode to output the contact form field as HTML.
+ * Validates input.
+ */
+class Grunion_Contact_Form_Field extends Crunion_Contact_Form_Shortcode {
+ public $shortcode_name = 'contact-field';
+
+ /**
+ * @var Grunion_Contact_Form parent form
+ */
+ public $form;
+
+ /**
+ * @var string default or POSTed value
+ */
+ public $value;
+
+ /**
+ * @var bool Is the input invalid?
+ */
+ public $error = false;
+
+ /**
+ * @param array $attributes An associative array of shortcode attributes. @see shortcode_atts()
+ * @param null|string $content Null for selfclosing shortcodes. The inner content otherwise.
+ * @param Grunion_Contact_Form $form The parent form
+ */
+ 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,
+ ), $attributes, 'contact-field'
+ );
+
+ // special default for subject field
+ if ( 'subject' == $attributes['type'] && is_null( $attributes['default'] ) && ! is_null( $form ) ) {
+ $attributes['default'] = $form->get_attribute( 'subject' );
+ }
+
+ // allow required=1 or required=true
+ if ( '1' == $attributes['required'] || 'true' == strtolower( $attributes['required'] ) ) {
+ $attributes['required'] = true;
+ } else {
+ $attributes['required'] = false;
+ }
+
+ // parse out comma-separated options list (for selects, radios, and checkbox-multiples)
+ if ( ! empty( $attributes['options'] ) && is_string( $attributes['options'] ) ) {
+ $attributes['options'] = array_map( 'trim', explode( ',', $attributes['options'] ) );
+
+ if ( ! empty( $attributes['values'] ) && is_string( $attributes['values'] ) ) {
+ $attributes['values'] = array_map( 'trim', explode( ',', $attributes['values'] ) );
+ }
+ }
+
+ if ( $form ) {
+ // make a unique field ID based on the label, with an incrementing number if needed to avoid clashes
+ $form_id = $form->get_attribute( 'id' );
+ $id = isset( $attributes['id'] ) ? $attributes['id'] : false;
+
+ $unescaped_label = $this->unesc_attr( $attributes['label'] );
+ $unescaped_label = str_replace( '%', '-', $unescaped_label ); // jQuery doesn't like % in IDs?
+ $unescaped_label = preg_replace( '/[^a-zA-Z0-9.-_:]/', '', $unescaped_label );
+
+ if ( empty( $id ) ) {
+ $id = sanitize_title_with_dashes( 'g' . $form_id . '-' . $unescaped_label );
+ $i = 0;
+ $max_tries = 99;
+ while ( isset( $form->fields[ $id ] ) ) {
+ $i++;
+ $id = sanitize_title_with_dashes( 'g' . $form_id . '-' . $unescaped_label . '-' . $i );
+
+ if ( $i > $max_tries ) {
+ break;
+ }
+ }
+ }
+
+ $attributes['id'] = $id;
+ }
+
+ parent::__construct( $attributes, $content );
+
+ // Store parent form
+ $this->form = $form;
+ }
+
+ /**
+ * This field's input is invalid. Flag as invalid and add an error to the parent form
+ *
+ * @param string $message The error message to display on the form.
+ */
+ function add_error( $message ) {
+ $this->is_error = true;
+
+ if ( ! is_wp_error( $this->form->errors ) ) {
+ $this->form->errors = new WP_Error;
+ }
+
+ $this->form->errors->add( $this->get_attribute( 'id' ), $message );
+ }
+
+ /**
+ * Is the field input invalid?
+ *
+ * @see $error
+ *
+ * @return bool
+ */
+ function is_error() {
+ return $this->error;
+ }
+
+ /**
+ * Validates the form input
+ */
+ function validate() {
+ // If it's not required, there's nothing to validate
+ if ( ! $this->get_attribute( 'required' ) ) {
+ return;
+ }
+
+ $field_id = $this->get_attribute( 'id' );
+ $field_type = $this->get_attribute( 'type' );
+ $field_label = $this->get_attribute( 'label' );
+
+ if ( isset( $_POST[ $field_id ] ) ) {
+ if ( is_array( $_POST[ $field_id ] ) ) {
+ $field_value = array_map( 'stripslashes', $_POST[ $field_id ] );
+ } else {
+ $field_value = stripslashes( $_POST[ $field_id ] );
+ }
+ } else {
+ $field_value = '';
+ }
+
+ switch ( $field_type ) {
+ case 'email':
+ // Make sure the email address is valid
+ if ( ! 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 ) );
+ }
+ break;
+ case 'checkbox-multiple':
+ // Check that there is at least one option selected
+ if ( empty( $field_value ) ) {
+ /* translators: %s is the name of a form field */
+ $this->add_error( sprintf( __( '%s requires at least one selection', 'jetpack' ), $field_label ) );
+ }
+ break;
+ default:
+ // Just check for presence of any text
+ if ( ! strlen( trim( $field_value ) ) ) {
+ /* translators: %s is the name of a form field */
+ $this->add_error( sprintf( __( '%s is required', 'jetpack' ), $field_label ) );
+ }
+ }
+ }
+
+
+ /**
+ * Check the default value for options field
+ *
+ * @param string value
+ * @param int index
+ * @param string default value
+ *
+ * @return string
+ */
+ public function get_option_value( $value, $index, $options ) {
+ if ( empty( $value[ $index ] ) ) {
+ return $options;
+ }
+ return $value[ $index ];
+ }
+
+ /**
+ * Outputs the HTML for this form field
+ *
+ * @return string HTML
+ */
+ function render() {
+ global $current_user, $user_identity;
+
+ $field_id = $this->get_attribute( 'id' );
+ $field_type = $this->get_attribute( 'type' );
+ $field_label = $this->get_attribute( 'label' );
+ $field_required = $this->get_attribute( 'required' );
+ $field_placeholder = $this->get_attribute( 'placeholder' );
+ $class = 'date' === $field_type ? 'jp-contact-form-date' : $this->get_attribute( 'class' );
+
+ /**
+ * Filters the "class" attribute of the contact form input
+ *
+ * @module contact-form
+ *
+ * @since 6.6.0
+ *
+ * @param string $class Additional CSS classes for input class attribute.
+ */
+ $field_class = apply_filters( 'jetpack_contact_form_input_class', $class );
+
+ if ( isset( $_POST[ $field_id ] ) ) {
+ if ( is_array( $_POST[ $field_id ] ) ) {
+ $this->value = array_map( 'stripslashes', $_POST[ $field_id ] );
+ } else {
+ $this->value = stripslashes( (string) $_POST[ $field_id ] );
+ }
+ } elseif ( isset( $_GET[ $field_id ] ) ) {
+ $this->value = stripslashes( (string) $_GET[ $field_id ] );
+ } elseif (
+ is_user_logged_in() &&
+ ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) ||
+ /**
+ * Allow third-party tools to prefill the contact form with the user's details when they're logged in.
+ *
+ * @module contact-form
+ *
+ * @since 3.2.0
+ *
+ * @param bool false Should the Contact Form be prefilled with your details when you're logged in. Default to false.
+ */
+ true === apply_filters( 'jetpack_auto_fill_logged_in_user', false )
+ )
+ ) {
+ // Special defaults for logged-in users
+ switch ( $this->get_attribute( 'type' ) ) {
+ case 'email':
+ $this->value = $current_user->data->user_email;
+ break;
+ case 'name':
+ $this->value = $user_identity;
+ break;
+ case 'url':
+ $this->value = $current_user->data->user_url;
+ break;
+ default:
+ $this->value = $this->get_attribute( 'default' );
+ }
+ } else {
+ $this->value = $this->get_attribute( 'default' );
+ }
+
+ $field_value = Grunion_Contact_Form_Plugin::strip_tags( $this->value );
+ $field_label = Grunion_Contact_Form_Plugin::strip_tags( $field_label );
+
+ $rendered_field = $this->render_field( $field_type, $field_id, $field_label, $field_value, $field_class, $field_placeholder, $field_required );
+
+ /**
+ * Filter the HTML of the Contact Form.
+ *
+ * @module contact-form
+ *
+ * @since 2.6.0
+ *
+ * @param string $rendered_field Contact Form HTML output.
+ * @param string $field_label Field label.
+ * @param int|null $id Post ID.
+ */
+ 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 ) {
+
+ $type_class = $type ? ' ' .$type : '';
+ return
+ "<label
+ for='" . esc_attr( $id ) . "'
+ class='grunion-field-label{$type_class}" . ( $this->is_error() ? ' form-error' : '' ) . "'
+ >"
+ . esc_html( $label )
+ . ( $required ? '<span>' . $required_field_text . '</span>' : '' )
+ . "</label>\n";
+
+ }
+
+ function render_input_field( $type, $id, $value, $class, $placeholder, $required ) {
+ return "<input
+ type='". esc_attr( $type ) ."'
+ name='" . esc_attr( $id ) . "'
+ id='" . esc_attr( $id ) . "'
+ value='" . esc_attr( $value ) . "'
+ " . $class . $placeholder . '
+ ' . ( $required ? "required aria-required='true'" : '' ) . "
+ />\n";
+ }
+
+ function render_email_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder ) {
+ $field = $this->render_label( 'email', $id, $label, $required, $required_field_text );
+ $field .= $this->render_input_field( 'email', $id, $value, $class, $placeholder, $required );
+ return $field;
+ }
+
+ function render_telephone_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder ) {
+ $field = $this->render_label( 'telephone', $id, $label, $required, $required_field_text );
+ $field .= $this->render_input_field( 'tel', $id, $value, $class, $placeholder, $required );
+ return $field;
+ }
+
+ function render_url_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder ) {
+ $field = $this->render_label( 'url', $id, $label, $required, $required_field_text );
+ $field .= $this->render_input_field( 'url', $id, $value, $class, $placeholder, $required );
+ return $field;
+ }
+
+ function render_textarea_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder ) {
+ $field = $this->render_label( 'textarea', 'contact-form-comment-' . $id, $label, $required, $required_field_text );
+ $field .= "<textarea
+ name='" . esc_attr( $id ) . "'
+ id='contact-form-comment-" . esc_attr( $id ) . "'
+ rows='20' "
+ . $class
+ . $placeholder
+ . ' ' . ( $required ? "required aria-required='true'" : '' ) .
+ '>' . esc_textarea( $value )
+ . "</textarea>\n";
+ return $field;
+ }
+
+ function render_radio_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 ) {
+ $option = Grunion_Contact_Form_Plugin::strip_tags( $option );
+ if ( $option ) {
+ $field .= "\t\t<label class='grunion-radio-label radio" . ( $this->is_error() ? ' form-error' : '' ) . "'>";
+ $field .= "<input
+ type='radio'
+ name='" . esc_attr( $id ) . "'
+ value='" . esc_attr( $this->get_option_value( $this->get_attribute( 'values' ), $optionIndex, $option ) ) . "' "
+ . $class
+ . checked( $option, $value, false ) . ' '
+ . ( $required ? "required aria-required='true'" : '' )
+ . '/> ';
+ $field .= esc_html( $option ) . "</label>\n";
+ $field .= "\t\t<div class='clear-form'></div>\n";
+ }
+ }
+ return $field;
+ }
+
+ function render_checkbox_field( $id, $label, $value, $class, $required, $required_field_text ) {
+ $field = "<label class='grunion-field-label checkbox" . ( $this->is_error() ? ' form-error' : '' ) . "'>";
+ $field .= "\t\t<input type='checkbox' name='" . esc_attr( $id ) . "' value='" . esc_attr__( 'Yes', 'jetpack' ) . "' " . $class . checked( (bool) $value, true, false ) . ' ' . ( $required ? "required aria-required='true'" : '' ) . "/> \n";
+ $field .= "\t\t" . esc_html( $label ) . ( $required ? '<span>' . $required_field_text . '</span>' : '' );
+ $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 ) {
+ $option = Grunion_Contact_Form_Plugin::strip_tags( $option );
+ if ( $option ) {
+ $field .= "\t\t<label class='grunion-checkbox-multiple-label checkbox-multiple" . ( $this->is_error() ? ' form-error' : '' ) . "'>";
+ $field .= "<input type='checkbox' name='" . esc_attr( $id ) . "[]' value='" . esc_attr( $this->get_option_value( $this->get_attribute( 'values' ), $optionIndex, $option ) ) . "' " . $class . checked( in_array( $option, (array) $value ), true, false ) . ' /> ';
+ $field .= esc_html( $option ) . "</label>\n";
+ $field .= "\t\t<div class='clear-form'></div>\n";
+ }
+ }
+
+ return $field;
+ }
+
+ function render_select_field( $id, $label, $value, $class, $required, $required_field_text ) {
+ $field = $this->render_label( 'select', $id, $label, $required, $required_field_text );
+ $field .= "\t<select name='" . esc_attr( $id ) . "' id='" . esc_attr( $id ) . "' " . $class . ( $required ? "required aria-required='true'" : '' ) . ">\n";
+ foreach ( (array) $this->get_attribute( 'options' ) as $optionIndex => $option ) {
+ $option = Grunion_Contact_Form_Plugin::strip_tags( $option );
+ if ( $option ) {
+ $field .= "\t\t<option"
+ . selected( $option, $value, false )
+ . " value='" . esc_attr( $this->get_option_value( $this->get_attribute( 'values' ), $optionIndex, $option ) )
+ . "'>" . esc_html( $option )
+ . "</option>\n";
+ }
+ }
+ $field .= "\t</select>\n";
+ return $field;
+ }
+
+ function render_date_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder ) {
+ $field = $this->render_label( 'date', $id, $label, $required, $required_field_text );
+ $field .= $this->render_input_field( 'text', $id, $value, $class, $placeholder, $required );
+
+ wp_enqueue_script(
+ 'grunion-frontend',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion-frontend.min.js',
+ 'modules/contact-form/js/grunion-frontend.js'
+ ),
+ array( 'jquery', 'jquery-ui-datepicker' )
+ );
+ wp_enqueue_style( 'jp-jquery-ui-datepicker', plugins_url( 'css/jquery-ui-datepicker.css', __FILE__ ), array( 'dashicons' ), '1.0' );
+
+ // Using Core's built-in datepicker localization routine
+ wp_localize_jquery_ui_datepicker();
+ return $field;
+ }
+
+ function render_default_field( $id, $label, $value, $class, $required, $required_field_text, $placeholder, $type ) {
+ $field = $this->render_label( $type, $id, $label, $required, $required_field_text );
+ $field .= $this->render_input_field( 'text', $id, $value, $class, $placeholder, $required );
+ return $field;
+ }
+
+ function render_field( $type, $id, $label, $value, $class, $placeholder, $required ) {
+
+ $field_placeholder = ( ! empty( $placeholder ) ) ? "placeholder='" . esc_attr( $placeholder ) . "'" : '';
+ $field_class = "class='" . trim( esc_attr( $type ) . ' ' . esc_attr( $class ) ) . "' ";
+ $wrap_classes = empty( $class ) ? '' : implode( '-wrap ', array_filter( explode( ' ', $class ) ) ) . '-wrap'; // this adds
+
+ $shell_field_class = "class='grunion-field-wrap grunion-field-" . trim( esc_attr( $type ) . '-wrap ' . esc_attr( $wrap_classes ) ) . "' ";
+ /**
+ /**
+ * Filter the Contact Form required field text
+ *
+ * @module contact-form
+ *
+ * @since 3.8.0
+ *
+ * @param string $var Required field text. Default is "(required)".
+ */
+ $required_field_text = esc_html( apply_filters( 'jetpack_required_field_text', __( '(required)', 'jetpack' ) ) );
+
+ $field = "\n<div {$shell_field_class} >\n"; // new in Jetpack 6.8.0
+ // If they are logged in, and this is their site, don't pre-populate fields
+ if ( current_user_can( 'manage_options' ) ) {
+ $value = '';
+ }
+ switch ( $type ) {
+ case 'email':
+ $field .= $this->render_email_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ case 'telephone':
+ $field .= $this->render_telephone_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ case 'url':
+ $field .= $this->render_url_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ case 'textarea':
+ $field .= $this->render_textarea_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ case 'radio':
+ $field .= $this->render_radio_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ case 'checkbox':
+ $field .= $this->render_checkbox_field( $id, $label, $value, $field_class, $required, $required_field_text );
+ break;
+ case 'checkbox-multiple':
+ $field .= $this->render_checkbox_multiple_field( $id, $label, $value, $field_class, $required, $required_field_text );
+ break;
+ case 'select':
+ $field .= $this->render_select_field( $id, $label, $value, $field_class, $required, $required_field_text );
+ break;
+ case 'date':
+ $field .= $this->render_date_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder );
+ break;
+ default: // text field
+ $field .= $this->render_default_field( $id, $label, $value, $field_class, $required, $required_field_text, $field_placeholder, $type );
+ break;
+ }
+ $field .= "\t</div>\n";
+ return $field;
+ }
+}
+
+add_action( 'init', array( 'Grunion_Contact_Form_Plugin', 'init' ), 9 );
+
+add_action( 'grunion_scheduled_delete', 'grunion_delete_old_spam' );
+
+/**
+ * Deletes old spam feedbacks to keep the posts table size under control
+ */
+function grunion_delete_old_spam() {
+ global $wpdb;
+
+ $grunion_delete_limit = 100;
+
+ $now_gmt = current_time( 'mysql', 1 );
+ $sql = $wpdb->prepare(
+ "
+ SELECT `ID`
+ FROM $wpdb->posts
+ WHERE DATE_SUB( %s, INTERVAL 15 DAY ) > `post_date_gmt`
+ AND `post_type` = 'feedback'
+ AND `post_status` = 'spam'
+ LIMIT %d
+ ", $now_gmt, $grunion_delete_limit
+ );
+ $post_ids = $wpdb->get_col( $sql );
+
+ foreach ( (array) $post_ids as $post_id ) {
+ // force a full delete, skip the trash
+ wp_delete_post( $post_id, true );
+ }
+
+ if (
+ /**
+ * Filter if the module run OPTIMIZE TABLE on the core WP tables.
+ *
+ * @module contact-form
+ *
+ * @since 1.3.1
+ * @since 6.4.0 Set to false by default.
+ *
+ * @param bool $filter Should Jetpack optimize the table, defaults to false.
+ */
+ apply_filters( 'grunion_optimize_table', false )
+ ) {
+ $wpdb->query( "OPTIMIZE TABLE $wpdb->posts" );
+ }
+
+ // if we hit the max then schedule another run
+ if ( count( $post_ids ) >= $grunion_delete_limit ) {
+ wp_schedule_single_event( time() + 700, 'grunion_scheduled_delete' );
+ }
+}
diff --git a/plugins/jetpack/modules/contact-form/grunion-editor-view.php b/plugins/jetpack/modules/contact-form/grunion-editor-view.php
new file mode 100644
index 00000000..d1ba0439
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/grunion-editor-view.php
@@ -0,0 +1,299 @@
+<?php
+
+/*
+ * A prototype to allow inline editing / editor views for contact forms.\
+ *
+ * Originally developed in: http://github.com/automattic/gm2016-grunion-editor
+ * Authors: Michael Arestad, Andrew Ozz, and George Stephanis
+ */
+
+class Grunion_Editor_View {
+
+ /**
+ * Add hooks according to screen.
+ *
+ * @param WP_Screen $screen Data about current screen.
+ */
+ public static function add_hooks( $screen ) {
+ if ( isset( $screen->base ) && 'post' === $screen->base ) {
+ add_action( 'admin_notices', array( __CLASS__, 'handle_editor_view_js' ) );
+ add_action( 'admin_head', array( __CLASS__, 'admin_head' ) );
+ }
+ }
+
+ public static function admin_head() {
+ remove_action( 'media_buttons', 'grunion_media_button', 999 );
+ add_action( 'media_buttons', array( __CLASS__, 'grunion_media_button' ), 999 );
+ }
+
+ public static function grunion_media_button() {
+ $title = __( 'Add Contact Form', 'jetpack' );
+ ?>
+
+ <button type="button" id="insert-jetpack-contact-form" class="button" title="<?php echo esc_attr( $title ); ?>" href="javascript:;">
+ <span class="jetpack-contact-form-icon"></span>
+ <?php echo esc_html( $title ); ?>
+ </button>
+
+ <?php
+ }
+
+ public static function mce_external_plugins( $plugin_array ) {
+ $plugin_array['grunion_form'] = Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/tinymce-plugin-form-button.min.js',
+ 'modules/contact-form/js/tinymce-plugin-form-button.js'
+ );
+ return $plugin_array;
+ }
+
+ public static function mce_buttons( $buttons ) {
+ $size = sizeof( $buttons );
+ $buttons1 = array_slice( $buttons, 0, $size - 1 );
+ $buttons2 = array_slice( $buttons, $size - 1 );
+ return array_merge(
+ $buttons1,
+ array( 'grunion' ),
+ $buttons2
+ );
+ }
+
+ /**
+ * WordPress Shortcode Editor View JS Code
+ */
+ public static function handle_editor_view_js() {
+ add_action( 'admin_print_footer_scripts', array( __CLASS__, 'editor_view_js_templates' ), 1 );
+ add_filter( 'mce_external_plugins', array( __CLASS__, 'mce_external_plugins' ) );
+ add_filter( 'mce_buttons', array( __CLASS__, 'mce_buttons' ) );
+
+ wp_enqueue_style( 'grunion-editor-ui', plugins_url( 'css/editor-ui.css', __FILE__ ) );
+ wp_style_add_data( 'grunion-editor-ui', 'rtl', 'replace' );
+ wp_enqueue_script(
+ 'grunion-editor-view',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/editor-view.min.js',
+ 'modules/contact-form/js/editor-view.js'
+ ),
+ array( 'wp-util', 'jquery', 'quicktags' ),
+ false,
+ true
+ );
+ wp_localize_script(
+ 'grunion-editor-view', 'grunionEditorView', array(
+ 'inline_editing_style' => plugins_url( 'css/editor-inline-editing-style.css', __FILE__ ),
+ 'inline_editing_style_rtl' => plugins_url( 'css/editor-inline-editing-style-rtl.css', __FILE__ ),
+ 'dashicons_css_url' => includes_url( 'css/dashicons.css' ),
+ 'default_form' => '[contact-field label="' . __( 'Name', 'jetpack' ) . '" type="name" required="true" /]' .
+ '[contact-field label="' . __( 'Email', 'jetpack' ) . '" type="email" required="true" /]' .
+ '[contact-field label="' . __( 'Website', 'jetpack' ) . '" type="url" /]' .
+ '[contact-field label="' . __( 'Message', 'jetpack' ) . '" type="textarea" /]',
+ 'labels' => array(
+ 'submit_button_text' => __( 'Submit', 'jetpack' ),
+ /** This filter is documented in modules/contact-form/grunion-contact-form.php */
+ 'required_field_text' => apply_filters( 'jetpack_required_field_text', __( '(required)', 'jetpack' ) ),
+ 'edit_close_ays' => __( 'Are you sure you\'d like to stop editing this form without saving your changes?', 'jetpack' ),
+ 'quicktags_label' => __( 'contact form', 'jetpack' ),
+ 'tinymce_label' => __( 'Add contact form', 'jetpack' ),
+ ),
+ )
+ );
+
+ add_editor_style( plugin_dir_url( __FILE__ ) . 'css/editor-style.css' );
+ }
+
+ /**
+ * JS Templates.
+ */
+ public static function editor_view_js_templates() {
+ ?>
+<script type="text/html" id="tmpl-grunion-contact-form">
+ <form class="card jetpack-contact-form-shortcode-preview" action='#' method='post' class='contact-form commentsblock' onsubmit="return false;">
+ {{{ data.body }}}
+ <p class='contact-submit'>
+ <input type='submit' value='{{ data.submit_button_text }}' class='pushbutton-wide'/>
+ </p>
+ </form>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-email">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label email'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <input type='email' name='{{ data.id }}' id='{{ data.id }}' value='{{ data.value }}' class='{{ data.class }}' placeholder='{{ data.placeholder }}' />
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-telephone">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label telephone'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <input type='tel' name='{{ data.id }}' id='{{ data.id }}' value='{{ data.value }}' class='{{ data.class }}' placeholder='{{ data.placeholder }}' />
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-textarea">
+ <div>
+ <label for='contact-form-comment-{{ data.id }}' class='grunion-field-label textarea'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <textarea name='{{ data.id }}' id='contact-form-comment-{{ data.id }}' rows='20' class='{{ data.class }}' placeholder='{{ data.placeholder }}'>{{ data.value }}</textarea>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-radio">
+ <div>
+ <label class='grunion-field-label'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <# _.each( data.options, function( option ) { #>
+ <label class='grunion-radio-label radio'>
+ <input type='radio' name='{{ data.id }}' value='{{ option }}' class="{{ data.class }}" <# if ( option === data.value ) print( "checked='checked'" ) #> />
+ <span>{{ option }}</span>
+ </label>
+ <# }); #>
+ <div class='clear-form'></div>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-checkbox">
+ <div>
+ <label class='grunion-field-label checkbox'>
+ <input type='checkbox' name='{{ data.id }}' value='<?php esc_attr__( 'Yes', 'jetpack' ); ?>' class="{{ data.class }}" <# if ( data.value ) print( 'checked="checked"' ) #> />
+ <span>{{ data.label }}</span><# if ( data.required ) print( " <span>" + data.required + "</span>" ) #>
+ </label>
+ <div class='clear-form'></div>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-checkbox-multiple">
+ <div>
+ <label class='grunion-field-label'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <# _.each( data.options, function( option ) { #>
+ <label class='grunion-checkbox-multiple-label checkbox-multiple'>
+ <input type='checkbox' name='{{ data.id }}[]' value='{{ option }}' class="{{ data.class }}" <# if ( option === data.value || _.contains( data.value, option ) ) print( "checked='checked'" ) #> />
+ <span>{{ option }}</span>
+ </label>
+ <# }); #>
+ <div class='clear-form'></div>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-select">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label select'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <select name='{{ data.id }}' id='{{ data.id }}' class="{{ data.class }}">
+ <# _.each( data.options, function( option ) { #>
+ <option <# if ( option === data.value ) print( "selected='selected'" ) #>>{{ option }}</option>
+ <# }); #>
+ </select>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-date">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label {{ data.type }}'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <input type='text' name='{{ data.id }}' id='{{ data.id }}' value='{{ data.value }}' class="{{ data.class }}" />
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-text">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label {{ data.type }}'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <input type='text' name='{{ data.id }}' id='{{ data.id }}' value='{{ data.value }}' class='{{ data.class }}' placeholder='{{ data.placeholder }}' />
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-url">
+ <div>
+ <label for='{{ data.id }}' class='grunion-field-label {{ data.type }}'>{{ data.label }}<# if ( data.required ) print( " <span>" + data.required + "</span>" ) #></label>
+ <input type='url' name='{{ data.id }}' id='{{ data.id }}' value='{{ data.value }}' class='{{ data.class }}' placeholder='{{ data.placeholder }}' />
+ </div>
+</script>
+
+
+<script type="text/html" id="tmpl-grunion-field-edit">
+ <div class="card is-compact grunion-field-edit grunion-field-{{ data.type }}" aria-label="<?php esc_attr_e( 'Form Field', 'jetpack' ); ?>">
+ <label class="grunion-name">
+ <span><?php esc_html_e( 'Field Label', 'jetpack' ); ?></span>
+ <input type="text" name="label" placeholder="<?php esc_attr_e( 'Label', 'jetpack' ); ?>" value="{{ data.label }}"/>
+ </label>
+
+ <?php
+ $grunion_field_types = array(
+ 'text' => __( 'Text', 'jetpack' ),
+ 'name' => __( 'Name', 'jetpack' ),
+ 'email' => __( 'Email', 'jetpack' ),
+ 'url' => __( 'Website', 'jetpack' ),
+ 'textarea' => __( 'Textarea', 'jetpack' ),
+ 'checkbox' => __( 'Checkbox', 'jetpack' ),
+ 'checkbox-multiple' => __( 'Checkbox with Multiple Items', 'jetpack' ),
+ 'select' => __( 'Drop down', 'jetpack' ),
+ 'radio' => __( 'Radio', 'jetpack' ),
+ 'date' => __( 'Date', 'jetpack' ),
+ );
+ ?>
+ <div class="grunion-type-options">
+ <label class="grunion-type">
+ <?php esc_html_e( 'Field Type', 'jetpack' ); ?>
+ <select name="type">
+ <?php foreach ( $grunion_field_types as $type => $label ) : ?>
+ <option <# if ( '<?php echo esc_js( $type ); ?>' === data.type ) print( "selected='selected'" ) #> value="<?php echo esc_attr( $type ); ?>">
+ <?php echo esc_html( $label ); ?>
+ </option>
+ <?php endforeach; ?>
+ </select>
+ </label>
+
+ <label class="grunion-required">
+ <input type="checkbox" name="required" value="1" <# if ( data.required ) print( 'checked="checked"' ) #> />
+ <span><?php esc_html_e( 'Required?', 'jetpack' ); ?></span>
+ </label>
+ </div>
+
+ <label class="grunion-options">
+ <?php esc_html_e( 'Options', 'jetpack' ); ?>
+ <ol>
+ <# if ( data.options ) { #>
+ <# _.each( data.options, function( option ) { #>
+ <li><input type="text" name="option" value="{{ option }}" /> <a class="delete-option" href="javascript:;"><span class="screen-reader-text"><?php esc_html_e( 'Delete Option', 'jetpack' ); ?></span></a></li>
+ <# }); #>
+ <# } else { #>
+ <li><input type="text" name="option" /> <a class="delete-option" href="javascript:;"><span class="screen-reader-text"><?php esc_html_e( 'Delete Option', 'jetpack' ); ?></span></a></li>
+ <li><input type="text" name="option" /> <a class="delete-option" href="javascript:;"><span class="screen-reader-text"><?php esc_html_e( 'Delete Option', 'jetpack' ); ?></span></a></li>
+ <li><input type="text" name="option" /> <a class="delete-option" href="javascript:;"><span class="screen-reader-text"><?php esc_html_e( 'Delete Option', 'jetpack' ); ?></span></a></li>
+ <# } #>
+ <li><a class="add-option" href="javascript:;"><?php esc_html_e( 'Add new option...', 'jetpack' ); ?></a></li>
+ </ol>
+ </label>
+
+ <a href="javascript:;" class="delete-field"><span class="screen-reader-text"><?php esc_html_e( 'Delete Field', 'jetpack' ); ?></span></a>
+ </div>
+</script>
+
+<script type="text/html" id="tmpl-grunion-field-edit-option">
+ <li><input type="text" name="option" /> <a class="delete-option" href="javascript:;"><span class="screen-reader-text"><?php esc_html_e( 'Delete Option', 'jetpack' ); ?></span></a></li>
+</script>
+
+<script type="text/html" id="tmpl-grunion-editor-inline">
+ <h1 id="form-settings-header" class="grunion-section-header"><?php esc_html_e( 'Contact form information', 'jetpack' ); ?></h1>
+ <section class="card grunion-form-settings" aria-labelledby="form-settings-header">
+ <label><?php esc_html_e( 'What would you like the subject of the email to be?', 'jetpack' ); ?>
+ <input type="text" name="subject" value="{{ data.subject }}" />
+ </label>
+ <label><?php esc_html_e( 'Which email address should we send the submissions to?', 'jetpack' ); ?>
+ <input type="text" name="to" value="{{ data.to }}" />
+ </label>
+ </section>
+ <h1 id="form-fields-header" class="grunion-section-header"><?php esc_html_e( 'Contact form fields', 'jetpack' ); ?></h1>
+ <section class="grunion-fields" aria-labelledby="form-fields-header">
+ {{{ data.fields }}}
+ </section>
+ <section class="grunion-controls">
+ <?php submit_button( esc_html__( 'Add Field', 'jetpack' ), 'secondary', 'add-field', false ); ?>
+
+ <div class="grunion-update-controls">
+ <?php submit_button( esc_html__( 'Cancel', 'jetpack' ), 'delete', 'cancel', false ); ?>
+ <?php submit_button( esc_html__( 'Update Form', 'jetpack' ), 'primary', 'submit', false ); ?>
+ </div>
+ </section>
+</script>
+
+</div>
+ <?php
+ }
+}
+
+add_action( 'current_screen', array( 'Grunion_Editor_View', 'add_hooks' ) );
diff --git a/plugins/jetpack/modules/contact-form/grunion-form-view.php b/plugins/jetpack/modules/contact-form/grunion-form-view.php
new file mode 100644
index 00000000..26ed6dfe
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/grunion-form-view.php
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Template for form builder
+ */
+
+/**
+ * Filter to modify the limit of 5 additional contact form fields.
+ *
+ * @module contact-form
+ *
+ * @since 3.2.0
+ *
+ * @param int 5 Maximum number of additional fields.
+ */
+$max_new_fields = apply_filters( 'grunion_max_new_fields', 5 );
+
+wp_register_script(
+ 'grunion',
+ Jetpack::get_file_url_for_environment(
+ '_inc/build/contact-form/js/grunion.min.js',
+ 'modules/contact-form/js/grunion.js'
+ ),
+ array( 'jquery-ui-sortable', 'jquery-ui-draggable' ),
+ JETPACK__VERSION
+);
+
+wp_localize_script(
+ 'grunion', 'GrunionFB_i18n', array(
+ 'nameLabel' => esc_attr( _x( 'Name', 'Label for HTML form "Name" field in contact form builder', 'jetpack' ) ),
+ 'emailLabel' => esc_attr( _x( 'Email', 'Label for HTML form "Email" field in contact form builder', 'jetpack' ) ),
+ 'urlLabel' => esc_attr( _x( 'Website', 'Label for HTML form "URL/Website" field in contact form builder', 'jetpack' ) ),
+ 'commentLabel' => esc_attr( _x( 'Comment', 'noun', 'jetpack' ) ),
+ 'newLabel' => esc_attr( _x( 'New Field', 'Default label for new HTML form field in contact form builder', 'jetpack' ) ),
+ 'optionsLabel' => esc_attr( _x( 'Options', 'Label for the set of options to be included in a user-created dropdown in contact form builder', 'jetpack' ) ),
+ 'optionsLabel' => esc_attr( _x( 'Option', 'Label for an option to be included in a user-created dropdown in contact form builder', 'jetpack' ) ),
+ 'firstOptionLabel' => esc_attr( _x( 'First option', 'Default label for the first option to be included in a user-created dropdown in contact form builder', 'jetpack' ) ),
+ 'problemGeneratingForm' => esc_attr( _x( "Oops, there was a problem generating your form. You'll likely need to try again.", 'error message in contact form builder', 'jetpack' ) ),
+ 'moveInstructions' => esc_attr__( "Drag up or down\nto re-arrange", 'jetpack' ),
+ 'moveLabel' => esc_attr( _x( 'move', 'Label to drag HTML form fields around to change their order in contact form builder', 'jetpack' ) ),
+ 'editLabel' => esc_attr( _x( 'edit', 'Link to edit an HTML form field in contact form builder', 'jetpack' ) ),
+ 'savedMessage' => esc_attr__( 'Saved successfully', 'jetpack' ),
+ 'requiredLabel' => esc_attr( _x( '(required)', 'This HTML form field is marked as required by the user in contact form builder', 'jetpack' ) ),
+ 'exitConfirmMessage' => esc_attr__( 'Are you sure you want to exit the form editor without saving? Any changes you have made will be lost.', 'jetpack' ),
+ 'maxNewFields' => intval( $max_new_fields ),
+ )
+);
+
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title><?php esc_html_e( 'Contact Form', 'jetpack' ); ?></title>
+<script type="text/javascript">
+ var ajaxurl = '<?php echo admin_url( 'admin-ajax.php' ); ?>';
+ var postId = <?php echo absint( $_GET['post_id'] ); ?>;
+ var ajax_nonce_shortcode = '<?php echo wp_create_nonce( 'grunion_shortcode' ); ?>';
+ var ajax_nonce_json = '<?php echo wp_create_nonce( 'grunion_shortcode_to_json' ); ?>';
+</script>
+<?php wp_print_scripts( 'grunion' ); ?>
+<script type="text/javascript">
+ jQuery(document).ready(function () {
+ FB.ContactForm.init();
+ FB.ContactForm.resizePop();
+ });
+ jQuery(window).resize(function() {
+ setTimeout(function () { FB.ContactForm.resizePop(); }, 50);
+ });
+</script>
+<style>
+ /* Reset */
+ html { height: 100%; }
+ body, div, ul, ol, li, h1, h2, h3, h4, h5, h6, form, fieldset, legend, input, button, textarea, p, blockquote, th, td { margin: 0; padding: 0; }
+ body { background: #F9F9F9; font-family:"Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif; font-size:12px; color: #333; line-height:1.5em; height: 100%; width: 100%; padding-bottom: 20px !important; }
+ a { color: #21759B; text-decoration: none; }
+ a:hover { text-decoration: underline; text-shadow: none !important; }
+ h1 { font-size: 21px; color:#5A5A5A; font-family:Georgia,"Times New Roman",Times,serif; font-weight:normal; margin-bottom: 21px; }
+ h3 { font-size: 13px; color: #666; margin-bottom: 18px; }
+ input { width: 301px; }
+ input[type='text'] { padding: 3px 5px; margin-right: 4px; -moz-border-radius:3px; border-radius:3px; -webkit-border-radius:3px; }
+ input[type='text']:focus { border: 2px solid #80B8D9; outline: 0 !important; }
+ input[type='checkbox'], input[type='radio'] { width: auto !important; float: left; margin-top: 3px; margin-right: 8px; }
+ input.fieldError, select.fieldError, textarea.fieldError { border: 2px solid #D56F55; }
+ img { border: none; }
+ label { color: #222; font-weight: bold; display: block; margin-bottom: 4px; }
+ label.radio { width: auto; margin: -2px 0 0 5px; }
+ label span.label-required { color: #AAA; margin-left: 4px; font-weight: normal; }
+ td { vertical-align: top; }
+ select { width: 300px; }
+ textarea { height: 100px; width: 311px; }
+ /* Core */
+ #media-upload-header { border-bottom: 1px solid #DFDFDF; font-weight:bold; margin:0; padding:3px 5px 0 5px; position:relative; background: #FFF; }
+ #sidemenu { bottom:-1px; font-size:12px; list-style:none outside none; padding-left:10px; position:relative; left:0; margin:0 5px; overflow:hidden; }
+ #sidemenu a { text-decoration:none; border-top: 1px solid #FFF; display:block; float:left; line-height:28px; padding:0 13px; outline: none; }
+ #sidemenu a.current { background-color:#F9F9F9; border-color:#DFDFDF #DFDFDF #F9F9F9; color:#D54E21; -moz-border-radius:4px 4px 0 0; border-radius:4px 4px 0 0; -webkit-border-radius:4px 4px 0 0; border-style:solid; border-width:1px; font-weight:normal; }
+ #sidemenu li { display:inline; margin-bottom:6px; line-height:200%; list-style:none outside none; margin:0; padding:0; text-align:center; white-space:nowrap; }
+ .button { background-color:#f2f2f2; border-color:#BBBBBB; min-width:80px; text-align:center; color:#464646; text-shadow:0 1px 0 #FFFFFF; border-style:solid; border-width:1px; cursor:pointer; width: auto; font-size:11px !important; line-height:13px; padding:3px 11px; margin-top: 12px; text-decoration:none; -moz-border-radius:11px; border-radius:11px; -webkit-border-radius:11px }
+ .button-primary { background-color:#21759B; font-weight: bold; border-color:#298CBA; text-align:center; color:#EAF2FA; text-shadow:0 -1px 0 rgba(0, 0, 0, 0.3); border-style:solid; border-width:1px; cursor:pointer; width: auto; font-size:11px !important; line-height:13px; padding:3px 11px; margin-top: 21px; text-decoration:none; -moz-border-radius:11px; border-radius:11px; -webkit-border-radius:11px }
+ .clear { clear: both; }
+ .fb-add-field { padding-left: 10px; }
+ .fb-add-option { margin: 0 0 14px 100px; }
+ .fb-container { margin: 21px; padding-bottom: 20px; }
+ .fb-desc, #fb-add-field { margin-top: 34px; }
+ .fb-extra-fields { margin-bottom: 2px; }
+ .fb-form-case { background: #FFF; padding: 13px; border: 1px solid #E2E2E2; width: 336px; -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px }
+ .fb-form-case a { outline: none; }
+ .fb-form-case input[type='text'], .fb-form-case textarea { background: #E1E1E1; }
+ .fb-radio-label { display: inline-block; float: left; width: 290px; }
+ .fb-new-fields { position: relative; border: 1px dashed #FFF; background: #FFF; padding: 4px 10px 10px; cursor: default; }
+ .fb-new-fields:hover { border: 1px dashed #BBDBEA; background: #F7FBFD; }
+ .fb-options { width: 170px !important; }
+ .fb-remove { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-field.gif') no-repeat; position: absolute; cursor: pointer !important; right: -26px; top: 27px; width: 20px; height: 23px; }
+ .fb-remove:hover { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-field-hover.gif') no-repeat; }
+ .fb-remove-small { top: 2px !important; }
+ .fb-remove-option { position: absolute; top: 1px; right: 10px; width: 20px; height: 23px; background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-option.gif') no-repeat; }
+ .fb-remove-option:hover { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-option-hover.gif') no-repeat; }
+ .fb-reorder { cursor: move; position: relative; }
+ .fb-reorder:hover div { display: block !important; width: 130px !important; position: absolute; top: 0; right: 0; z-index: 200; padding: 5px 10px; color: #555; font-size: 11px; background: #FFF; border: 1px solid #CCC; -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px; }
+ .fb-right { position: absolute; right: 0; top: 0; width: 315px; margin: 57px 21px 0 0; }
+ .fb-right .fb-new-fields { border: none; background: #F9F9F9; padding: 0; }
+ .fb-right input[type='text'] { width: 195px; margin-bottom: 14px; }
+ .fb-right label { color: #444; width: 100px; float: left; font-weight: normal; }
+ .fb-right select { width: 195px !important; margin-bottom: 14px; }
+ .fb-right textarea { margin-bottom: 13px; }
+ .fb-right p { color: #999; line-height: 19px; }
+ .fb-settings input[type='text'], .fb-settings textarea { background-image: none !important; }
+ .fb-success { position: absolute; top: -3px; right: 100px; padding: 6px 23px 4px 23px; background: #FFFFE0; font-weight: normal; border: 1px solid #E6DB55; color: #333; -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px; }
+ .right { float: right; }
+ /* rtl */
+ body.rtl{ direction: rtl; font-family:Tahoma,Arial,sans-serif}
+ .rtl input[type='text'] { margin-left: 4px; margin-right: 0; }
+ .rtl input[type='checkbox'], .rtl input[type='radio'] { float: right; }
+ .rtl input[type='radio'] { margin-left: 8px; margin-right: 0; }
+ .rtl label.radio { margin: -2px 5px 0 0; }
+ .rtl label span.label-required { margin-right: 4px; margin-left:0 }
+ .rtl #sidemenu { padding-right:10px; padding-left: 0; left:auto; right: 0; }
+ .rtl #sidemenu a { float:right; }
+ .rtl .fb-add-field { padding-right: 10px; padding-left: 0; }
+ .rtl .fb-add-option { margin: 0 100px 14px 0; }
+ .rtl .fb-radio-label { margin-right: 8px; margin-left: 0; float: right; }
+ .rtl .fb-remove { right: auto; left: -26px; transform: scaleX(-1); }
+ .rtl .fb-remove-option { right: auto; left: 10px; }
+ .rtl .fb-reorder:hover div { left: 0; right: auto; }
+ .rtl .fb-right { left: 0; right: auto; margin: 57px 0 0 21px; }
+ .rtl .fb-right label { float: right; }
+ .rtl .fb-success { right: auto; left: 100px;}
+ .rtl .right { float: left; }
+ @media only screen and (min--moz-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-device-pixel-ratio: 1.5) {
+ .fb-remove { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-field-2x.png') no-repeat; background-size: 20px 23px; }
+ .fb-remove:hover { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-field-hover-2x.png') no-repeat; background-size: 20px 23px; }
+ .fb-remove-option { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-option-2x.png') no-repeat; background-size: 20px 23px; }
+ .fb-remove-option:hover { background: url('<?php echo GRUNION_PLUGIN_URL; ?>/images/grunion-remove-option-hover-2x.png') no-repeat; background-size: 20px 23px; }
+ }
+</style>
+</head>
+
+<body
+<?php
+if ( is_rtl() ) {
+ echo 'class="rtl"'; }
+?>
+>
+ <div id="media-upload-header">
+ <div id="fb-success" class="fb-success" style="display: none;"><?php esc_html_e( 'Your new field was saved successfully', 'jetpack' ); ?></div>
+ <ul id="sidemenu">
+ <li id="tab-preview"><a class="current" href=""><?php esc_html_e( 'Form builder', 'jetpack' ); ?></a></li>
+ <li id="tab-settings"><a href=""><?php esc_html_e( 'Email notifications', 'jetpack' ); ?></a></li>
+ </ul>
+ </div>
+ <div class="fb-right">
+ <div id="fb-desc" class="fb-desc">
+ <h3><?php esc_html_e( 'How does this work?', 'jetpack' ); ?></h3>
+ <p><?php esc_html_e( 'By adding a contact form, your readers will be able to submit feedback to you. All feedback is automatically scanned for spam, and the legitimate feedback will be emailed to you.', 'jetpack' ); ?></p>
+ <h3 style="margin-top: 21px;"><?php esc_html_e( 'Can I add more fields?', 'jetpack' ); ?></h3>
+ <p>
+ <?php
+ printf(
+ esc_html( _x( 'Sure thing. %1$s to add a new text box, textarea, radio, checkbox, or dropdown field.', '%1$s = "Click here" in an HTML link', 'jetpack' ) ),
+ '<a href="#" class="fb-add-field" style="padding-left: 0;">' . esc_html__( 'Click here', 'jetpack' ) . '</a>'
+ );
+ ?>
+ </p>
+ <h3 style="margin-top: 21px;"><?php esc_html_e( 'Can I view my feedback within WordPress?', 'jetpack' ); ?></h3>
+ <p>
+ <?php
+ printf(
+ esc_html( _x( 'Yep, you can read your feedback at any time by clicking the "%1$s" link in the admin menu.', '%1$s = "Feedback" in an HTML link', 'jetpack' ) ),
+ '<a id="fb-feedback" href="' . admin_url( 'edit.php?post_type=feedback' ) . '">' . esc_html__( 'Feedback', 'jetpack' ) . '</a>'
+ );
+ ?>
+ </p>
+ <div class="clear"></div>
+ </div>
+ <div id="fb-email-desc" class="fb-desc" style="display: none;">
+ <h3><?php esc_html_e( 'Do I need to fill this out?', 'jetpack' ); ?></h3>
+ <p><?php esc_html_e( 'Nope. However, if you&#8217;d like to modify where your feedback is sent, or the subject line you can. If you don&#8217;t make any changes here, feedback will be sent to the author of the page/post and the subject will be the name of this page/post.', 'jetpack' ); ?></p>
+ <h3 style="margin-top: 21px;"><?php esc_html_e( 'Can I send a notification to more than one person?', 'jetpack' ); ?></h3>
+ <p><?php esc_html_e( 'Yep. You can enter multiple email addresses in the Email address field, and separate them with commas. A notification email will then be sent to each email address.', 'jetpack' ); ?></p>
+ <div class="clear"></div>
+ </div>
+ <div id="fb-add-field" style="display: none;">
+ <h3><?php esc_html_e( 'Edit this new field', 'jetpack' ); ?></h3>
+
+ <label for="fb-new-label"><?php esc_html_e( 'Label', 'jetpack' ); ?></label>
+ <input type="text" id="fb-new-label" value="<?php esc_attr_e( 'New field', 'jetpack' ); ?>" />
+
+ <label for="fb-new-label"><?php esc_html_e( 'Field type', 'jetpack' ); ?></label>
+ <select id="fb-new-type">
+ <option value="checkbox"><?php esc_html_e( 'Checkbox', 'jetpack' ); ?></option>
+ <option value="checkbox-multiple"><?php esc_html_e( 'Checkbox with Multiple Items', 'jetpack' ); ?></option>
+ <option value="select"><?php esc_html_e( 'Drop down', 'jetpack' ); ?></option>
+ <option value="email"><?php esc_html_e( 'Email', 'jetpack' ); ?></option>
+ <option value="name"><?php esc_html_e( 'Name', 'jetpack' ); ?></option>
+ <option value="radio"><?php esc_html_e( 'Radio', 'jetpack' ); ?></option>
+ <option value="text" selected="selected"><?php esc_html_e( 'Text', 'jetpack' ); ?></option>
+ <option value="textarea"><?php esc_html_e( 'Textarea', 'jetpack' ); ?></option>
+ <option value="url"><?php esc_html_e( 'Website', 'jetpack' ); ?></option>
+ </select>
+ <div class="clear"></div>
+
+ <div id="fb-options" style="display: none;">
+ <div id="fb-new-options">
+ <label for="fb-option0"><?php esc_html_e( 'Options', 'jetpack' ); ?></label>
+ <input type="text" id="fb-option0" optionid="0" value="<?php esc_attr_e( 'First option', 'jetpack' ); ?>" class="fb-options" />
+ </div>
+ <div id="fb-add-option" class="fb-add-option">
+ <a href="#" id="fb-another-option"><?php esc_html_e( 'Add another option', 'jetpack' ); ?></a>
+ </div>
+ </div>
+
+ <div class="fb-required">
+ <label for="fb-new-label"></label>
+ <input type="checkbox" id="fb-new-required" />
+ <label for="fb-new-label" class="fb-radio-label"><?php esc_html_e( 'Required?', 'jetpack' ); ?></label>
+ <div class="clear"></div>
+ </div>
+
+ <input type="hidden" id="fb-field-id" />
+ <input type="submit" class="button" value="<?php esc_attr_e( 'Save this field', 'jetpack' ); ?>" id="fb-save-field" name="save">
+ </div>
+ </div>
+ <form id="fb-preview">
+ <div id="fb-preview-form" class="fb-container">
+ <h1><?php esc_html_e( 'Here&#8217;s what your form will look like', 'jetpack' ); ?></h1>
+ <div id="sortable" class="fb-form-case">
+
+ <div id="fb-extra-fields" class="fb-extra-fields"></div>
+
+ <a href="#" id="fb-new-field" class="fb-add-field"><?php esc_html_e( 'Add a new field', 'jetpack' ); ?></a>
+ </div>
+ <input type="submit" class="button-primary" tabindex="4" value="<?php esc_attr_e( 'Add this form to my post', 'jetpack' ); ?>" id="fb-save-form" name="save">
+ </div>
+ <div id="fb-email-settings" class="fb-container" style="display: none;">
+ <h1><?php esc_html_e( 'Email settings', 'jetpack' ); ?></h1>
+ <div class="fb-form-case fb-settings">
+ <label for="fb-fieldname"><?php esc_html_e( 'Enter your email address', 'jetpack' ); ?></label>
+ <input type="text" id="fb-field-my-email" style="background: #FFF !important;" />
+
+ <label for="fb-fieldemail" style="margin-top: 14px;"><?php esc_html_e( 'What should the subject line be?', 'jetpack' ); ?></label>
+ <input type="text" id="fb-field-subject" style="background: #FFF !important;" />
+ </div>
+ <input type="submit" class="button-primary" value="<?php esc_attr_e( 'Save and go back to form builder', 'jetpack' ); ?>" id="fb-prev-form" name="save">
+ </div>
+ </form>
+</body>
+</html>
diff --git a/plugins/jetpack/modules/contact-form/images/blank-screen-akismet.png b/plugins/jetpack/modules/contact-form/images/blank-screen-akismet.png
new file mode 100644
index 00000000..a4ba1e2d
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/blank-screen-akismet.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/blank-screen-button.png b/plugins/jetpack/modules/contact-form/images/blank-screen-button.png
new file mode 100644
index 00000000..58dfa26b
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/blank-screen-button.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-form-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-form-2x.png
new file mode 100644
index 00000000..824d85a3
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-form-2x.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-form.png b/plugins/jetpack/modules/contact-form/images/grunion-form.png
new file mode 100644
index 00000000..f4a0cc1d
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-form.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png
new file mode 100644
index 00000000..bfbca5ed
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-2x.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png
new file mode 100644
index 00000000..dca39da9
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover-2x.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover.gif b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover.gif
new file mode 100644
index 00000000..20d9e712
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field-hover.gif
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-field.gif b/plugins/jetpack/modules/contact-form/images/grunion-remove-field.gif
new file mode 100644
index 00000000..55062664
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-field.gif
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-option-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-2x.png
new file mode 100644
index 00000000..4272442c
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-2x.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover-2x.png b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover-2x.png
new file mode 100644
index 00000000..210de1b3
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover-2x.png
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover.gif b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover.gif
new file mode 100644
index 00000000..9098b065
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-option-hover.gif
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/images/grunion-remove-option.gif b/plugins/jetpack/modules/contact-form/images/grunion-remove-option.gif
new file mode 100644
index 00000000..ec491663
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/images/grunion-remove-option.gif
Binary files differ
diff --git a/plugins/jetpack/modules/contact-form/js/editor-view.js b/plugins/jetpack/modules/contact-form/js/editor-view.js
new file mode 100644
index 00000000..b62d92f8
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/js/editor-view.js
@@ -0,0 +1,284 @@
+/* global grunionEditorView, tinyMCE, QTags, wp */
+( function( $, wp, grunionEditorView ) {
+ wp.mce = wp.mce || {};
+ if ( 'undefined' === typeof wp.mce.views ) {
+ return;
+ }
+
+ wp.mce.grunion_wp_view_renderer = {
+ shortcode_string: 'contact-form',
+ template: wp.template( 'grunion-contact-form' ),
+ field_templates: {
+ email: wp.template( 'grunion-field-email' ),
+ telephone: wp.template( 'grunion-field-telephone' ),
+ textarea: wp.template( 'grunion-field-textarea' ),
+ radio: wp.template( 'grunion-field-radio' ),
+ checkbox: wp.template( 'grunion-field-checkbox' ),
+ 'checkbox-multiple': wp.template( 'grunion-field-checkbox-multiple' ),
+ select: wp.template( 'grunion-field-select' ),
+ date: wp.template( 'grunion-field-date' ),
+ text: wp.template( 'grunion-field-text' ),
+ name: wp.template( 'grunion-field-text' ),
+ url: wp.template( 'grunion-field-url' ),
+ },
+ edit_template: wp.template( 'grunion-field-edit' ),
+ editor_inline: wp.template( 'grunion-editor-inline' ),
+ editor_option: wp.template( 'grunion-field-edit-option' ),
+ getContent: function() {
+ var content = this.shortcode.content,
+ index = 0,
+ field,
+ named,
+ body = '';
+
+ // If it's the legacy `[contact-form /]` syntax, populate default fields.
+ if ( ! content ) {
+ content = grunionEditorView.default_form;
+ }
+
+ // Render the fields.
+ while ( ( field = wp.shortcode.next( 'contact-field', content, index ) ) ) {
+ index = field.index + field.content.length;
+ named = field.shortcode.attrs.named;
+ if ( ! named.type || ! this.field_templates[ named.type ] ) {
+ named.type = 'text';
+ }
+ if ( named.required ) {
+ named.required = grunionEditorView.labels.required_field_text;
+ }
+ if ( named.options && 'string' === typeof named.options ) {
+ named.options = named.options.split( ',' );
+ }
+ body += this.field_templates[ named.type ]( named );
+ }
+
+ var options = {
+ body: body,
+ submit_button_text: grunionEditorView.labels.submit_button_text,
+ };
+
+ return this.template( options );
+ },
+ edit: function( data, update_callback ) {
+ var shortcode_data = wp.shortcode.next( this.shortcode_string, data ),
+ shortcode = shortcode_data.shortcode,
+ $tinyMCE_document = $( tinyMCE.activeEditor.getDoc() ),
+ $view = $tinyMCE_document.find( '.wpview.wpview-wrap' ).filter( function() {
+ return $( this ).attr( 'data-mce-selected' );
+ } ),
+ $editframe = $( '<iframe scrolling="no" class="inline-edit-contact-form" />' ),
+ index = 0,
+ named,
+ fields = '',
+ field;
+
+ if ( ! shortcode.content ) {
+ shortcode.content = grunionEditorView.default_form;
+ }
+
+ // Render the fields.
+ while ( ( field = wp.shortcode.next( 'contact-field', shortcode.content, index ) ) ) {
+ index = field.index + field.content.length;
+ named = field.shortcode.attrs.named;
+ if ( named.options && 'string' === typeof named.options ) {
+ named.options = named.options.split( ',' );
+ }
+ fields += this.edit_template( named );
+ }
+
+ $editframe.on( 'checkheight', function() {
+ var innerDoc = this.contentDocument ? this.contentDocument : this.contentWindow.document;
+ this.style.height = '10px';
+ this.style.height = 5 + innerDoc.body.scrollHeight + 'px';
+ tinyMCE.activeEditor.execCommand( 'wpAutoResize' );
+ } );
+
+ $editframe.on( 'load', function() {
+ var stylesheet_url =
+ 1 === window.isRtl
+ ? grunionEditorView.inline_editing_style_rtl
+ : grunionEditorView.inline_editing_style,
+ $stylesheet = $( '<link rel="stylesheet" href="' + stylesheet_url + '" />' ),
+ $dashicons_css = $(
+ '<link rel="stylesheet" href="' + grunionEditorView.dashicons_css_url + '" />'
+ );
+
+ $stylesheet.on( 'load', function() {
+ $editframe
+ .contents()
+ .find( 'body' )
+ .css( 'visibility', 'visible' );
+ $editframe.trigger( 'checkheight' );
+ } );
+ $editframe
+ .contents()
+ .find( 'head' )
+ .append( $stylesheet )
+ .append( $dashicons_css );
+
+ $editframe
+ .contents()
+ .find( 'body' )
+ .html(
+ wp.mce.grunion_wp_view_renderer.editor_inline( {
+ to: shortcode.attrs.named.to,
+ subject: shortcode.attrs.named.subject,
+ fields: fields,
+ } )
+ )
+ .css( 'visibility', 'hidden' );
+
+ $editframe
+ .contents()
+ .find( 'input:first' )
+ .focus();
+
+ setTimeout( function() {
+ $editframe.trigger( 'checkheight' );
+ }, 250 );
+
+ // Add a second timeout for super long forms racing, and to not slow it down for shorter forms unnecessarily.
+ setTimeout( function() {
+ $editframe.trigger( 'checkheight' );
+ }, 500 );
+
+ var $editfields = $editframe.contents().find( '.grunion-fields' ),
+ $buttons = $editframe.contents().find( '.grunion-controls' );
+
+ $editfields.sortable();
+
+ // Now, add all the listeners!
+
+ $editfields.on( 'change select', 'select[name=type]', function() {
+ $( this ).closest( '.grunion-field-edit' )[ 0 ].className =
+ 'card is-compact grunion-field-edit grunion-field-' + $( this ).val();
+ $editframe.trigger( 'checkheight' );
+ } );
+
+ $editfields.on( 'click', '.delete-option', function( e ) {
+ e.preventDefault();
+ $( this )
+ .closest( 'li' )
+ .remove();
+ $editframe.trigger( 'checkheight' );
+ } );
+
+ $editfields.on( 'click', '.add-option', function( e ) {
+ var $new_option = $( wp.mce.grunion_wp_view_renderer.editor_option() );
+ e.preventDefault();
+ $( this )
+ .closest( 'li' )
+ .before( $new_option );
+ $editframe.trigger( 'checkheight' );
+ $new_option.find( 'input:first' ).focus();
+ } );
+
+ $editfields.on( 'click', '.delete-field', function( e ) {
+ e.preventDefault();
+ $( this )
+ .closest( '.card' )
+ .remove();
+ $editframe.trigger( 'checkheight' );
+ } );
+
+ $buttons.find( 'input[name=submit]' ).on( 'click', function() {
+ var new_data = shortcode;
+
+ new_data.type = 'closed';
+ new_data.attrs = {};
+ new_data.content = '';
+
+ $editfields.children().each( function() {
+ var field_shortcode = {
+ tag: 'contact-field',
+ type: 'single',
+ attrs: {
+ label: $( this )
+ .find( 'input[name=label]' )
+ .val(),
+ type: $( this )
+ .find( 'select[name=type]' )
+ .val(),
+ },
+ },
+ options = [];
+
+ if ( $( this ).find( 'input[name=required]:checked' ).length ) {
+ field_shortcode.attrs.required = '1';
+ }
+
+ $( this )
+ .find( 'input[name=option]' )
+ .each( function() {
+ if ( $( this ).val() ) {
+ options.push( $( this ).val() );
+ }
+ } );
+ if ( options.length ) {
+ field_shortcode.attrs.options = options.join( ',' );
+ }
+
+ new_data.content += wp.shortcode.string( field_shortcode );
+ } );
+
+ if (
+ $editframe
+ .contents()
+ .find( 'input[name=to]' )
+ .val()
+ ) {
+ new_data.attrs.to = $editframe
+ .contents()
+ .find( 'input[name=to]' )
+ .val();
+ }
+ if (
+ $editframe
+ .contents()
+ .find( 'input[name=subject]' )
+ .val()
+ ) {
+ new_data.attrs.subject = $editframe
+ .contents()
+ .find( 'input[name=subject]' )
+ .val();
+ }
+
+ update_callback( wp.shortcode.string( new_data ) );
+ } );
+
+ $buttons.find( 'input[name=cancel]' ).on( 'click', function() {
+ update_callback( wp.shortcode.string( shortcode ) );
+ } );
+
+ $buttons.find( 'input[name=add-field]' ).on( 'click', function() {
+ var $new_field = $( wp.mce.grunion_wp_view_renderer.edit_template( {} ) );
+ $editfields.append( $new_field );
+ $editfields.sortable( 'refresh' );
+ $editframe.trigger( 'checkheight' );
+ $new_field.find( 'input:first' ).focus();
+ } );
+ } );
+
+ $view.html( $editframe );
+ },
+ };
+ wp.mce.views.register( 'contact-form', wp.mce.grunion_wp_view_renderer );
+
+ // Add the 'text' editor button.
+ QTags.addButton( 'grunion_shortcode', grunionEditorView.labels.quicktags_label, function() {
+ QTags.insertContent( '[contact-form]' + grunionEditorView.default_form + '[/contact-form]' );
+ } );
+
+ var $wp_content_wrap = $( '#wp-content-wrap' );
+ $( '#insert-jetpack-contact-form' ).on( 'click', function( e ) {
+ e.preventDefault();
+ if ( $wp_content_wrap.hasClass( 'tmce-active' ) ) {
+ tinyMCE.execCommand( 'grunion_add_form' );
+ } else if ( $wp_content_wrap.hasClass( 'html-active' ) ) {
+ QTags.insertContent( '[contact-form]' + grunionEditorView.default_form + '[/contact-form]' );
+ } else {
+ window.console.error( 'Neither TinyMCE nor QuickTags is active. Unable to insert form.' );
+ }
+ } );
+} )( jQuery, wp, grunionEditorView );
diff --git a/plugins/jetpack/modules/contact-form/js/grunion-admin.js b/plugins/jetpack/modules/contact-form/js/grunion-admin.js
new file mode 100644
index 00000000..100fb22b
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/js/grunion-admin.js
@@ -0,0 +1,30 @@
+/* global ajaxurl */
+jQuery( function( $ ) {
+ $( document ).on( 'click', '#jetpack-check-feedback-spam:not(.button-disabled)', function( e ) {
+ e.preventDefault();
+
+ $( '#jetpack-check-feedback-spam:not(.button-disabled)' ).addClass( 'button-disabled' );
+ $( '.jetpack-check-feedback-spam-spinner' )
+ .addClass( 'spinner' )
+ .show();
+ grunion_check_for_spam( 0, 100 );
+ } );
+
+ function grunion_check_for_spam( offset, limit ) {
+ $.post(
+ ajaxurl,
+ {
+ action: 'grunion_recheck_queue',
+ offset: offset,
+ limit: limit,
+ },
+ function( result ) {
+ if ( result.processed < limit ) {
+ window.location.reload();
+ } else {
+ grunion_check_for_spam( offset + limit, limit );
+ }
+ }
+ );
+ }
+} );
diff --git a/plugins/jetpack/modules/contact-form/js/grunion-frontend.js b/plugins/jetpack/modules/contact-form/js/grunion-frontend.js
new file mode 100644
index 00000000..1c1819a5
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/js/grunion-frontend.js
@@ -0,0 +1,3 @@
+jQuery( function( $ ) {
+ $( '.contact-form input.jp-contact-form-date' ).datepicker();
+} );
diff --git a/plugins/jetpack/modules/contact-form/js/grunion.js b/plugins/jetpack/modules/contact-form/js/grunion.js
new file mode 100644
index 00000000..e34fc135
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/js/grunion.js
@@ -0,0 +1,1180 @@
+/* jshint onevar: false, devel: true, smarttabs: true */
+/* global GrunionFB_i18n: true, FB, ajax_nonce_shortcode, ajax_nonce_json, ajaxurl, postId */
+
+if ( ! window.FB ) {
+ window.FB = {};
+}
+
+GrunionFB_i18n = jQuery.extend(
+ {
+ nameLabel: 'Name',
+ emailLabel: 'Email',
+ urlLabel: 'Website',
+ commentLabel: 'Comment',
+ newLabel: 'New Field',
+ optionsLabel: 'Options',
+ optionLabel: 'Option',
+ firstOptionLabel: 'First option',
+ problemGeneratingForm:
+ "Oops, there was a problem generating your form. You'll likely need to try again.",
+ moveInstructions: 'Drag up or down\nto re-arrange',
+ moveLabel: 'move',
+ editLabel: 'edit',
+ savedMessage: 'Saved successfully',
+ requiredLabel: '(required)',
+ exitConfirmMessage:
+ 'Are you sure you want to exit the form editor without saving? Any changes you have made will be lost.',
+ maxNewFields: 5,
+ invalidEmail: ' is an invalid email address.',
+ },
+ GrunionFB_i18n
+);
+
+GrunionFB_i18n.moveInstructions = GrunionFB_i18n.moveInstructions.replace( '\n', '<br />' );
+
+FB.span = jQuery( '<span>' );
+FB.esc_html = function( string ) {
+ return FB.span.text( string ).html();
+};
+
+FB.esc_attr = function( string ) {
+ string = FB.esc_html( string );
+ return string.replace( '"', '&quot;' ).replace( "'", '&#039;' );
+};
+
+FB.ContactForm = ( function() {
+ var fbForm = {
+ // Main object that generated shortcode via AJAX call
+ action: 'grunion_shortcode',
+ _ajax_nonce: ajax_nonce_shortcode,
+ to: '',
+ subject: '',
+ fields: {},
+ };
+ var defaultFields = {
+ name: {
+ label: GrunionFB_i18n.nameLabel,
+ type: 'name',
+ required: true,
+ options: [],
+ order: '1',
+ },
+ email: {
+ label: GrunionFB_i18n.emailLabel,
+ type: 'email',
+ required: true,
+ options: [],
+ order: '2',
+ },
+ url: {
+ label: GrunionFB_i18n.urlLabel,
+ type: 'url',
+ required: false,
+ options: [],
+ order: '3',
+ },
+ comment: {
+ label: GrunionFB_i18n.commentLabel,
+ type: 'textarea',
+ required: true,
+ options: [],
+ order: '4',
+ },
+ };
+ var debug = false; // will print errors to log if true
+ var grunionNewCount = 0; // increment for new fields
+ var maxNewFields = GrunionFB_i18n.maxNewFields; // See filter in ../grunion-form-view.php
+ var optionsCache = {};
+ var optionsCount = 0; // increment for options
+ var shortcode;
+
+ function addField() {
+ try {
+ grunionNewCount++;
+ if ( grunionNewCount <= maxNewFields ) {
+ // Add to preview
+ jQuery( '#fb-extra-fields' ).append(
+ '<div id="fb-new-field' +
+ grunionNewCount +
+ '" fieldid="' +
+ grunionNewCount +
+ '" class="fb-new-fields"><div class="fb-fields"><div id="' +
+ grunionNewCount +
+ '" class="fb-remove"></div><label fieldid="' +
+ grunionNewCount +
+ '" for="fb-field' +
+ grunionNewCount +
+ '"><span class="label-text">' +
+ GrunionFB_i18n.newLabel +
+ '</span> </label><input type="text" id="fb-field' +
+ grunionNewCount +
+ '" disabled="disabled" /></div></div>'
+ );
+ // Add to form object
+ fbForm.fields[ grunionNewCount ] = {
+ label: GrunionFB_i18n.newLabel,
+ type: 'text',
+ required: false,
+ options: [],
+ order: '5',
+ };
+ if ( grunionNewCount === maxNewFields ) {
+ jQuery( '#fb-new-field' ).hide();
+ }
+ // Reset form for this new field
+ optionsCount = 0;
+ optionsCache = {};
+ jQuery( '#fb-new-options' ).html(
+ '<label for="fb-option0">' +
+ GrunionFB_i18n.optionsLabel +
+ '</label><input type="text" id="fb-option0" optionid="0" value="' +
+ GrunionFB_i18n.firstOptionLabel +
+ '" class="fb-options" />'
+ );
+ jQuery( '#fb-options' ).hide();
+ jQuery( '#fb-new-label' ).val( GrunionFB_i18n.newLabel );
+ jQuery( '#fb-new-type' ).val( 'text' );
+ jQuery( '#fb-field-id' ).val( grunionNewCount );
+ setTimeout( function() {
+ jQuery( '#fb-new-label' )
+ .focus()
+ .select();
+ }, 100 );
+ } else {
+ jQuery( '#fb-new-field' ).hide();
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'addField(): ' + e );
+ }
+ }
+ }
+ function addOption() {
+ try {
+ optionsCount = jQuery( '#fb-new-options .fb-options' ).length;
+ var thisId = jQuery( '#fb-field-id' ).val();
+ var thisType = jQuery( '#fb-new-type' ).val();
+ if ( thisType === 'radio' ) {
+ // Add to right col
+ jQuery( '#fb-new-options' ).append(
+ '<div id="fb-option-box-' +
+ optionsCount +
+ '" class="fb-new-fields"><span optionid="' +
+ optionsCount +
+ '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' +
+ optionsCount +
+ '" optionid="' +
+ optionsCount +
+ '" value="' +
+ GrunionFB_i18n.optionLabel +
+ '" class="fb-options" /><div>'
+ );
+ // Add to preview
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).append(
+ '<div id="fb-radio-' +
+ thisId +
+ '-' +
+ optionsCount +
+ '"><input type="radio" disabled="disabled" id="fb-field' +
+ thisId +
+ '" name="radio-' +
+ thisId +
+ '" /><span>' +
+ GrunionFB_i18n.optionLabel +
+ '</span><div class="clear"></div></div>'
+ );
+ } else if ( 'checkbox-multiple' === thisType ) {
+ // Add to right col
+ jQuery( '#fb-new-options' ).append(
+ '<div id="fb-option-box-' +
+ optionsCount +
+ '" class="fb-new-fields"><span optionid="' +
+ optionsCount +
+ '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' +
+ optionsCount +
+ '" optionid="' +
+ optionsCount +
+ '" value="' +
+ GrunionFB_i18n.optionLabel +
+ '" class="fb-options" /><div>'
+ );
+ // Add to preview
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).append(
+ '<div id="fb-checkbox-multiple-' +
+ thisId +
+ '-' +
+ optionsCount +
+ '"><input type="checkbox" disabled="disabled" id="fb-field' +
+ thisId +
+ '" name="checkbox-multiple-' +
+ thisId +
+ '" /><span>' +
+ GrunionFB_i18n.optionLabel +
+ '</span><div class="clear"></div></div>'
+ );
+ } else {
+ // Add to right col
+ jQuery( '#fb-new-options' ).append(
+ '<div id="fb-option-box-' +
+ optionsCount +
+ '" class="fb-new-fields"><span optionid="' +
+ optionsCount +
+ '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' +
+ optionsCount +
+ '" optionid="' +
+ optionsCount +
+ '" value="" class="fb-options" /><div>'
+ );
+ // Add to preview
+ jQuery( '#fb-field' + thisId ).append(
+ '<option id="fb-' +
+ thisId +
+ '-' +
+ optionsCount +
+ '" value="' +
+ thisId +
+ '-' +
+ optionsCount +
+ '"></option>'
+ );
+ }
+ // Add to fbForm object
+ fbForm.fields[ thisId ].options[ optionsCount ] = '';
+ // Add focus to new field
+ jQuery( '#fb-option' + optionsCount )
+ .focus()
+ .select();
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'addOption(): ' + e );
+ }
+ }
+ }
+ function buildPreview() {
+ try {
+ if ( fbForm.to ) {
+ jQuery( '#fb-field-my-email' ).val( fbForm.to );
+ }
+ if ( fbForm.subject ) {
+ jQuery( '#fb-field-subject' ).val( fbForm.subject );
+ }
+ // Loop over and add fields
+ jQuery.each( fbForm.fields, function( index, value ) {
+ jQuery( '#fb-extra-fields' ).before(
+ '<div class="fb-new-fields ui-state-default" fieldid="' +
+ index +
+ '" id="fb-new-field' +
+ index +
+ '"><div class="fb-fields"></div></div>'
+ );
+ jQuery( '#fb-field-id' ).val( index );
+ optionsCache[ index ] = {};
+ optionsCache[ index ].options = [];
+ if (
+ 'radio' === value.type ||
+ 'select' === value.type ||
+ 'checkbox-multiple' === value.type
+ ) {
+ jQuery.each( value.options, function( i, value ) {
+ optionsCache[ index ].options[ i ] = value;
+ } );
+ }
+ updateType( value.type, value.label, value.required );
+ } );
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'buildPreview(): ' + e );
+ }
+ }
+ }
+ function customOptions( id, thisType ) {
+ try {
+ var thisOptions = '';
+ for ( var i = 0; i < optionsCache[ id ].options.length; i++ ) {
+ if ( optionsCache[ id ].options[ i ] !== undefined ) {
+ if ( thisType === 'radio' ) {
+ thisOptions =
+ thisOptions +
+ '<div id="fb-radio-' +
+ id +
+ '-' +
+ i +
+ '"><input type="radio" id="fb-field' +
+ id +
+ '" name="radio-' +
+ id +
+ '" /><span>' +
+ FB.esc_html( optionsCache[ id ].options[ i ] ) +
+ '</span><div class="clear"></div></div>';
+ } else if ( 'checkbox-multiple' === thisType ) {
+ thisOptions =
+ thisOptions +
+ '<div id="fb-checkbox-multiple-' +
+ id +
+ '-' +
+ i +
+ '"><input type="checkbox" id="fb-field' +
+ id +
+ '" name="checkbox-multiple-' +
+ id +
+ '" /><span>' +
+ FB.esc_html( optionsCache[ id ].options[ i ] ) +
+ '</span><div class="clear"></div></div>';
+ } else {
+ thisOptions =
+ thisOptions +
+ '<option id="fb-' +
+ id +
+ '-' +
+ i +
+ '" value="' +
+ id +
+ '-' +
+ i +
+ '">' +
+ FB.esc_html( optionsCache[ id ].options[ i ] ) +
+ '</option>';
+ }
+ }
+ }
+ return thisOptions;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'customOptions(): ' + e );
+ }
+ }
+ }
+ function deleteField( that ) {
+ try {
+ grunionNewCount--;
+ var thisId = that.attr( 'id' );
+ delete fbForm.fields[ thisId ];
+ jQuery( '#' + thisId )
+ .parent()
+ .parent()
+ .remove();
+ if ( grunionNewCount <= maxNewFields ) {
+ jQuery( '#fb-new-field' ).show();
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'deleteField(): ' + e );
+ }
+ }
+ }
+ function editField( that ) {
+ try {
+ scroll( 0, 0 );
+ setTimeout( function() {
+ jQuery( '#fb-new-label' )
+ .focus()
+ .select();
+ }, 100 );
+ var thisId = that.parent().attr( 'fieldid' );
+ loadFieldEditor( thisId );
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'editField(): ' + e );
+ }
+ }
+ }
+ function grabShortcode() {
+ try {
+ // Takes fbForm object and returns shortcode syntax
+ jQuery.post( ajaxurl, fbForm, function( response ) {
+ shortcode = response;
+ } );
+ } catch ( e ) {
+ alert( GrunionFB_i18n.problemGeneratingForm );
+ if ( debug ) {
+ console.log( 'grabShortcode(): ' + e );
+ }
+ }
+ }
+ function hideDesc() {
+ jQuery( '#fb-desc' ).hide();
+ jQuery( '#fb-add-field' ).show();
+ }
+ function hidePopup() {
+ try {
+ // copied from wp-includes/js/thickbox/thickbox.js
+ jQuery( '#TB_imageOff', window.parent.document ).unbind( 'click' );
+ jQuery( '#TB_closeWindowButton', window.parent.document ).unbind( 'click' );
+ jQuery( '#TB_window', window.parent.document ).fadeOut( 'fast' );
+ jQuery( 'body', window.parent.document ).removeClass( 'modal-open' );
+ jQuery( '#TB_window,#TB_overlay,#TB_HideSelect', window.parent.document )
+ .trigger( 'unload' )
+ .unbind()
+ .remove();
+ jQuery( '#TB_load', window.parent.document ).remove();
+ if ( typeof window.parent.document.body.style.maxHeight === 'undefined' ) {
+ //if IE 6
+ jQuery( 'body', 'html', window.parent.document ).css( { height: 'auto', width: 'auto' } );
+ jQuery( 'html', window.parent.document ).css( 'overflow', '' );
+ }
+ window.parent.document.onkeydown = '';
+ window.parent.document.onkeyup = '';
+ return false;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'hidePopup(): ' + e );
+ }
+ }
+ }
+ function hideShowEditLink( whichType, that ) {
+ try {
+ if ( whichType === 'show' ) {
+ // Prevents showing links twice
+ if ( jQuery( '.fb-edit-field' ).is( ':visible' ) ) {
+ jQuery( '.fb-edit-field' ).remove();
+ }
+ that
+ .find( 'label' )
+ .prepend(
+ '<span class="right fb-edit-field" style="font-weight: normal;"><a href="" class="fb-reorder"><div style="display: none;">' +
+ GrunionFB_i18n.moveInstructions +
+ '</div>' +
+ GrunionFB_i18n.moveLabel +
+ '</a>&nbsp;&nbsp;<span style="color: #C7D8DE;">|</span>&nbsp;&nbsp;<a href="" class="fb-edit">' +
+ GrunionFB_i18n.editLabel +
+ '</a></span>'
+ );
+ } else {
+ jQuery( '.fb-edit-field' ).remove();
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'hideShowEditLink(): ' + e );
+ }
+ }
+ }
+ function loadFieldEditor( id ) {
+ try {
+ var thisType = fbForm.fields[ id ].type;
+ jQuery( '#fb-options' ).hide();
+ // Reset hidden field ID
+ jQuery( '#fb-field-id' ).val( id );
+ // Load label
+ jQuery( '#fb-new-label' ).val( fbForm.fields[ id ].label );
+ // Load type
+ jQuery( '#fb-new-type' ).val( fbForm.fields[ id ].type );
+ // Load required
+ if ( fbForm.fields[ id ].required ) {
+ jQuery( '#fb-new-required' ).prop( 'checked', true );
+ } else {
+ jQuery( '#fb-new-required' ).prop( 'checked', false );
+ }
+ // Load options if there are any
+ if ( 'select' === thisType || 'radio' === thisType || 'checkbox-multiple' === thisType ) {
+ var thisOptions = fbForm.fields[ id ].options;
+ jQuery( '#fb-options' ).show();
+ jQuery( '#fb-new-options' ).html( '' ); // Clear it all out
+ for ( var i = 0; i < thisOptions.length; i++ ) {
+ if ( thisOptions[ i ] !== undefined ) {
+ if ( thisType === 'radio' ) {
+ jQuery( '#fb-new-options' ).append(
+ '<div id="fb-option-box-' +
+ i +
+ '" class="fb-new-fields"><span optionid="' +
+ i +
+ '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' +
+ i +
+ '" optionid="' +
+ i +
+ '" value="' +
+ FB.esc_attr( fbForm.fields[ id ].options[ i ] ) +
+ '" class="fb-options" /><div>'
+ );
+ } else {
+ jQuery( '#fb-new-options' ).append(
+ '<div id="fb-option-box-' +
+ i +
+ '" class="fb-new-fields"><span optionid="' +
+ i +
+ '" class="fb-remove-option"></span><label></label><input type="text" id="fb-option' +
+ i +
+ '" optionid="' +
+ i +
+ '" value="' +
+ FB.esc_attr( fbForm.fields[ id ].options[ i ] ) +
+ '" class="fb-options" /><div>'
+ );
+ }
+ }
+ }
+ }
+ // Load editor & hide description
+ hideDesc();
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'loadFieldEditor(): ' + e );
+ }
+ }
+ }
+ function parseShortcode( data ) {
+ try {
+ // Clean up fields by resetting them
+ fbForm.fields = {};
+ // Add new fields
+ if ( ! data ) {
+ fbForm.fields = defaultFields;
+ } else {
+ jQuery.each( data.fields, function( index, value ) {
+ if ( 1 === parseInt( value.required, 10 ) ) {
+ value.required = 'true';
+ }
+ fbForm.fields[ index ] = value;
+ } );
+ fbForm.to = data.to;
+ fbForm.subject = data.subject;
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'parseShortcode(): ' + e );
+ }
+ }
+ }
+ function removeOption( optionId ) {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ var thisVal = jQuery( '#fb-option' + optionId ).val();
+ var thisType = jQuery( '#fb-new-type' ).val();
+ // Remove from right
+ jQuery( '#fb-option-box-' + optionId ).remove();
+ // Remove from preview
+ if ( thisType === 'radio' ) {
+ jQuery( '#fb-radio-' + thisId + '-' + optionId ).remove();
+ } else if ( 'checkbox-multiple' === thisType ) {
+ jQuery( '#fb-checkbox-multiple-' + thisId + '-' + optionId ).remove();
+ } else {
+ jQuery( '#fb-' + thisId + '-' + optionId ).remove();
+ }
+ // Remove from fbForm object
+ var idx = fbForm.fields[ thisId ].options.indexOf( thisVal );
+ if ( idx !== -1 ) {
+ fbForm.fields[ thisId ].options.splice( idx, 1 );
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'removeOption(): ' + e );
+ }
+ }
+ }
+ function removeOptions() {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ jQuery( '#fb-options' ).hide();
+ if ( optionsCache[ thisId ] === undefined ) {
+ optionsCache[ thisId ] = {};
+ }
+ optionsCache[ thisId ].options = fbForm.fields[ thisId ].options; // Save options in case they change their mind
+ fbForm.fields[ thisId ].options = []; // Removes all options
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'removeOptions(): ' + e );
+ }
+ }
+ }
+ function sendShortcodeToEditor() {
+ try {
+ // Serialize fields
+ jQuery( 'div#sortable div.fb-new-fields' ).each( function( index ) {
+ var thisId = jQuery( this ).attr( 'fieldid' );
+ fbForm.fields[ thisId ].order = index;
+ } );
+ // Export to WYSIWYG editor
+ jQuery.post( ajaxurl, fbForm, function( response ) {
+ var isVisual = jQuery( '#edButtonPreview', window.parent.document ).hasClass( 'active' );
+ /* WP 3.3+ */
+ if ( ! isVisual ) {
+ isVisual = jQuery( '#wp-content-wrap', window.parent.document ).hasClass( 'tmce-active' );
+ }
+
+ var win = window.dialogArguments || opener || parent || top;
+ var currentCode;
+ if ( isVisual ) {
+ currentCode = win.tinyMCE.activeEditor.getContent();
+ } else {
+ currentCode = jQuery( '#editorcontainer textarea', window.parent.document ).val();
+ /* WP 3.3+ */
+ if ( typeof currentCode !== 'string' ) {
+ currentCode = jQuery( '.wp-editor-area', window.parent.document ).val();
+ }
+ }
+ var regexp = new RegExp(
+ '\\[contact-form\\b.*?\\/?\\](?:[\\s\\S]+?\\[\\/contact-form\\])?'
+ );
+
+ // Remove new lines that cause BR tags to show up
+ response = response.replace( /\n/g, ' ' );
+ // Convert characters to comma
+ response = response.replace( /%26#x002c;/g, ',' );
+
+ // Add new shortcode
+ if ( currentCode.match( regexp ) ) {
+ if ( isVisual ) {
+ win.tinyMCE.activeEditor.execCommand(
+ 'mceSetContent',
+ false,
+ currentCode.replace( regexp, response )
+ );
+ } else {
+ // looks like the visual editor is disabled,
+ // update the contents of the post directly
+ jQuery( '#content', window.parent.document ).val(
+ currentCode.replace( regexp, response )
+ );
+ }
+ } else {
+ try {
+ win.send_to_editor( response );
+ } catch ( e ) {
+ if ( isVisual ) {
+ win.tinyMCE.activeEditor.execCommand( 'mceInsertContent', false, response );
+ } else {
+ // looks like the visual editor is disabled,
+ // update the contents of the post directly
+ jQuery( '#content', window.parent.document ).val( currentCode + response );
+ }
+ }
+ }
+ hidePopup();
+ } );
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'sendShortcodeToEditor(): ' + e );
+ }
+ }
+ }
+ function showDesc() {
+ jQuery( '#fb-desc' ).show();
+ jQuery( '#fb-add-field' ).hide();
+ }
+ function showAndHideMessage( message ) {
+ try {
+ var newMessage = ! message ? GrunionFB_i18n.savedMessage : message;
+ jQuery( '#fb-success' ).text( newMessage );
+ jQuery( '#fb-success' ).slideDown( 'fast' );
+ setTimeout( function() {
+ jQuery( '#fb-success' ).slideUp( 'fast' );
+ }, 2500 );
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'showAndHideMessage(): ' + e );
+ }
+ }
+ }
+ function switchTabs( whichType ) {
+ try {
+ if ( whichType === 'preview' ) {
+ if ( ! validateEmails( jQuery( '#fb-field-my-email' ).val() ) ) {
+ return;
+ }
+ jQuery( '#tab-preview a' ).addClass( 'current' );
+ jQuery( '#tab-settings a' ).removeClass( 'current' );
+ jQuery( '#fb-preview-form, #fb-desc' ).show();
+ jQuery( '#fb-email-settings, #fb-email-desc' ).hide();
+ showAndHideMessage( GrunionFB_i18n.savedMessage );
+ } else {
+ jQuery( '#tab-preview a' ).removeClass( 'current' );
+ jQuery( '#tab-settings a' ).addClass( 'current' );
+ jQuery( '#fb-preview-form, #fb-desc, #fb-add-field' ).hide();
+ jQuery( '#fb-email-settings, #fb-email-desc' ).show();
+ jQuery( '#fb-field-my-email' )
+ .focus()
+ .select();
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'switchTabs(): ' + e );
+ }
+ }
+ }
+ function validateEmails( emails ) {
+ // Field is allowed to be empty :)
+ if ( 0 === emails.length ) {
+ return true;
+ }
+
+ var $e,
+ emailList = emails.split( ',' );
+
+ for ( $e = 0; $e < emailList.length; $e++ ) {
+ if ( false === validateEmail( emailList[ $e ] ) ) {
+ alert( emailList[ $e ] + GrunionFB_i18n.invalidEmail );
+ return false;
+ }
+ }
+
+ return true;
+ }
+ /* Uses The Official Standard: RFC 5322 -- http://www.regular-expressions.info/email.html */
+ function validateEmail( email ) {
+ var re = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i;
+ return re.test( email );
+ }
+ function updateLabel() {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ var thisLabel = jQuery( '#fb-new-label' ).val();
+ // Update preview
+ if ( thisLabel.length === 0 ) {
+ jQuery( '#fb-new-field' + thisId + ' label .label-text' ).text( GrunionFB_i18n.newLabel );
+ } else {
+ jQuery( '#fb-new-field' + thisId + ' label .label-text' ).text( thisLabel );
+ }
+ // Update fbForm object
+ fbForm.fields[ thisId ].label = thisLabel;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateLabel(): ' + e );
+ }
+ }
+ }
+ function updateMyEmail() {
+ try {
+ var thisEmail = jQuery( '#fb-field-my-email' ).val();
+ fbForm.to = thisEmail;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateMyEmail(): ' + e );
+ }
+ }
+ }
+ function updateOption( that ) {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ var thisOptionid = that.attr( 'optionid' );
+ var thisOptionValue = that.val();
+ var thisType = jQuery( '#fb-new-type' ).val();
+ // Update preview
+ if ( thisType === 'radio' ) {
+ jQuery( '#fb-radio-' + thisId + '-' + thisOptionid + ' span' ).text( thisOptionValue );
+ } else if ( 'checkbox-multiple' === thisType ) {
+ jQuery( '#fb-checkbox-multiple-' + thisId + '-' + thisOptionid + ' span' ).text(
+ thisOptionValue
+ );
+ } else {
+ jQuery( '#fb-' + thisId + '-' + thisOptionid ).text( thisOptionValue );
+ }
+ // Update fbForm object
+ fbForm.fields[ thisId ].options[ thisOptionid ] = thisOptionValue;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateOption(): ' + e );
+ }
+ }
+ }
+ function updateRequired() {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ var thisChecked = jQuery( '#fb-new-required' ).is( ':checked' );
+ // Update object and preview
+ if ( thisChecked ) {
+ fbForm.fields[ thisId ].required = true;
+ jQuery( '#fb-new-field' + thisId + ' label' ).append(
+ '<span class="label-required">' + GrunionFB_i18n.requiredLabel + '</span>'
+ );
+ } else {
+ fbForm.fields[ thisId ].required = false;
+ jQuery( '#fb-new-field' + thisId + ' label .label-required' ).remove();
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateRequired(): ' + e );
+ }
+ }
+ }
+ function updateSubject() {
+ try {
+ var thisSubject = jQuery( '#fb-field-subject' ).val();
+ fbForm.subject = thisSubject;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateSubject(): ' + e );
+ }
+ }
+ }
+ function updateType( thisType, thisLabelText, thisRequired ) {
+ try {
+ var thisId = jQuery( '#fb-field-id' ).val();
+ if ( ! thisType ) {
+ thisType = jQuery( '#fb-new-type' ).val();
+ }
+ if ( ! thisLabelText ) {
+ thisLabelText = jQuery( '#fb-new-field' + thisId + ' .label-text' ).text();
+ }
+ var isRequired = thisRequired
+ ? '<span class="label-required">' + GrunionFB_i18n.requiredLabel + '</span>'
+ : '';
+ var thisLabel =
+ '<label fieldid="' +
+ thisId +
+ '" for="fb-field' +
+ thisId +
+ '"><span class="label-text">' +
+ FB.esc_html( thisLabelText ) +
+ '</span>' +
+ isRequired +
+ '</label>';
+ var thisRadio =
+ '<input type="radio" name="radio-' +
+ thisId +
+ '" id="fb-field' +
+ thisId +
+ ' "disabled="disabled" />';
+ var thisRadioLabel =
+ '<label fieldid="' +
+ thisId +
+ '" for="fb-field' +
+ thisId +
+ '" class="fb-radio-label"><span class="label-text">' +
+ FB.esc_html( thisLabelText ) +
+ '</span>' +
+ isRequired +
+ '</label>';
+ var thisRadioRemove = '<div class="fb-remove fb-remove-small" id="' + thisId + '"></div>';
+ var thisRemove = '<div class="fb-remove" id="' + thisId + '"></div>';
+ var thisCheckbox =
+ '<input type="checkbox" id="fb-field' + thisId + '" "disabled="disabled" />';
+ var thisCheckboxMultiple =
+ '<input type="checkbox" id="fb-field' + thisId + '" "disabled="disabled" />';
+ var thisCheckboxMultipleRemove =
+ '<div class="fb-remove fb-remove-small" id="' + thisId + '"></div>';
+ var thisText = '<input type="text" id="fb-field' + thisId + '" "disabled="disabled" />';
+ var thisTextarea = '<textarea id="fb-field' + thisId + '" "disabled="disabled"></textarea>';
+ var thisClear = '<div class="clear"></div>';
+ var thisSelect =
+ '<select id="fb-field' +
+ thisId +
+ '" fieldid="' +
+ thisId +
+ '"><option id="fb-' +
+ thisId +
+ '-' +
+ optionsCount +
+ '" value="' +
+ thisId +
+ '-' +
+ optionsCount +
+ '">' +
+ GrunionFB_i18n.firstOptionLabel +
+ '</option></select>';
+ switch ( thisType ) {
+ case 'checkbox':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRadioRemove + thisCheckbox + thisRadioLabel + thisClear
+ );
+ break;
+ case 'checkbox-multiple':
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisLabel +
+ thisCheckboxMultipleRemove +
+ '<div fieldid="' +
+ thisId +
+ '" id="fb-custom-checkbox-multiple' +
+ thisId +
+ '"></div>'
+ );
+ if (
+ optionsCache[ thisId ] !== undefined &&
+ optionsCache[ thisId ].options.length !== 0
+ ) {
+ fbForm.fields[ thisId ].options = optionsCache[ thisId ].options;
+ jQuery( '#fb-custom-checkbox-multiple' + thisId ).append(
+ customOptions( thisId, thisType )
+ );
+ } else {
+ jQuery( '#fb-new-options' ).html(
+ '<label for="fb-option0">' +
+ GrunionFB_i18n.optionsLabel +
+ '</label><input type="text" id="fb-option0" optionid="0" value="' +
+ GrunionFB_i18n.firstOptionLabel +
+ '" class="fb-options" />'
+ );
+ jQuery( '#fb-custom-checkbox-multiple' + thisId ).append(
+ '<div id="fb-checkbox-multiple-' +
+ thisId +
+ '-0">' +
+ thisCheckboxMultiple +
+ '<span>' +
+ GrunionFB_i18n.firstOptionLabel +
+ '</span>' +
+ thisClear +
+ '</div>'
+ );
+ fbForm.fields[ thisId ].options[ optionsCount ] = GrunionFB_i18n.firstOptionLabel;
+ }
+ jQuery( '#fb-options' ).show();
+ setTimeout( function() {
+ jQuery( '#fb-option0' )
+ .focus()
+ .select();
+ }, 100 );
+ break;
+ case 'email':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisText
+ );
+ break;
+ case 'name':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisText
+ );
+ break;
+ case 'radio':
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisLabel +
+ thisRadioRemove +
+ '<div fieldid="' +
+ thisId +
+ '" id="fb-custom-radio' +
+ thisId +
+ '"></div>'
+ );
+ if (
+ optionsCache[ thisId ] !== undefined &&
+ optionsCache[ thisId ].options.length !== 0
+ ) {
+ fbForm.fields[ thisId ].options = optionsCache[ thisId ].options;
+ jQuery( '#fb-custom-radio' + thisId ).append( customOptions( thisId, thisType ) );
+ } else {
+ jQuery( '#fb-new-options' ).html(
+ '<label for="fb-option0">' +
+ GrunionFB_i18n.optionsLabel +
+ '</label><input type="text" id="fb-option0" optionid="0" value="' +
+ GrunionFB_i18n.firstOptionLabel +
+ '" class="fb-options" />'
+ );
+ jQuery( '#fb-custom-radio' + thisId ).append(
+ '<div id="fb-radio-' +
+ thisId +
+ '-0">' +
+ thisRadio +
+ '<span>' +
+ GrunionFB_i18n.firstOptionLabel +
+ '</span>' +
+ thisClear +
+ '</div>'
+ );
+ fbForm.fields[ thisId ].options[ optionsCount ] = GrunionFB_i18n.firstOptionLabel;
+ }
+ jQuery( '#fb-options' ).show();
+ setTimeout( function() {
+ jQuery( '#fb-option0' )
+ .focus()
+ .select();
+ }, 100 );
+ break;
+ case 'select':
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisSelect
+ );
+ if (
+ optionsCache[ thisId ] !== undefined &&
+ optionsCache[ thisId ].options.length !== 0
+ ) {
+ fbForm.fields[ thisId ].options = optionsCache[ thisId ].options;
+ jQuery( '#fb-field' + thisId ).html( customOptions( thisId, thisType ) );
+ } else {
+ jQuery( '#fb-new-options' ).html(
+ '<label for="fb-option0">' +
+ GrunionFB_i18n.optionsLabel +
+ '</label><input type="text" id="fb-option0" optionid="0" value="' +
+ GrunionFB_i18n.firstOptionLabel +
+ '" class="fb-options" />'
+ );
+ fbForm.fields[ thisId ].options[ optionsCount ] = GrunionFB_i18n.firstOptionLabel;
+ }
+ jQuery( '#fb-options' ).show();
+ setTimeout( function() {
+ jQuery( '#fb-option0' )
+ .focus()
+ .select();
+ }, 100 );
+ break;
+ case 'text':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisText
+ );
+ break;
+ case 'textarea':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisTextarea
+ );
+ break;
+ case 'url':
+ removeOptions();
+ jQuery( '#fb-new-field' + thisId + ' .fb-fields' ).html(
+ thisRemove + thisLabel + thisText
+ );
+ break;
+ }
+ // update object
+ fbForm.fields[ thisId ].type = thisType;
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'updateType(): ' + e );
+ }
+ }
+ }
+ return {
+ resizePop: function() {
+ try {
+ //Thickbox won't resize for some reason, we are manually doing it here
+ var totalWidth = jQuery( 'body', window.parent.document ).width();
+ var totalHeight = jQuery( 'body', window.parent.document ).height();
+ var isIE6 = typeof document.body.style.maxHeight === 'undefined';
+
+ jQuery( '#TB_window, #TB_iframeContent', window.parent.document ).css( 'width', '768px' );
+ jQuery( '#TB_window', window.parent.document ).css( {
+ left: ( totalWidth - 768 ) / 2 + 'px',
+ top: '23px',
+ position: 'absolute',
+ marginLeft: '0',
+ } );
+ if ( ! isIE6 ) {
+ // take away IE6
+ jQuery( '#TB_window, #TB_iframeContent', window.parent.document ).css(
+ 'height',
+ totalHeight - 73 + 'px'
+ );
+ }
+ } catch ( e ) {
+ if ( debug ) {
+ console.log( 'resizePop(): ' + e );
+ }
+ }
+ },
+ init: function() {
+ // Scroll to top of page
+ window.parent.scroll( 0, 0 );
+ //Check for existing form data
+ var contentSource;
+ if (
+ jQuery( '#edButtonPreview', window.parent.document ).hasClass( 'active' ) ||
+ jQuery( '#wp-content-wrap', window.parent.document ).hasClass( 'tmce-active' )
+ ) {
+ var win = window.dialogArguments || opener || parent || top;
+ contentSource = win.tinyMCE.activeEditor.getContent();
+ } else {
+ contentSource = jQuery( '#content', window.parent.document ).val();
+ }
+ var data = {
+ action: 'grunion_shortcode_to_json',
+ _ajax_nonce: ajax_nonce_json,
+ post_id: postId,
+ content: contentSource,
+ };
+
+ var $doc = jQuery( document );
+
+ jQuery.post( ajaxurl, data, function( response ) {
+ // Setup fbForm
+ parseShortcode( jQuery.parseJSON( response ) );
+ // Now build out the preview form
+ buildPreview();
+ } );
+ // actions
+ jQuery( '.fb-add-field' ).click( function() {
+ addField();
+ hideDesc();
+ return false;
+ } );
+ jQuery( '#fb-new-label' ).keyup( function() {
+ updateLabel();
+ } );
+ jQuery( '#fb-new-type' ).change( function() {
+ updateType();
+ } );
+ jQuery( '#fb-new-required' ).click( function() {
+ updateRequired();
+ } );
+ $doc.on( 'click', '.fb-remove', function() {
+ showDesc();
+ deleteField( jQuery( this ) );
+ grabShortcode();
+ } );
+ jQuery( '#fb-preview' ).submit( function() {
+ sendShortcodeToEditor();
+ return false;
+ } );
+ jQuery( '#TB_overlay, #TB_closeWindowButton', window.parent.document ).mousedown( function() {
+ if ( confirm( GrunionFB_i18n.exitConfirmMessage ) ) {
+ hidePopup();
+ }
+ } );
+ $doc.on( 'click', '#fb-another-option', function() {
+ addOption();
+ } );
+ $doc.on( 'keyup', '.fb-options', function() {
+ updateOption( jQuery( this ) );
+ } );
+ $doc.on( 'click', '.fb-remove-option', function() {
+ removeOption( jQuery( this ).attr( 'optionid' ) );
+ } );
+ jQuery( '#tab-preview a' ).click( function() {
+ switchTabs( 'preview' );
+ return false;
+ } );
+ jQuery( '#fb-prev-form' ).click( function() {
+ switchTabs( 'preview' );
+ return false;
+ } );
+ jQuery( '#tab-settings a' ).click( function() {
+ switchTabs();
+ return false;
+ } );
+ jQuery( '#fb-field-my-email' ).blur( function() {
+ updateMyEmail();
+ } );
+ jQuery( '#fb-field-subject' ).blur( function() {
+ updateSubject();
+ } );
+ $doc.on( 'mouseenter', '.fb-form-case .fb-new-fields', function() {
+ hideShowEditLink( 'show', jQuery( this ) );
+ } );
+ $doc.on( 'mouseleave', '.fb-form-case .fb-new-fields', function() {
+ hideShowEditLink( 'hide' );
+ return false;
+ } );
+ $doc.on( 'click', '.fb-edit-field', function() {
+ editField( jQuery( this ) );
+ return false;
+ } );
+ $doc.on( 'click', '.fb-edit-field .fb-reorder', function() {
+ return false;
+ } );
+ $doc.on( 'click', '#fb-save-field', function() {
+ showDesc();
+ showAndHideMessage();
+ return false;
+ } );
+ jQuery( '#fb-feedback' ).click( function() {
+ var thisHref = jQuery( this ).attr( 'href' );
+ window.parent.location = thisHref;
+ return false;
+ } );
+ jQuery( '#sortable' ).sortable( {
+ axis: 'y',
+ handle: '.fb-reorder',
+ revert: true,
+ start: function() {
+ jQuery( '.fb-edit-field' ).hide();
+ },
+ } );
+ jQuery( '#draggable' ).draggable( {
+ axis: 'y',
+ handle: '.fb-reorder',
+ connectToSortable: '#sortable',
+ helper: 'clone',
+ revert: 'invalid',
+ } );
+ },
+ };
+} )();
diff --git a/plugins/jetpack/modules/contact-form/js/tinymce-plugin-form-button.js b/plugins/jetpack/modules/contact-form/js/tinymce-plugin-form-button.js
new file mode 100644
index 00000000..8104ea32
--- /dev/null
+++ b/plugins/jetpack/modules/contact-form/js/tinymce-plugin-form-button.js
@@ -0,0 +1,37 @@
+/* global grunionEditorView, tinymce */
+( function() {
+ tinymce.create( 'tinymce.plugins.grunion_form', {
+ init: function( editor ) {
+ editor.addButton( 'grunion', {
+ title: grunionEditorView.labels.tinymce_label,
+ cmd: 'grunion_add_form',
+ icon: 'grunion',
+ } );
+ editor.addCommand( 'grunion_add_form', function() {
+ if ( grunionEditorView.default_form ) {
+ editor.execCommand(
+ 'mceInsertContent',
+ 0,
+ '[contact-form]' + grunionEditorView.default_form + '[/contact-form]'
+ );
+ } else {
+ editor.execCommand( 'mceInsertContent', 0, '[contact-form /]' );
+ }
+ } );
+ },
+
+ createControl: function() {
+ return null;
+ },
+
+ getInfo: function() {
+ return {
+ longname: 'Grunion Contact Form',
+ author: 'Automattic',
+ version: '1',
+ };
+ },
+ } );
+
+ tinymce.PluginManager.add( 'grunion_form', tinymce.plugins.grunion_form );
+} )();