Grunion_Contact_Form

Class for the contact-form shortcode.

Defined (1)

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

/modules/contact-form/grunion-contact-form.php  
  1. class Grunion_Contact_Form extends Crunion_Contact_Form_Shortcode { 
  2. public $shortcode_name = 'contact-form'; 
  3.  
  4. /** 
  5. * @var WP_Error stores form submission errors 
  6. */ 
  7. public $errors; 
  8.  
  9. /** 
  10. * @var Grunion_Contact_Form The most recent (inclusive) contact-form shortcode processed 
  11. */ 
  12. static $last; 
  13.  
  14. /** 
  15. * @var Whatever form we are currently looking at. If processed, will become $last 
  16. */ 
  17. static $current_form; 
  18.  
  19. /** 
  20. * @var bool Whether to print the grunion.css style when processing the contact-form shortcode 
  21. */ 
  22. static $style = false; 
  23.  
  24. function __construct( $attributes, $content = null ) { 
  25. global $post; 
  26.  
  27. // Set up the default subject and recipient for this form 
  28. $default_to = ''; 
  29. $default_subject = '[' . get_option( 'blogname' ) . ']'; 
  30.  
  31. if ( ! isset( $attributes ) || ! is_array( $attributes ) ) { 
  32. $attributes = array(); 
  33.  
  34. if ( ! empty( $attributes['widget'] ) && $attributes['widget'] ) { 
  35. $default_to .= get_option( 'admin_email' ); 
  36. $attributes['id'] = 'widget-' . $attributes['widget']; 
  37. $default_subject = sprintf( _x( '%1$s Sidebar', '%1$s = blog name', 'jetpack' ), $default_subject ); 
  38. } elseif ( $post ) { 
  39. $attributes['id'] = $post->ID; 
  40. $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 ) ); 
  41. $post_author = get_userdata( $post->post_author ); 
  42. $default_to .= $post_author->user_email; 
  43.  
  44. // Keep reference to $this for parsing form fields 
  45. self::$current_form = $this; 
  46.  
  47. $this->defaults = array( 
  48. 'to' => $default_to,  
  49. 'subject' => $default_subject,  
  50. 'show_subject' => 'no', // only used in back-compat mode 
  51. 'widget' => 0, // Not exposed to the user. Works with Grunion_Contact_Form_Plugin::widget_atts() 
  52. 'id' => null, // Not exposed to the user. Set above. 
  53. 'submit_button_text' => __( 'Submit »', 'jetpack' ),  
  54. ); 
  55.  
  56. $attributes = shortcode_atts( $this->defaults, $attributes, 'contact-form' ); 
  57.  
  58. // We only enable the contact-field shortcode temporarily while processing the contact-form shortcode 
  59. Grunion_Contact_Form_Plugin::$using_contact_form_field = true; 
  60.  
  61. parent::__construct( $attributes, $content ); 
  62.  
  63. // There were no fields in the contact form. The form was probably just [contact-form /]. Build a default form. 
  64. if ( empty( $this->fields ) ) { 
  65. // same as the original Grunion v1 form 
  66. $default_form = ' 
  67. [contact-field label="' . __( 'Name', 'jetpack' ) . '" type="name" required="true" /] 
  68. [contact-field label="' . __( 'Email', 'jetpack' ) . '" type="email" required="true" /] 
  69. [contact-field label="' . __( 'Website', 'jetpack' ) . '" type="url" /]'; 
  70.  
  71. if ( 'yes' == strtolower( $this->get_attribute( 'show_subject' ) ) ) { 
  72. $default_form .= ' 
  73. [contact-field label="' . __( 'Subject', 'jetpack' ) . '" type="subject" /]'; 
  74.  
  75. $default_form .= ' 
  76. [contact-field label="' . __( 'Message', 'jetpack' ) . '" type="textarea" /]'; 
  77.  
  78. $this->parse_content( $default_form ); 
  79.  
  80. // Store the shortcode 
  81. $this->store_shortcode( $default_form, $attributes ); 
  82. } else { 
  83. // Store the shortcode 
  84. $this->store_shortcode( $content, $attributes ); 
  85.  
  86. // $this->body and $this->fields have been setup. We no longer need the contact-field shortcode. 
  87. Grunion_Contact_Form_Plugin::$using_contact_form_field = false; 
  88.  
  89. /** 
  90. * Store shortcode content for recall later 
  91. * - used to receate shortcode when user uses do_shortcode 
  92. * @param string $content 
  93. */ 
  94. static function store_shortcode( $content = null, $attributes = null ) { 
  95.  
  96. if ( $content != null and isset( $attributes['id'] ) ) { 
  97.  
  98. $shortcode_meta = get_post_meta( $attributes['id'], '_g_feedback_shortcode', true ); 
  99.  
  100. if ( $shortcode_meta != '' or $shortcode_meta != $content ) { 
  101. update_post_meta( $attributes['id'], '_g_feedback_shortcode', $content ); 
  102.  
  103. // Save attributes to post_meta for later use. They're not available later in do_shortcode situations. 
  104. update_post_meta( $attributes['id'], '_g_feedback_shortcode_atts', $attributes ); 
  105.  
  106. /** 
  107. * Toggle for printing the grunion.css stylesheet 
  108. * @param bool $style 
  109. */ 
  110. static function style( $style ) { 
  111. $previous_style = self::$style; 
  112. self::$style = (bool) $style; 
  113. return $previous_style; 
  114.  
  115. /** 
  116. * Turn on printing of grunion.css stylesheet 
  117. * @see ::style() 
  118. * @internal 
  119. * @param bool $style 
  120. */ 
  121. static function _style_on() { 
  122. return self::style( true ); 
  123.  
  124. /** 
  125. * The contact-form shortcode processor 
  126. * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts() 
  127. * @param string|null $content The shortcode's inner content: [contact-form]$content[/contact-form] 
  128. * @return string HTML for the concat form. 
  129. */ 
  130. static function parse( $attributes, $content ) { 
  131. require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php'; 
  132. if ( Jetpack_Sync_Settings::is_syncing() ) { 
  133. return ''; 
  134. // Create a new Grunion_Contact_Form object (this class) 
  135. $form = new Grunion_Contact_Form( $attributes, $content ); 
  136.  
  137. $id = $form->get_attribute( 'id' ); 
  138.  
  139. if ( ! $id ) { // something terrible has happened 
  140. return '[contact-form]'; 
  141.  
  142. if ( is_feed() ) { 
  143. return '[contact-form]'; 
  144.  
  145. // Only allow one contact form per post/widget 
  146. if ( self::$last && $id == self::$last->get_attribute( 'id' ) ) { 
  147. // We're processing the same post 
  148. if ( self::$last->attributes != $form->attributes || self::$last->content != $form->content ) { 
  149. // And we're processing a different shortcode; 
  150. return ''; 
  151. } // else, we're processing the same shortcode - probably a separate run of do_shortcode() - let it through 
  152.  
  153. } else { 
  154. self::$last = $form; 
  155.  
  156. // Enqueue the grunion.css stylesheet if self::$style allows it 
  157. if ( self::$style && ( empty( $_REQUEST['action'] ) || $_REQUEST['action'] != 'grunion_shortcode_to_json' ) ) { 
  158. // Enqueue the style here instead of printing it, because if some other plugin has run the_post()+rewind_posts(),  
  159. // (like VideoPress does), the style tag gets "printed" the first time and discarded, leaving the contact form unstyled. 
  160. // when WordPress does the real loop. 
  161. wp_enqueue_style( 'grunion.css' ); 
  162.  
  163. $r = ''; 
  164. $r .= "<div id='contact-form-$id'>\n"; 
  165.  
  166. if ( is_wp_error( $form->errors ) && $form->errors->get_error_codes() ) { 
  167. // There are errors. Display them 
  168. $r .= "<div class='form-error'>\n<h3>" . __( 'Error!', 'jetpack' ) . "</h3>\n<ul class='form-errors'>\n"; 
  169. foreach ( $form->errors->get_error_messages() as $message ) { 
  170. $r .= "\t<li class='form-error-message'>" . esc_html( $message ) . "</li>\n"; 
  171. $r .= "</ul>\n</div>\n\n"; 
  172.  
  173. if ( isset( $_GET['contact-form-id'] ) && $_GET['contact-form-id'] == self::$last->get_attribute( 'id' ) && isset( $_GET['contact-form-sent'] ) ) { 
  174. // The contact form was submitted. Show the success message/results 
  175. $feedback_id = (int) $_GET['contact-form-sent']; 
  176.  
  177. $back_url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', '_wpnonce' ) ); 
  178.  
  179. $r_success_message = 
  180. '<h3>' . __( 'Message Sent', 'jetpack' ) . 
  181. ' (<a href="' . esc_url( $back_url ) . '">' . esc_html__( 'go back', 'jetpack' ) . '</a>)' . 
  182. "</h3>\n\n"; 
  183.  
  184. // Don't show the feedback details unless the nonce matches 
  185. if ( $feedback_id && wp_verify_nonce( stripslashes( $_GET['_wpnonce'] ), "contact-form-sent-{$feedback_id}" ) ) { 
  186. $r_success_message .= self::success_message( $feedback_id, $form ); 
  187.  
  188. /** 
  189. * Filter the message returned after a successfull contact form submission. 
  190. * @module contact-form 
  191. * @since 1.3.1 
  192. * @param string $r_success_message Success message. 
  193. */ 
  194. $r .= apply_filters( 'grunion_contact_form_success_message', $r_success_message ); 
  195. } else { 
  196. // Nothing special - show the normal contact form 
  197. if ( $form->get_attribute( 'widget' ) ) { 
  198. // Submit form to the current URL 
  199. $url = remove_query_arg( array( 'contact-form-id', 'contact-form-sent', 'action', '_wpnonce' ) ); 
  200. } else { 
  201. // Submit form to the post permalink 
  202. $url = get_permalink(); 
  203.  
  204. // For SSL/TLS page. See RFC 3986 Section 4.2 
  205. $url = set_url_scheme( $url ); 
  206.  
  207. // May eventually want to send this to admin-post.php... 
  208. /** 
  209. * Filter the contact form action URL. 
  210. * @module contact-form 
  211. * @since 1.3.1 
  212. * @param string $contact_form_id Contact form post URL. 
  213. * @param $post $GLOBALS['post'] Post global variable. 
  214. * @param int $id Contact Form ID. 
  215. */ 
  216. $url = apply_filters( 'grunion_contact_form_form_action', "{$url}#contact-form-{$id}", $GLOBALS['post'], $id ); 
  217.  
  218. $r .= "<form action='" . esc_url( $url ) . "' method='post' class='contact-form commentsblock'>\n"; 
  219. $r .= $form->body; 
  220. $r .= "\t<p class='contact-submit'>\n"; 
  221. $r .= "\t\t<input type='submit' value='" . esc_attr( $form->get_attribute( 'submit_button_text' ) ) . "' class='pushbutton-wide'/>\n"; 
  222. if ( is_user_logged_in() ) { 
  223. $r .= "\t\t" . wp_nonce_field( 'contact-form_' . $id, '_wpnonce', true, false ) . "\n"; // nonce and referer 
  224. $r .= "\t\t<input type='hidden' name='contact-form-id' value='$id' />\n"; 
  225. $r .= "\t\t<input type='hidden' name='action' value='grunion-contact-form' />\n"; 
  226. $r .= "\t</p>\n"; 
  227. $r .= "</form>\n"; 
  228.  
  229. $r .= '</div>'; 
  230.  
  231. return $r; 
  232.  
  233. /** 
  234. * Returns a success message to be returned if the form is sent via AJAX. 
  235. * @param int $feedback_id 
  236. * @param object Grunion_Contact_Form $form 
  237. * @return string $message 
  238. */ 
  239. static function success_message( $feedback_id, $form ) { 
  240. return wp_kses( 
  241. '<blockquote class="contact-form-submission">' 
  242. . '<p>' . join( self::get_compiled_form( $feedback_id, $form ), '</p><p>' ) . '</p>' 
  243. . '</blockquote>',  
  244. array( 'br' => array(), 'blockquote' => array( 'class' => array() ), 'p' => array() ) 
  245. ); 
  246.  
  247. /** 
  248. * Returns a compiled form with labels and values in a form of an array 
  249. * of lines. 
  250. * @param int $feedback_id 
  251. * @param object Grunion_Contact_Form $form 
  252. * @return array $lines 
  253. */ 
  254. static function get_compiled_form( $feedback_id, $form ) { 
  255. $feedback = get_post( $feedback_id ); 
  256. $field_ids = $form->get_field_ids(); 
  257. $content_fields = Grunion_Contact_Form_Plugin::parse_fields_from_content( $feedback_id ); 
  258.  
  259. // Maps field_ids to post_meta keys 
  260. $field_value_map = array( 
  261. 'name' => 'author',  
  262. 'email' => 'author_email',  
  263. 'url' => 'author_url',  
  264. 'subject' => 'subject',  
  265. 'textarea' => false, // not a post_meta key. This is stored in post_content 
  266. ); 
  267.  
  268. $compiled_form = array(); 
  269.  
  270. // "Standard" field whitelist 
  271. foreach ( $field_value_map as $type => $meta_key ) { 
  272. if ( isset( $field_ids[ $type ] ) ) { 
  273. $field = $form->fields[ $field_ids[ $type ] ]; 
  274.  
  275. if ( $meta_key ) { 
  276. if ( isset( $content_fields[ "_feedback_{$meta_key}" ] ) ) { 
  277. $value = $content_fields[ "_feedback_{$meta_key}" ]; 
  278. } else { 
  279. // The feedback content is stored as the first "half" of post_content 
  280. $value = $feedback->post_content; 
  281. list( $value ) = explode( '<!--more-->', $value ); 
  282. $value = trim( $value ); 
  283.  
  284. $field_index = array_search( $field_ids[ $type ], $field_ids['all'] ); 
  285. $compiled_form[ $field_index ] = sprintf( 
  286. '<b>%1$s:</b> %2$s<br /><br />',  
  287. wp_kses( $field->get_attribute( 'label' ), array() ),  
  288. nl2br( wp_kses( $value, array() ) ) 
  289. ); 
  290.  
  291. // "Non-standard" fields 
  292. if ( $field_ids['extra'] ) { 
  293. // array indexed by field label (not field id) 
  294. $extra_fields = get_post_meta( $feedback_id, '_feedback_extra_fields', true ); 
  295.  
  296. /** 
  297. * Only get data for the compiled form if `$extra_fields` is a valid and non-empty array. 
  298. */ 
  299. if ( is_array( $extra_fields ) && ! empty( $extra_fields ) ) { 
  300.  
  301. $extra_field_keys = array_keys( $extra_fields ); 
  302.  
  303. $i = 0; 
  304. foreach ( $field_ids['extra'] as $field_id ) { 
  305. $field = $form->fields[ $field_id ]; 
  306. $field_index = array_search( $field_id, $field_ids['all'] ); 
  307.  
  308. $label = $field->get_attribute( 'label' ); 
  309.  
  310. $compiled_form[ $field_index ] = sprintf( 
  311. '<b>%1$s:</b> %2$s<br /><br />',  
  312. wp_kses( $label, array() ),  
  313. nl2br( wp_kses( $extra_fields[ $extra_field_keys[ $i ] ], array() ) ) 
  314. ); 
  315.  
  316. $i++; 
  317.  
  318. // Sorting lines by the field index 
  319. ksort( $compiled_form ); 
  320.  
  321. return $compiled_form; 
  322.  
  323. /** 
  324. * The contact-field shortcode processor 
  325. * 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. 
  326. * @param array $attributes Key => Value pairs as parsed by shortcode_parse_atts() 
  327. * @param string|null $content The shortcode's inner content: [contact-field]$content[/contact-field] 
  328. * @return HTML for the contact form field 
  329. */ 
  330. static function parse_contact_field( $attributes, $content ) { 
  331. // Don't try to parse contact form fields if not inside a contact form 
  332. if ( ! Grunion_Contact_Form_Plugin::$using_contact_form_field ) { 
  333. $att_strs = array(); 
  334. foreach ( $attributes as $att => $val ) { 
  335. if ( is_numeric( $att ) ) { // Is a valueless attribute 
  336. $att_strs[] = esc_html( $val ); 
  337. } elseif ( isset( $val ) ) { // A regular attr - value pair 
  338. $att_strs[] = esc_html( $att ) . '=\'' . esc_html( $val ) . '\''; 
  339.  
  340. $html = '[contact-field ' . implode( ' ', $att_strs ); 
  341.  
  342. if ( isset( $content ) && ! empty( $content ) ) { // If there is content, let's add a closing tag 
  343. $html .= ']' . esc_html( $content ) . '[/contact-field]'; 
  344. } else { // Otherwise let's add a closing slash in the first tag 
  345. $html .= '/]'; 
  346.  
  347. return $html; 
  348.  
  349. $form = Grunion_Contact_Form::$current_form; 
  350.  
  351. $field = new Grunion_Contact_Form_Field( $attributes, $content, $form ); 
  352.  
  353. $field_id = $field->get_attribute( 'id' ); 
  354. if ( $field_id ) { 
  355. $form->fields[ $field_id ] = $field; 
  356. } else { 
  357. $form->fields[] = $field; 
  358.  
  359. if ( 
  360. isset( $_POST['action'] ) && 'grunion-contact-form' === $_POST['action'] 
  361. && 
  362. isset( $_POST['contact-form-id'] ) && $form->get_attribute( 'id' ) == $_POST['contact-form-id'] 
  363. ) { 
  364. // If we're processing a POST submission for this contact form, validate the field value so we can show errors as necessary. 
  365. $field->validate(); 
  366.  
  367. // Output HTML 
  368. return $field->render(); 
  369.  
  370. /** 
  371. * Loops through $this->fields to generate a (structured) list of field IDs. 
  372. * Important: Currently the whitelisted fields are defined as follows: 
  373. * `name`, `email`, `url`, `subject`, `textarea` 
  374. * If you need to add new fields to the Contact Form, please don't add them 
  375. * to the whitelisted fields and leave them as extra fields. 
  376. * The reasoning behind this is that both the admin Feedback view and the CSV 
  377. * export will not include any fields that are added to the list of 
  378. * whitelisted fields without taking proper care to add them to all the 
  379. * other places where they accessed/used/saved. 
  380. * The safest way to add new fields is to add them to the dropdown and the 
  381. * HTML list ( @see Grunion_Contact_Form_Field::render ) and don't add them 
  382. * to the list of whitelisted fields. This way they will become a part of the 
  383. * `extra fields` which are saved in the post meta and will be properly 
  384. * handled by the admin Feedback view and the CSV Export without any extra 
  385. * work. 
  386. * If there is need to add a field to the whitelisted fields, then please 
  387. * take proper care to add logic to handle the field in the following places: 
  388. * - Below in the switch statement - so the field is recognized as whitelisted. 
  389. * - Grunion_Contact_Form::process_submission - validation and logic. 
  390. * - Grunion_Contact_Form::process_submission - add the field as an additional 
  391. * field in the `post_content` when saving the feedback content. 
  392. * - Grunion_Contact_Form_Plugin::parse_fields_from_content - add mapping 
  393. * for the field, defined in the above method. 
  394. * - Grunion_Contact_Form_Plugin::map_parsed_field_contents_of_post_to_field_names - 
  395. * add mapping of the field for the CSV Export. Otherwise it will be missing 
  396. * from the exported data. 
  397. * - admin.php / grunion_manage_post_columns - add the field to the render logic. 
  398. * Otherwise it will be missing from the admin Feedback view. 
  399. * @return array 
  400. */ 
  401. function get_field_ids() { 
  402. $field_ids = array( 
  403. 'all' => array(), // array of all field_ids 
  404. 'extra' => array(), // array of all non-whitelisted field IDs 
  405.  
  406. // Whitelisted "standard" field IDs: 
  407. // 'email' => field_id,  
  408. // 'name' => field_id,  
  409. // 'url' => field_id,  
  410. // 'subject' => field_id,  
  411. // 'textarea' => field_id,  
  412. ); 
  413.  
  414. foreach ( $this->fields as $id => $field ) { 
  415. $field_ids['all'][] = $id; 
  416.  
  417. $type = $field->get_attribute( 'type' ); 
  418. if ( isset( $field_ids[ $type ] ) ) { 
  419. // This type of field is already present in our whitelist of "standard" fields for this form 
  420. // Put it in extra 
  421. $field_ids['extra'][] = $id; 
  422. continue; 
  423.  
  424. /** 
  425. * See method description before modifying the switch cases. 
  426. */ 
  427. switch ( $type ) { 
  428. case 'email' : 
  429. case 'name' : 
  430. case 'url' : 
  431. case 'subject' : 
  432. case 'textarea' : 
  433. $field_ids[ $type ] = $id; 
  434. break; 
  435. default : 
  436. // Put everything else in extra 
  437. $field_ids['extra'][] = $id; 
  438.  
  439. return $field_ids; 
  440.  
  441. /** 
  442. * Process the contact form's POST submission 
  443. * Stores feedback. Sends email. 
  444. */ 
  445. function process_submission() { 
  446. global $post; 
  447.  
  448. $plugin = Grunion_Contact_Form_Plugin::init(); 
  449.  
  450. $id = $this->get_attribute( 'id' ); 
  451. $to = $this->get_attribute( 'to' ); 
  452. $widget = $this->get_attribute( 'widget' ); 
  453.  
  454. $contact_form_subject = $this->get_attribute( 'subject' ); 
  455.  
  456. $to = str_replace( ' ', '', $to ); 
  457. $emails = explode( ', ', $to ); 
  458.  
  459. $valid_emails = array(); 
  460.  
  461. foreach ( (array) $emails as $email ) { 
  462. if ( ! is_email( $email ) ) { 
  463. continue; 
  464.  
  465. if ( function_exists( 'is_email_address_unsafe' ) && is_email_address_unsafe( $email ) ) { 
  466. continue; 
  467.  
  468. $valid_emails[] = $email; 
  469.  
  470. // No one to send it to, which means none of the "to" attributes are valid emails. 
  471. // Use default email instead. 
  472. if ( ! $valid_emails ) { 
  473. $valid_emails = $this->defaults['to']; 
  474.  
  475. $to = $valid_emails; 
  476.  
  477. // Last ditch effort to set a recipient if somehow none have been set. 
  478. if ( empty( $to ) ) { 
  479. $to = get_option( 'admin_email' ); 
  480.  
  481. // Make sure we're processing the form we think we're processing... probably a redundant check. 
  482. if ( $widget ) { 
  483. if ( 'widget-' . $widget != $_POST['contact-form-id'] ) { 
  484. return false; 
  485. } else { 
  486. if ( $post->ID != $_POST['contact-form-id'] ) { 
  487. return false; 
  488.  
  489. $field_ids = $this->get_field_ids(); 
  490.  
  491. // Initialize all these "standard" fields to null 
  492. $comment_author_email = $comment_author_email_label = // v 
  493. $comment_author = $comment_author_label = // v 
  494. $comment_author_url = $comment_author_url_label = // v 
  495. $comment_content = $comment_content_label = null; 
  496.  
  497. // For each of the "standard" fields, grab their field label and value. 
  498. if ( isset( $field_ids['name'] ) ) { 
  499. $field = $this->fields[ $field_ids['name'] ]; 
  500. $comment_author = Grunion_Contact_Form_Plugin::strip_tags( 
  501. stripslashes( 
  502. /** This filter is already documented in core/wp-includes/comment-functions.php */ 
  503. apply_filters( 'pre_comment_author_name', addslashes( $field->value ) ) 
  504. ); 
  505. $comment_author_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); 
  506.  
  507. if ( isset( $field_ids['email'] ) ) { 
  508. $field = $this->fields[ $field_ids['email'] ]; 
  509. $comment_author_email = Grunion_Contact_Form_Plugin::strip_tags( 
  510. stripslashes( 
  511. /** This filter is already documented in core/wp-includes/comment-functions.php */ 
  512. apply_filters( 'pre_comment_author_email', addslashes( $field->value ) ) 
  513. ); 
  514. $comment_author_email_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); 
  515.  
  516. if ( isset( $field_ids['url'] ) ) { 
  517. $field = $this->fields[ $field_ids['url'] ]; 
  518. $comment_author_url = Grunion_Contact_Form_Plugin::strip_tags( 
  519. stripslashes( 
  520. /** This filter is already documented in core/wp-includes/comment-functions.php */ 
  521. apply_filters( 'pre_comment_author_url', addslashes( $field->value ) ) 
  522. ); 
  523. if ( 'http://' == $comment_author_url ) { 
  524. $comment_author_url = ''; 
  525. $comment_author_url_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); 
  526.  
  527. if ( isset( $field_ids['textarea'] ) ) { 
  528. $field = $this->fields[ $field_ids['textarea'] ]; 
  529. $comment_content = trim( Grunion_Contact_Form_Plugin::strip_tags( $field->value ) ); 
  530. $comment_content_label = Grunion_Contact_Form_Plugin::strip_tags( $field->get_attribute( 'label' ) ); 
  531.  
  532. if ( isset( $field_ids['subject'] ) ) { 
  533. $field = $this->fields[ $field_ids['subject'] ]; 
  534. if ( $field->value ) { 
  535. $contact_form_subject = Grunion_Contact_Form_Plugin::strip_tags( $field->value ); 
  536.  
  537. $all_values = $extra_values = array(); 
  538. $i = 1; // Prefix counter for stored metadata 
  539.  
  540. // For all fields, grab label and value 
  541. foreach ( $field_ids['all'] as $field_id ) { 
  542. $field = $this->fields[ $field_id ]; 
  543. $label = $i . '_' . $field->get_attribute( 'label' ); 
  544. $value = $field->value; 
  545.  
  546. $all_values[ $label ] = $value; 
  547. $i++; // Increment prefix counter for the next field 
  548.  
  549. // For the "non-standard" fields, grab label and value 
  550. // Extra fields have their prefix starting from count( $all_values ) + 1 
  551. foreach ( $field_ids['extra'] as $field_id ) { 
  552. $field = $this->fields[ $field_id ]; 
  553. $label = $i . '_' . $field->get_attribute( 'label' ); 
  554. $value = $field->value; 
  555.  
  556. if ( is_array( $value ) ) { 
  557. $value = implode( ', ', $value ); 
  558.  
  559. $extra_values[ $label ] = $value; 
  560. $i++; // Increment prefix counter for the next extra field 
  561.  
  562. $contact_form_subject = trim( $contact_form_subject ); 
  563.  
  564. $comment_author_IP = Grunion_Contact_Form_Plugin::get_ip_address(); 
  565.  
  566. $vars = array( 'comment_author', 'comment_author_email', 'comment_author_url', 'contact_form_subject', 'comment_author_IP' ); 
  567. foreach ( $vars as $var ) { 
  568. $$var = str_replace( array( "\n", "\r" ), '', $$var ); 
  569.  
  570. // Ensure that Akismet gets all of the relevant information from the contact form,  
  571. // not just the textarea field and predetermined subject. 
  572. $akismet_vars = compact( $vars ); 
  573. $akismet_vars['comment_content'] = $comment_content; 
  574.  
  575. foreach ( array_merge( $field_ids['all'], $field_ids['extra'] ) as $field_id ) { 
  576. $field = $this->fields[ $field_id ]; 
  577.  
  578. // Skip any fields that are just a choice from a pre-defined list. They wouldn't have any value 
  579. // from a spam-filtering point of view. 
  580. if ( in_array( $field->get_attribute( 'type' ), array( 'select', 'checkbox', 'checkbox-multiple', 'radio' ) ) ) { 
  581. continue; 
  582.  
  583. // Normalize the label into a slug. 
  584. $field_slug = trim( // Strip all leading/trailing dashes. 
  585. preg_replace( // Normalize everything to a-z0-9_- 
  586. '/[^a-z0-9_]+/',  
  587. '-',  
  588. strtolower( $field->get_attribute( 'label' ) ) // Lowercase 
  589. ),  
  590. '-' 
  591. ); 
  592.  
  593. $field_value = ( is_array( $field->value ) ) ? trim( implode( ', ', $field->value ) ) : trim( $field->value ); 
  594.  
  595. // Skip any values that are already in the array we're sending. 
  596. if ( $field_value && in_array( $field_value, $akismet_vars ) ) { 
  597. continue; 
  598.  
  599. $akismet_vars[ 'contact_form_field_' . $field_slug ] = $field_value; 
  600.  
  601. $spam = ''; 
  602. $akismet_values = $plugin->prepare_for_akismet( $akismet_vars ); 
  603.  
  604. // Is it spam? 
  605. /** This filter is already documented in modules/contact-form/admin.php */ 
  606. $is_spam = apply_filters( 'jetpack_contact_form_is_spam', false, $akismet_values ); 
  607. if ( is_wp_error( $is_spam ) ) { // WP_Error to abort 
  608. return $is_spam; // abort 
  609. } elseif ( $is_spam === true ) { // TRUE to flag a spam 
  610. $spam = '***SPAM*** '; 
  611.  
  612. if ( ! $comment_author ) { 
  613. $comment_author = $comment_author_email; 
  614.  
  615. /** 
  616. * Filter the email where a submitted feedback is sent. 
  617. * @module contact-form 
  618. * @since 1.3.1 
  619. * @param string|array $to Array of valid email addresses, or single email address. 
  620. */ 
  621. $to = (array) apply_filters( 'contact_form_to', $to ); 
  622. $reply_to_addr = $to[0]; // get just the address part before the name part is added 
  623.  
  624. foreach ( $to as $to_key => $to_value ) { 
  625. $to[ $to_key ] = Grunion_Contact_Form_Plugin::strip_tags( $to_value ); 
  626. $to[ $to_key ] = self::add_name_to_address( $to_value ); 
  627.  
  628. $blog_url = parse_url( site_url() ); 
  629. $from_email_addr = 'wordpress@' . $blog_url['host']; 
  630.  
  631. if ( ! empty( $comment_author_email ) ) { 
  632. $reply_to_addr = $comment_author_email; 
  633.  
  634. $headers = 'From: "' . $comment_author . '" <' . $from_email_addr . ">\r\n" . 
  635. 'Reply-To: "' . $comment_author . '" <' . $reply_to_addr . ">\r\n"; 
  636.  
  637. // Build feedback reference 
  638. $feedback_time = current_time( 'mysql' ); 
  639. $feedback_title = "{$comment_author} - {$feedback_time}"; 
  640. $feedback_id = md5( $feedback_title ); 
  641.  
  642. $all_values = array_merge( $all_values, array( 
  643. 'entry_title' => the_title_attribute( 'echo=0' ),  
  644. 'entry_permalink' => esc_url( get_permalink( get_the_ID() ) ),  
  645. 'feedback_id' => $feedback_id,  
  646. ) ); 
  647.  
  648. /** This filter is already documented in modules/contact-form/admin.php */ 
  649. $subject = apply_filters( 'contact_form_subject', $contact_form_subject, $all_values ); 
  650. $url = $widget ? home_url( '/' ) : get_permalink( $post->ID ); 
  651.  
  652. $date_time_format = _x( '%1$s \a\t %2$s', '{$date_format} \a\t {$time_format}', 'jetpack' ); 
  653. $date_time_format = sprintf( $date_time_format, get_option( 'date_format' ), get_option( 'time_format' ) ); 
  654. $time = date_i18n( $date_time_format, current_time( 'timestamp' ) ); 
  655.  
  656. // keep a copy of the feedback as a custom post type 
  657. $feedback_status = $is_spam === true ? 'spam' : 'publish'; 
  658.  
  659. foreach ( (array) $akismet_values as $av_key => $av_value ) { 
  660. $akismet_values[ $av_key ] = Grunion_Contact_Form_Plugin::strip_tags( $av_value ); 
  661.  
  662. foreach ( (array) $all_values as $all_key => $all_value ) { 
  663. $all_values[ $all_key ] = Grunion_Contact_Form_Plugin::strip_tags( $all_value ); 
  664.  
  665. foreach ( (array) $extra_values as $ev_key => $ev_value ) { 
  666. $extra_values[ $ev_key ] = Grunion_Contact_Form_Plugin::strip_tags( $ev_value ); 
  667.  
  668. /** 
  669. We need to make sure that the post author is always zero for contact 
  670. * form submissions. This prevents export/import from trying to create 
  671. * new users based on form submissions from people who were logged in 
  672. * at the time. 
  673. * Unfortunately wp_insert_post() tries very hard to make sure the post 
  674. * author gets the currently logged in user id. That is how we ended up 
  675. * with this work around. */ 
  676. add_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10, 2 ); 
  677.  
  678. $post_id = wp_insert_post( array( 
  679. 'post_date' => addslashes( $feedback_time ),  
  680. 'post_type' => 'feedback',  
  681. 'post_status' => addslashes( $feedback_status ),  
  682. 'post_parent' => (int) $post->ID,  
  683. 'post_title' => addslashes( wp_kses( $feedback_title, array() ) ),  
  684. '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 
  685. 'post_name' => $feedback_id,  
  686. ) ); 
  687.  
  688. // once insert has finished we don't need this filter any more 
  689. remove_filter( 'wp_insert_post_data', array( $plugin, 'insert_feedback_filter' ), 10 ); 
  690.  
  691. update_post_meta( $post_id, '_feedback_extra_fields', $this->addslashes_deep( $extra_values ) ); 
  692.  
  693. if ( 'publish' == $feedback_status ) { 
  694. // Increase count of unread feedback. 
  695. $unread = get_option( 'feedback_unread_count', 0 ) + 1; 
  696. update_option( 'feedback_unread_count', $unread ); 
  697.  
  698. if ( defined( 'AKISMET_VERSION' ) ) { 
  699. update_post_meta( $post_id, '_feedback_akismet_values', $this->addslashes_deep( $akismet_values ) ); 
  700.  
  701. $message = self::get_compiled_form( $post_id, $this ); 
  702.  
  703. array_push( 
  704. $message,  
  705. "<br />",  
  706. '<hr />',  
  707. __( 'Time:', 'jetpack' ) . ' ' . $time . '<br />',  
  708. __( 'IP Address:', 'jetpack' ) . ' ' . $comment_author_IP . '<br />',  
  709. __( 'Contact Form URL:', 'jetpack' ) . ' ' . $url . '<br />' 
  710. ); 
  711.  
  712. if ( is_user_logged_in() ) { 
  713. array_push( 
  714. $message,  
  715. sprintf( 
  716. '<p>' . __( 'Sent by a verified %s user.', 'jetpack' ) . '</p>',  
  717. isset( $GLOBALS['current_site']->site_name ) && $GLOBALS['current_site']->site_name ? 
  718. $GLOBALS['current_site']->site_name : '"' . get_option( 'blogname' ) . '"' 
  719. ); 
  720. } else { 
  721. array_push( $message, '<p>' . __( 'Sent by an unverified visitor to your site.', 'jetpack' ) . '</p>' ); 
  722.  
  723. $message = join( $message, '' ); 
  724.  
  725. /** 
  726. * Filters the message sent via email after a successfull form submission. 
  727. * @module contact-form 
  728. * @since 1.3.1 
  729. * @param string $message Feedback email message. 
  730. */ 
  731. $message = apply_filters( 'contact_form_message', $message ); 
  732.  
  733. // This is called after `contact_form_message`, in order to preserve back-compat 
  734. $message = self::wrap_message_in_html_tags( $message ); 
  735.  
  736. update_post_meta( $post_id, '_feedback_email', $this->addslashes_deep( compact( 'to', 'message' ) ) ); 
  737.  
  738. /** 
  739. * Fires right before the contact form message is sent via email to 
  740. * the recipient specified in the contact form. 
  741. * @module contact-form 
  742. * @since 1.3.1 
  743. * @param integer $post_id Post contact form lives on 
  744. * @param array $all_values Contact form fields 
  745. * @param array $extra_values Contact form fields not included in $all_values 
  746. */ 
  747. do_action( 'grunion_pre_message_sent', $post_id, $all_values, $extra_values ); 
  748.  
  749. // schedule deletes of old spam feedbacks 
  750. if ( ! wp_next_scheduled( 'grunion_scheduled_delete' ) ) { 
  751. wp_schedule_event( time() + 250, 'daily', 'grunion_scheduled_delete' ); 
  752.  
  753. add_filter( 'wp_mail_content_type', __CLASS__ . '::get_mail_content_type' ); 
  754. add_action( 'phpmailer_init', __CLASS__ . '::add_plain_text_alternative' ); 
  755. if ( 
  756. $is_spam !== true && 
  757. /** 
  758. * Filter to choose whether an email should be sent after each successfull contact form submission. 
  759. * @module contact-form 
  760. * @since 2.6.0 
  761. * @param bool true Should an email be sent after a form submission. Default to true. 
  762. * @param int $post_id Post ID. 
  763. */ 
  764. true === apply_filters( 'grunion_should_send_email', true, $post_id ) 
  765. ) { 
  766. wp_mail( $to, "{$spam}{$subject}", $message, $headers ); 
  767. } elseif ( 
  768. true === $is_spam && 
  769. /** 
  770. * Choose whether an email should be sent for each spam contact form submission. 
  771. * @module contact-form 
  772. * @since 1.3.1 
  773. * @param bool false Should an email be sent after a spam form submission. Default to false. 
  774. */ 
  775. apply_filters( 'grunion_still_email_spam', false ) == true 
  776. ) { // don't send spam by default. Filterable. 
  777. wp_mail( $to, "{$spam}{$subject}", $message, $headers ); 
  778. remove_filter( 'wp_mail_content_type', __CLASS__ . '::get_mail_content_type' ); 
  779. remove_action( 'phpmailer_init', __CLASS__ . '::add_plain_text_alternative' ); 
  780.  
  781. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { 
  782. return self::success_message( $post_id, $this ); 
  783.  
  784. $redirect = wp_get_referer(); 
  785. if ( ! $redirect ) { // wp_get_referer() returns false if the referer is the same as the current page 
  786. $redirect = $_SERVER['REQUEST_URI']; 
  787.  
  788. $redirect = add_query_arg( urlencode_deep( array( 
  789. 'contact-form-id' => $id,  
  790. 'contact-form-sent' => $post_id,  
  791. '_wpnonce' => wp_create_nonce( "contact-form-sent-{$post_id}" ), // wp_nonce_url HTMLencodes :( 
  792. ) ), $redirect ); 
  793.  
  794. /** 
  795. * Filter the URL where the reader is redirected after submitting a form. 
  796. * @module contact-form 
  797. * @since 1.9.0 
  798. * @param string $redirect Post submission URL. 
  799. * @param int $id Contact Form ID. 
  800. * @param int $post_id Post ID. 
  801. */ 
  802. $redirect = apply_filters( 'grunion_contact_form_redirect_url', $redirect, $id, $post_id ); 
  803.  
  804. wp_safe_redirect( $redirect ); 
  805. exit; 
  806.  
  807. /** 
  808. * Add a display name part to an email address 
  809. * SpamAssassin doesn't like addresses in HTML messages that are missing display names (e.g., `foo@bar.org` 
  810. * instead of `"Foo Bar" <foo@bar.org>`. 
  811. * @param string $address 
  812. * @return string 
  813. */ 
  814. function add_name_to_address( $address ) { 
  815. // If it's just the address, without a display name 
  816. if ( is_email( $address ) ) { 
  817. $address = sprintf( '"%s" <%s>', $address, $address ); 
  818.  
  819. return $address; 
  820.  
  821. /** 
  822. * Get the content type that should be assigned to outbound emails 
  823. * @return string 
  824. */ 
  825. static function get_mail_content_type() { 
  826. return 'text/html'; 
  827.  
  828. /** 
  829. * Wrap a message body with the appropriate in HTML tags 
  830. * This helps to ensure correct parsing by clients, and also helps avoid triggering spam filtering rules 
  831. * @param string $body 
  832. * @return string 
  833. */ 
  834. static function wrap_message_in_html_tags( $body ) { 
  835. // Don't do anything if the message was already wrapped in HTML tags 
  836. // That could have be done by a plugin via filters 
  837. if ( false !== strpos( $body, '<html' ) ) { 
  838. return $body; 
  839.  
  840. $html_message = sprintf( 
  841. // The tabs are just here so that the raw code is correctly formatted for developers 
  842. // They're removed so that they don't affect the final message sent to users 
  843. str_replace( "\t", '',  
  844. "<!doctype html> 
  845. <html xmlns=\"http://www.w3.org/1999/xhtml\"> 
  846. <body> 
  847.  
  848. %s 
  849.  
  850. </body> 
  851. </html>" 
  852. ),  
  853. $body 
  854. ); 
  855.  
  856. return $html_message; 
  857.  
  858. /** 
  859. * Add a plain-text alternative part to an outbound email 
  860. * This makes the message more accessible to mail clients that aren't HTML-aware, and decreases the likelihood 
  861. * that the message will be flagged as spam. 
  862. * @param PHPMailer $phpmailer 
  863. */ 
  864. static function add_plain_text_alternative( $phpmailer ) { 
  865. // Add an extra break so that the extra space above the <p> is preserved after the <p> is stripped out 
  866. $alt_body = str_replace( '<p>', '<p><br />', $phpmailer->Body ); 
  867.  
  868. // Convert <br> to \n breaks, to preserve the space between lines that we want to keep 
  869. $alt_body = str_replace( array( '<br>', '<br />' ), "\n", $alt_body ); 
  870.  
  871. // Convert <hr> to an plain-text equivalent, to preserve the integrity of the message 
  872. $alt_body = str_replace( array( "<hr>", "<hr />" ), "----\n", $alt_body ); 
  873.  
  874. // Trim the plain text message to remove the \n breaks that were after <doctype>, <html>, and <body> 
  875. $phpmailer->AltBody = trim( strip_tags( $alt_body ) ); 
  876.  
  877. function addslashes_deep( $value ) { 
  878. if ( is_array( $value ) ) { 
  879. return array_map( array( $this, 'addslashes_deep' ), $value ); 
  880. } elseif ( is_object( $value ) ) { 
  881. $vars = get_object_vars( $value ); 
  882. foreach ( $vars as $key => $data ) { 
  883. $value->{$key} = $this->addslashes_deep( $data ); 
  884. return $value; 
  885.  
  886. return addslashes( $value );