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