dSanitizedInput.InputNotSanitized ) ); } $feedbacks = get_posts( $args ); if ( empty( $feedbacks ) ) { return; } /** * 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; } return $data; } /** * Download exported data as CSV */ public function download_feedback_as_csv() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verification is done on get_feedback_entries_from_post function $post_data = wp_unslash( $_POST ); $data = $this->get_feedback_entries_from_post(); if ( empty( $data ) ) { return; } // Check if we want to download all the feedbacks or just a certain contact form if ( ! empty( $post_data['post'] ) && $post_data['post'] !== 'all' ) { $filename = sprintf( '%s - %s.csv', Admin::init()->get_export_filename( get_the_title( (int) $post_data['post'] ) ), gmdate( 'Y-m-d H:i' ) ); } else { $filename = sprintf( '%s - %s.csv', Admin::init()->get_export_filename(), gmdate( 'Y-m-d H:i' ) ); } /** * 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 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose $this->record_tracks_event( 'forms_export_responses', array( 'format' => 'csv' ) ); exit(); } /** * Create a new post with a Form block */ public function create_new_form() { $post_id = wp_insert_post( array( 'post_title' => esc_html__( 'Jetpack Forms', 'jetpack-forms' ), 'post_content' => '
', ) ); if ( ! is_wp_error( $post_id ) ) { $array_result = array( 'post_url' => admin_url( 'post.php?post=' . intval( $post_id ) . '&action=edit' ), ); wp_send_json( $array_result ); } wp_die(); } /** * Send an event to Tracks * * @param string $event_name - the name of the event. * @param array $event_props - event properties to send. * * @return null|void */ public function record_tracks_event( $event_name, $event_props ) { /* * Event details. */ $event_user = wp_get_current_user(); /* * Record event. * We use different libs on wpcom and Jetpack. */ if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { $event_name = 'wpcom_' . $event_name; $event_props['blog_id'] = get_current_blog_id(); // logged out visitor, record event with blog owner. if ( empty( $event_user->ID ) ) { $event_user_id = wpcom_get_blog_owner( $event_props['blog_id'] ); $event_user = get_userdata( $event_user_id ); } require_lib( 'tracks/client' ); tracks_record_event( $event_user, $event_name, $event_props ); } else { $user_connected = ( new \Automattic\Jetpack\Connection\Manager( 'jetpack-forms' ) )->is_user_connected( get_current_user_id() ); if ( ! $user_connected ) { return; } // logged out visitor, record event with Jetpack master user. if ( empty( $event_user->ID ) ) { $master_user_id = Jetpack_Options::get_option( 'master_user' ); if ( ! empty( $master_user_id ) ) { $event_user = get_userdata( $master_user_id ); } } $tracking = new \Automattic\Jetpack\Tracking(); $tracking->record_user_event( $event_name, $event_props, $event_user ); } } /** * 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 https://www.contextis.com/en/blog/comma-separated-vulnerabilities * * @param string $field - the CSV 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 an array of parent post IDs for the user. * The parent posts are those posts where forms have been published. * * @param array $query_args A WP_Query compatible array of query args. * * @return array The array of post IDs */ public static function get_all_parent_post_ids( $query_args = array() ) { $default_query_args = array( 'fields' => 'id=>parent', 'posts_per_page' => 100000, // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page 'post_type' => 'feedback', 'post_status' => 'publish', 'suppress_filters' => false, ); $args = array_merge( $default_query_args, $query_args ); // Get the feedbacks' parents' post IDs $feedbacks = get_posts( $args ); return array_values( array_unique( array_values( $feedbacks ) ) ); } /** * Returns a string of HTML ', esc_attr( $parent_id ), $selected_id === $parent_id ? 'selected' : '', esc_html( basename( $parsed_url['path'] ) ) ); } 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; } /** * Returns if the feedback post has JSON data * * @param int $post_id The feedback post ID to check. * @return bool */ public function has_json_data( $post_id ) { $post_content = get_post_field( 'post_content', $post_id ); $content = explode( "\nJSON_DATA", $post_content ); if ( empty( $content[1] ) ) { return false; } $json_data = json_decode( $content[1], true ); return is_array( $json_data ) && ! empty( $json_data ); } /** * Parse the contact form fields. * * @param int $post_id - the post ID. * @return array 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( '', $post_content ); $lines = array(); if ( count( $content ) > 1 ) { $content = str_ireplace( array( '