Grunion_Contact_Form_Plugin

Sets up various actions, filters, post types, post statuses, shortcodes.

Defined (1)

The class is defined in the following location(s).

/modules/contact-form/grunion-contact-form.php  
  1. class Grunion_Contact_Form_Plugin { 
  2.  
  3. /** 
  4. * @var string The Widget ID of the widget currently being processed. Used to build the unique contact-form ID for forms embedded in widgets. 
  5. */ 
  6. public $current_widget_id; 
  7.  
  8. static $using_contact_form_field = false; 
  9.  
  10. static function init() { 
  11. static $instance = false; 
  12.  
  13. if ( !$instance ) { 
  14. $instance = new Grunion_Contact_Form_Plugin; 
  15.  
  16. return $instance; 
  17.  
  18. /** 
  19. * Strips HTML tags from input. Output is NOT HTML safe. 
  20. * @param mixed $data_with_tags 
  21. * @return mixed 
  22. */ 
  23. public static function strip_tags( $data_with_tags ) { 
  24. if ( is_array( $data_with_tags ) ) { 
  25. foreach ( $data_with_tags as $index => $value ) { 
  26. $index = sanitize_text_field( strval( $index ) ); 
  27. $value = wp_kses( strval( $value ), array() ); 
  28. $value = str_replace( '&', '&', $value ); // undo damage done by wp_kses_normalize_entities() 
  29.  
  30. $data_without_tags[ $index ] = $value; 
  31. } else { 
  32. $data_without_tags = wp_kses( $data_with_tags, array() ); 
  33. $data_without_tags = str_replace( '&', '&', $data_without_tags ); // undo damage done by wp_kses_normalize_entities() 
  34.  
  35. return $data_without_tags; 
  36.  
  37. function __construct() { 
  38. $this->add_shortcode(); 
  39.  
  40. // While generating the output of a text widget with a contact-form shortcode, we need to know its widget ID. 
  41. add_action( 'dynamic_sidebar', array( $this, 'track_current_widget' ) ); 
  42.  
  43. // Add a "widget" shortcode attribute to all contact-form shortcodes embedded in widgets 
  44. add_filter( 'widget_text', array( $this, 'widget_atts' ), 0 ); 
  45.  
  46. // If Text Widgets don't get shortcode processed, hack ours into place. 
  47. if ( !has_filter( 'widget_text', 'do_shortcode' ) ) 
  48. add_filter( 'widget_text', array( $this, 'widget_shortcode_hack' ), 5 ); 
  49.  
  50. // Akismet to the rescue 
  51. if ( defined( 'AKISMET_VERSION' ) || function_exists( 'akismet_http_post' ) ) { 
  52. add_filter( 'jetpack_contact_form_is_spam', array( $this, 'is_spam_akismet' ), 10, 2 ); 
  53. add_action( 'contact_form_akismet', array( $this, 'akismet_submit' ), 10, 2 ); 
  54.  
  55. add_action( 'loop_start', array( 'Grunion_Contact_Form', '_style_on' ) ); 
  56.  
  57. add_action( 'wp_ajax_grunion-contact-form', array( $this, 'ajax_request' ) ); 
  58. add_action( 'wp_ajax_nopriv_grunion-contact-form', array( $this, 'ajax_request' ) ); 
  59.  
  60. // Export to CSV feature 
  61. if ( is_admin() ) { 
  62. add_action( 'admin_init', array( $this, 'download_feedback_as_csv' ) ); 
  63. add_action( 'admin_footer-edit.php', array( $this, 'export_form' ) ); 
  64.  
  65. // custom post type we'll use to keep copies of the feedback items 
  66. register_post_type( 'feedback', array( 
  67. 'labels' => array( 
  68. 'name' => __( 'Feedback', 'jetpack' ),  
  69. 'singular_name' => __( 'Feedback', 'jetpack' ),  
  70. 'search_items' => __( 'Search Feedback', 'jetpack' ),  
  71. 'not_found' => __( 'No feedback found', 'jetpack' ),  
  72. 'not_found_in_trash' => __( 'No feedback found', 'jetpack' ) 
  73. ),  
  74. 'menu_icon' => GRUNION_PLUGIN_URL . '/images/grunion-menu.png',  
  75. 'show_ui' => TRUE,  
  76. 'show_in_admin_bar' => FALSE,  
  77. 'public' => FALSE,  
  78. 'rewrite' => FALSE,  
  79. 'query_var' => FALSE,  
  80. 'capability_type' => 'page' 
  81. ) ); 
  82.  
  83. // Add to REST API post type whitelist 
  84. add_filter( 'rest_api_allowed_post_types', array( $this, 'allow_feedback_rest_api_type' ) ); 
  85.  
  86. // Add "spam" as a post status 
  87. register_post_status( 'spam', array( 
  88. 'label' => 'Spam',  
  89. 'public' => FALSE,  
  90. 'exclude_from_search' => TRUE,  
  91. 'show_in_admin_all_list' => FALSE,  
  92. 'label_count' => _n_noop( 'Spam <span class="count">(%s)</span>', 'Spam <span class="count">(%s)</span>', 'jetpack' ),  
  93. 'protected' => TRUE,  
  94. '_builtin' => FALSE 
  95. ) ); 
  96.  
  97. // POST handler 
  98. if ( 
  99. isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' == strtoupper( $_SERVER['REQUEST_METHOD'] ) 
  100. && 
  101. isset( $_POST['action'] ) && 'grunion-contact-form' == $_POST['action'] 
  102. && 
  103. isset( $_POST['contact-form-id'] ) 
  104. ) { 
  105. add_action( 'template_redirect', array( $this, 'process_form_submission' ) ); 
  106.  
  107. /** Can be dequeued by placing the following in wp-content/themes/yourtheme/functions.php 
  108. * function remove_grunion_style() { 
  109. * wp_deregister_style('grunion.css'); 
  110. * } 
  111. * add_action('wp_print_styles', 'remove_grunion_style'); 
  112. */ 
  113. if( is_rtl() ) { 
  114. wp_register_style( 'grunion.css', GRUNION_PLUGIN_URL . 'css/rtl/grunion-rtl.css', array(), JETPACK__VERSION ); 
  115. } else { 
  116. wp_register_style( 'grunion.css', GRUNION_PLUGIN_URL . 'css/grunion.css', array(), JETPACK__VERSION ); 
  117.  
  118. /** 
  119. * Add to REST API post type whitelist 
  120. */ 
  121. function allow_feedback_rest_api_type( $post_types ) { 
  122. $post_types[] = 'feedback'; 
  123. return $post_types; 
  124.  
  125. /** 
  126. * Handles all contact-form POST submissions 
  127. * Conditionally attached to `template_redirect` 
  128. */ 
  129. function process_form_submission() { 
  130. // Add a filter to replace tokens in the subject field with sanitized field values 
  131. add_filter( 'contact_form_subject', array( $this, 'replace_tokens_with_input' ), 10, 2 ); 
  132.  
  133. $id = stripslashes( $_POST['contact-form-id'] ); 
  134.  
  135. if ( is_user_logged_in() ) { 
  136. check_admin_referer( "contact-form_{$id}" ); 
  137.  
  138. $is_widget = 0 === strpos( $id, 'widget-' ); 
  139.  
  140. $form = false; 
  141.  
  142. if ( $is_widget ) { 
  143. // It's a form embedded in a text widget 
  144.  
  145. $this->current_widget_id = substr( $id, 7 ); // remove "widget-" 
  146. $widget_type = implode( '-', array_slice( explode( '-', $this->current_widget_id ), 0, -1 ) ); // Remove trailing -# 
  147.  
  148. // Is the widget active? 
  149. $sidebar = is_active_widget( false, $this->current_widget_id, $widget_type ); 
  150.  
  151. // This is lame - no core API for getting a widget by ID 
  152. $widget = isset( $GLOBALS['wp_registered_widgets'][$this->current_widget_id] ) ? $GLOBALS['wp_registered_widgets'][$this->current_widget_id] : false; 
  153.  
  154. if ( $sidebar && $widget && isset( $widget['callback'] ) ) { 
  155. // This is lamer - no API for outputting a given widget by ID 
  156. ob_start(); 
  157. // Process the widget to populate Grunion_Contact_Form::$last 
  158. call_user_func( $widget['callback'], array(), $widget['params'][0] ); 
  159. ob_end_clean(); 
  160. } else { 
  161. // It's a form embedded in a post 
  162.  
  163. $post = get_post( $id ); 
  164.  
  165. // Process the content to populate Grunion_Contact_Form::$last 
  166. /** This filter is already documented in core. wp-includes/post-template.php */ 
  167. apply_filters( 'the_content', $post->post_content ); 
  168.  
  169. $form = Grunion_Contact_Form::$last; 
  170.  
  171. // No form may mean user is using do_shortcode, grab the form using the stored post meta 
  172. if ( ! $form ) { 
  173.  
  174. // Get shortcode from post meta 
  175. $shortcode = get_post_meta( $_POST['contact-form-id'], '_g_feedback_shortcode', true ); 
  176.  
  177. // Format it 
  178. if ( $shortcode != '' ) { 
  179. $shortcode = '[contact-form]' . $shortcode . '[/contact-form]'; 
  180. do_shortcode( $shortcode ); 
  181.  
  182. // Recreate form 
  183. $form = Grunion_Contact_Form::$last; 
  184.  
  185. if ( ! $form ) { 
  186. return false; 
  187.  
  188. if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) 
  189. return $form->errors; 
  190.  
  191. // Process the form 
  192. return $form->process_submission(); 
  193.  
  194. function ajax_request() { 
  195. $submission_result = self::process_form_submission(); 
  196.  
  197. if ( ! $submission_result ) { 
  198. header( "HTTP/1.1 500 Server Error", 500, true ); 
  199. echo '<div class="form-error"><ul class="form-errors"><li class="form-error-message">'; 
  200. esc_html_e( 'An error occurred. Please try again later.', 'jetpack' ); 
  201. echo '</li></ul></div>'; 
  202. } elseif ( is_wp_error( $submission_result ) ) { 
  203. header( "HTTP/1.1 400 Bad Request", 403, true ); 
  204. echo '<div class="form-error"><ul class="form-errors"><li class="form-error-message">'; 
  205. echo esc_html( $submission_result->get_error_message() ); 
  206. echo '</li></ul></div>'; 
  207. } else { 
  208. echo '<h3>' . esc_html__( 'Message Sent', 'jetpack' ) . '</h3>' . $submission_result; 
  209.  
  210. die; 
  211.  
  212. /** 
  213. * Ensure the post author is always zero for contact-form feedbacks 
  214. * Attached to `wp_insert_post_data` 
  215. * @see Grunion_Contact_Form::process_submission() 
  216. * @param array $data the data to insert 
  217. * @param array $postarr the data sent to wp_insert_post() 
  218. * @return array The filtered $data to insert 
  219. */ 
  220. function insert_feedback_filter( $data, $postarr ) { 
  221. if ( $data['post_type'] == 'feedback' && $postarr['post_type'] == 'feedback' ) { 
  222. $data['post_author'] = 0; 
  223.  
  224. return $data; 
  225. /** 
  226. * Adds our contact-form shortcode 
  227. * The "child" contact-field shortcode is enabled as needed by the contact-form shortcode handler 
  228. */ 
  229. function add_shortcode() { 
  230. add_shortcode( 'contact-form', array( 'Grunion_Contact_Form', 'parse' ) ); 
  231. add_shortcode( 'contact-field', array( 'Grunion_Contact_Form', 'parse_contact_field' ) ); 
  232.  
  233. static function tokenize_label( $label ) { 
  234. return '{' . trim( preg_replace( '#^\d+_#', '', $label ) ) . '}'; 
  235.  
  236. static function sanitize_value( $value ) { 
  237. return preg_replace( '=((<CR>|<LF>|0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $value ); 
  238.  
  239. /** 
  240. * Replaces tokens like {city} or {City} (case insensitive) with the value 
  241. * of an input field of that name 
  242. * @param string $subject 
  243. * @param array $field_values Array with field label => field value associations 
  244. * @return string The filtered $subject with the tokens replaced 
  245. */ 
  246. function replace_tokens_with_input( $subject, $field_values ) { 
  247. // Wrap labels into tokens (inside {}) 
  248. $wrapped_labels = array_map( array( 'Grunion_Contact_Form_Plugin', 'tokenize_label' ), array_keys( $field_values ) ); 
  249. // Sanitize all values 
  250. $sanitized_values = array_map( array( 'Grunion_Contact_Form_Plugin', 'sanitize_value' ), array_values( $field_values ) ); 
  251.  
  252. foreach ( $sanitized_values as $k => $sanitized_value ) { 
  253. if ( is_array( $sanitized_value ) ) { 
  254. $sanitized_values[ $k ] = implode( ', ', $sanitized_value ); 
  255.  
  256. // Search for all valid tokens (based on existing fields) and replace with the field's value 
  257. $subject = str_ireplace( $wrapped_labels, $sanitized_values, $subject ); 
  258. return $subject; 
  259.  
  260. /** 
  261. * Tracks the widget currently being processed. 
  262. * Attached to `dynamic_sidebar` 
  263. * @see $current_widget_id 
  264. * @param array $widget The widget data 
  265. */ 
  266. function track_current_widget( $widget ) { 
  267. $this->current_widget_id = $widget['id']; 
  268.  
  269. /** 
  270. * Adds a "widget" attribute to every contact-form embedded in a text widget. 
  271. * Used to tell the difference between post-embedded contact-forms and widget-embedded contact-forms 
  272. * Attached to `widget_text` 
  273. * @param string $text The widget text 
  274. * @return string The filtered widget text 
  275. */ 
  276. function widget_atts( $text ) { 
  277. Grunion_Contact_Form::style( true ); 
  278.  
  279. return preg_replace( '/\[contact-form([^a-zA-Z_-])/', '[contact-form widget="' . $this->current_widget_id . '"\\1', $text ); 
  280.  
  281. /** 
  282. * For sites where text widgets are not processed for shortcodes, we add this hack to process just our shortcode 
  283. * Attached to `widget_text` 
  284. * @param string $text The widget text 
  285. * @return string The contact-form filtered widget text 
  286. */ 
  287. function widget_shortcode_hack( $text ) { 
  288. if ( !preg_match( '/\[contact-form([^a-zA-Z_-])/', $text ) ) { 
  289. return $text; 
  290.  
  291. $old = $GLOBALS['shortcode_tags']; 
  292. remove_all_shortcodes(); 
  293. Grunion_Contact_Form_Plugin::$using_contact_form_field = true; 
  294. $this->add_shortcode(); 
  295.  
  296. $text = do_shortcode( $text ); 
  297.  
  298. Grunion_Contact_Form_Plugin::$using_contact_form_field = false; 
  299. $GLOBALS['shortcode_tags'] = $old; 
  300.  
  301. return $text; 
  302.  
  303. /** 
  304. * Populate an array with all values necessary to submit a NEW contact-form feedback to Akismet. 
  305. * Note that this includes the current user_ip etc, so this should only be called when accepting a new item via $_POST 
  306. * @param array $form Contact form feedback array 
  307. * @return array feedback array with additional data ready for submission to Akismet 
  308. */ 
  309. function prepare_for_akismet( $form ) { 
  310. $form['comment_type'] = 'contact_form'; 
  311. $form['user_ip'] = preg_replace( '/[^0-9., ]/', '', $_SERVER['REMOTE_ADDR'] ); 
  312. $form['user_agent'] = $_SERVER['HTTP_USER_AGENT']; 
  313. $form['referrer'] = $_SERVER['HTTP_REFERER']; 
  314. $form['blog'] = get_option( 'home' ); 
  315.  
  316. $ignore = array( 'HTTP_COOKIE' ); 
  317.  
  318. foreach ( $_SERVER as $k => $value ) 
  319. if ( !in_array( $k, $ignore ) && is_string( $value ) ) 
  320. $form["$k"] = $value; 
  321.  
  322. return $form; 
  323.  
  324. /** 
  325. * Submit contact-form data to Akismet to check for spam. 
  326. * If you're accepting a new item via $_POST, run it Grunion_Contact_Form_Plugin::prepare_for_akismet() first 
  327. * Attached to `jetpack_contact_form_is_spam` 
  328. * @param bool $is_spam 
  329. * @param array $form 
  330. * @return bool|WP_Error TRUE => spam, FALSE => not spam, WP_Error => stop processing entirely 
  331. */ 
  332. function is_spam_akismet( $is_spam, $form = array() ) { 
  333. global $akismet_api_host, $akismet_api_port; 
  334.  
  335. // The signature of this function changed from accepting just $form. 
  336. // If something only sends an array, assume it's still using the old 
  337. // signature and work around it. 
  338. if ( empty( $form ) && is_array( $is_spam ) ) { 
  339. $form = $is_spam; 
  340. $is_spam = false; 
  341.  
  342. // If a previous filter has alrady marked this as spam, trust that and move on. 
  343. if ( $is_spam ) { 
  344. return $is_spam; 
  345.  
  346. if ( !function_exists( 'akismet_http_post' ) && !defined( 'AKISMET_VERSION' ) ) 
  347. return false; 
  348.  
  349. $query_string = http_build_query( $form ); 
  350.  
  351. if ( method_exists( 'Akismet', 'http_post' ) ) { 
  352. $response = Akismet::http_post( $query_string, 'comment-check' ); 
  353. } else { 
  354. $response = akismet_http_post( $query_string, $akismet_api_host, '/1.1/comment-check', $akismet_api_port ); 
  355.  
  356. $result = false; 
  357.  
  358. if ( isset( $response[0]['x-akismet-pro-tip'] ) && 'discard' === trim( $response[0]['x-akismet-pro-tip'] ) && get_option( 'akismet_strictness' ) === '1' ) 
  359. $result = new WP_Error( 'feedback-discarded', __('Feedback discarded.', 'jetpack' ) ); 
  360. elseif ( isset( $response[1] ) && 'true' == trim( $response[1] ) ) // 'true' is spam 
  361. $result = true; 
  362.  
  363. /** 
  364. * Filter the results returned by Akismet for each submitted contact form. 
  365. * @module contact-form 
  366. * @since 1.3.1 
  367. * @param WP_Error|bool $result Is the submitted feedback spam. 
  368. * @param array|bool $form Submitted feedback. 
  369. */ 
  370. return apply_filters( 'contact_form_is_spam_akismet', $result, $form ); 
  371.  
  372. /** 
  373. * Submit a feedback as either spam or ham 
  374. * @param string $as Either 'spam' or 'ham'. 
  375. * @param array $form the contact-form data 
  376. */ 
  377. function akismet_submit( $as, $form ) { 
  378. global $akismet_api_host, $akismet_api_port; 
  379.  
  380. if ( !in_array( $as, array( 'ham', 'spam' ) ) ) 
  381. return false; 
  382.  
  383. $query_string = ''; 
  384. if ( is_array( $form ) ) 
  385. $query_string = http_build_query( $form ); 
  386. if ( method_exists( 'Akismet', 'http_post' ) ) { 
  387. $response = Akismet::http_post( $query_string, "submit-{$as}" ); 
  388. } else { 
  389. $response = akismet_http_post( $query_string, $akismet_api_host, "/1.1/submit-{$as}", $akismet_api_port ); 
  390.  
  391. return trim( $response[1] ); 
  392.  
  393. /** 
  394. * Prints the menu 
  395. */ 
  396. function export_form() { 
  397. if ( get_current_screen()->id != 'edit-feedback' ) 
  398. return; 
  399.  
  400. if ( ! current_user_can( 'export' ) ) { 
  401. return; 
  402.  
  403. // if there aren't any feedbacks, bail out 
  404. if ( ! (int) wp_count_posts( 'feedback' )->publish ) 
  405. return; 
  406. ?> 
  407.  
  408. <div id="feedback-export" style="display:none"> 
  409. <h2><?php _e( 'Export feedback as CSV', 'jetpack' ) ?></h2> 
  410. <div class="clear"></div> 
  411. <form action="<?php echo admin_url( 'admin-post.php' ); ?>" method="post" class="form"> 
  412. <?php wp_nonce_field( 'feedback_export', 'feedback_export_nonce' ); ?> 
  413.  
  414. <input name="action" value="feedback_export" type="hidden"> 
  415. <label for="post"><?php _e( 'Select feedback to download', 'jetpack' ) ?></label> 
  416. <select name="post"> 
  417. <option value="all"><?php esc_html_e( 'All posts', 'jetpack' ) ?></option> 
  418. <?php echo $this->get_feedbacks_as_options() ?> 
  419. </select> 
  420.  
  421. <br><br> 
  422. <input type="submit" name="submit" id="submit" class="button button-primary" value="<?php esc_html_e( 'Download', 'jetpack' ); ?>"> 
  423. </form> 
  424. </div> 
  425.  
  426. <?php 
  427. // There aren't any usable actions in core to output the "export feedback" form in the correct place,  
  428. // so this inline JS moves it from the top of the page to the bottom. 
  429. ?> 
  430. <script type='text/javascript'> 
  431. var menu = document.getElementById( 'feedback-export' ),  
  432. wrapper = document.getElementsByClassName( 'wrap' )[0]; 
  433. wrapper.appendChild(menu); 
  434. menu.style.display = 'block'; 
  435. </script> 
  436. <?php 
  437.  
  438. /** 
  439. * download as a csv a contact form or all of them in a csv file 
  440. */ 
  441. function download_feedback_as_csv() { 
  442. if ( empty( $_POST['feedback_export_nonce'] ) ) 
  443. return; 
  444.  
  445. check_admin_referer( 'feedback_export', 'feedback_export_nonce' ); 
  446.  
  447. if ( ! current_user_can( 'export' ) ) { 
  448. return; 
  449.  
  450. $args = array( 
  451. 'posts_per_page' => -1,  
  452. 'post_type' => 'feedback',  
  453. 'post_status' => 'publish',  
  454. 'order' => 'ASC',  
  455. 'fields' => 'ids',  
  456. 'suppress_filters' => false,  
  457. ); 
  458.  
  459. $filename = date( "Y-m-d" ) . '-feedback-export.csv'; 
  460.  
  461. // Check if we want to download all the feedbacks or just a certain contact form 
  462. if ( ! empty( $_POST['post'] ) && $_POST['post'] !== 'all' ) { 
  463. $args['post_parent'] = (int) $_POST['post']; 
  464. $filename = date( "Y-m-d" ) . '-' . str_replace( ' ', '-', get_the_title( (int) $_POST['post'] ) ) . '.csv'; 
  465.  
  466. $feedbacks = get_posts( $args ); 
  467. $filename = sanitize_file_name( $filename ); 
  468. $fields = $this->get_field_names( $feedbacks ); 
  469.  
  470. array_unshift( $fields, __( 'Contact Form', 'jetpack' ) ); 
  471.  
  472. if ( empty( $feedbacks ) ) 
  473. return; 
  474.  
  475. // Forces the download of the CSV instead of echoing 
  476. header( 'Content-Disposition: attachment; filename=' . $filename ); 
  477. header( 'Pragma: no-cache' ); 
  478. header( 'Expires: 0' ); 
  479. header( 'Content-Type: text/csv; charset=utf-8' ); 
  480.  
  481. $output = fopen( 'php://output', 'w' ); 
  482.  
  483. // Prints the header 
  484. fputcsv( $output, $fields ); 
  485.  
  486. // Create the csv string from the array of post ids 
  487. foreach ( $feedbacks as $feedback ) { 
  488. fputcsv( $output, self::make_csv_row_from_feedback( $feedback, $fields ) ); 
  489.  
  490. fclose( $output ); 
  491.  
  492. /** 
  493. * Returns a string of HTML <option> items from an array of posts 
  494. * @return string a string of HTML <option> items 
  495. */ 
  496. protected function get_feedbacks_as_options() { 
  497. $options = ''; 
  498.  
  499. // Get the feedbacks' parents' post IDs 
  500. $feedbacks = get_posts( array( 
  501. 'fields' => 'id=>parent',  
  502. 'posts_per_page' => 100000,  
  503. 'post_type' => 'feedback',  
  504. 'post_status' => 'publish',  
  505. 'suppress_filters' => false,  
  506. ) ); 
  507. $parents = array_unique( array_values( $feedbacks ) ); 
  508.  
  509. $posts = get_posts( array( 
  510. 'orderby' => 'ID',  
  511. 'posts_per_page' => 1000,  
  512. 'post_type' => 'any',  
  513. 'post__in' => array_values( $parents ),  
  514. 'suppress_filters' => false,  
  515. ) ); 
  516.  
  517. // creates the string of <option> elements 
  518. foreach ( $posts as $post ) { 
  519. $options .= sprintf( '<option value="%s">%s</option>', esc_attr( $post->ID ), esc_html( $post->post_title ) ); 
  520.  
  521. return $options; 
  522.  
  523. /** 
  524. * Get the names of all the form's fields 
  525. * @param array|int $posts the post we want the fields of 
  526. * @return array the array of fields 
  527. */ 
  528. protected function get_field_names( $posts ) { 
  529. $posts = (array) $posts; 
  530. $all_fields = array(); 
  531.  
  532. foreach ( $posts as $post ) { 
  533. $fields = self::parse_fields_from_content( $post ); 
  534.  
  535. if ( isset( $fields['_feedback_all_fields'] ) ) { 
  536. $extra_fields = array_keys( $fields['_feedback_all_fields'] ); 
  537. $all_fields = array_merge( $all_fields, $extra_fields ); 
  538.  
  539. $all_fields = array_unique( $all_fields ); 
  540. return $all_fields; 
  541.  
  542. public static function parse_fields_from_content( $post_id ) { 
  543. static $post_fields; 
  544.  
  545. if ( !is_array( $post_fields ) ) 
  546. $post_fields = array(); 
  547.  
  548. if ( isset( $post_fields[$post_id] ) ) 
  549. return $post_fields[$post_id]; 
  550.  
  551. $all_values = array(); 
  552. $post_content = get_post_field( 'post_content', $post_id ); 
  553. $content = explode( '<!--more-->', $post_content ); 
  554. $lines = array(); 
  555.  
  556. if ( count( $content ) > 1 ) { 
  557. $content = str_ireplace( array( '<br />', ')</p>' ), '', $content[1] ); 
  558. $one_line = preg_replace( '/\s+/', ' ', $content ); 
  559. $one_line = preg_replace( '/.*Array \( (.*)\)/', '$1', $one_line ); 
  560.  
  561. preg_match_all( '/\[([^\]]+)\] =\>\; ([^\[]+)/', $one_line, $matches ); 
  562.  
  563. if ( count( $matches ) > 1 ) 
  564. $all_values = array_combine( array_map('trim', $matches[1]), array_map('trim', $matches[2]) ); 
  565.  
  566. $lines = array_filter( explode( "\n", $content ) ); 
  567.  
  568. $var_map = array( 
  569. 'AUTHOR' => '_feedback_author',  
  570. 'AUTHOR EMAIL' => '_feedback_author_email',  
  571. 'AUTHOR URL' => '_feedback_author_url',  
  572. 'SUBJECT' => '_feedback_subject',  
  573. 'IP' => '_feedback_ip' 
  574. ); 
  575.  
  576. $fields = array(); 
  577.  
  578. foreach( $lines as $line ) { 
  579. $vars = explode( ': ', $line, 2 ); 
  580. if ( !empty( $vars ) ) { 
  581. if ( isset( $var_map[$vars[0]] ) ) { 
  582. $fields[$var_map[$vars[0]]] = self::strip_tags( trim( $vars[1] ) ); 
  583.  
  584. $fields['_feedback_all_fields'] = $all_values; 
  585.  
  586. $post_fields[$post_id] = $fields; 
  587.  
  588. return $fields; 
  589.  
  590. /** 
  591. * Creates a valid csv row from a post id 
  592. * @param int $post_id The id of the post 
  593. * @param array $fields An array containing the names of all the fields of the csv 
  594. * @return String The csv row 
  595. */ 
  596. protected static function make_csv_row_from_feedback( $post_id, $fields ) { 
  597. $content_fields = self::parse_fields_from_content( $post_id ); 
  598. $all_fields = array(); 
  599.  
  600. if ( isset( $content_fields['_feedback_all_fields'] ) ) 
  601. $all_fields = $content_fields['_feedback_all_fields']; 
  602.  
  603. // Overwrite the parsed content with the content we stored in post_meta in a better format. 
  604. $extra_fields = get_post_meta( $post_id, '_feedback_extra_fields', true ); 
  605. foreach ( $extra_fields as $extra_field => $extra_value ) { 
  606. $all_fields[$extra_field] = $extra_value; 
  607.  
  608. // The first element in all of the exports will be the subject 
  609. $row_items[] = $content_fields['_feedback_subject']; 
  610.  
  611. // Loop the fields array in order to fill the $row_items array correctly 
  612. foreach ( $fields as $field ) { 
  613. if ( $field === __( 'Contact Form', 'jetpack' ) ) // the first field will ever be the contact form, so we can continue 
  614. continue; 
  615. elseif ( array_key_exists( $field, $all_fields ) ) 
  616. $row_items[] = $all_fields[$field]; 
  617. else 
  618. $row_items[] = ''; 
  619.  
  620. return $row_items; 
  621.  
  622. public static function get_ip_address() { 
  623. return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : null;