post_type ) { wp_die( esc_html__( 'That form does not exist.', '2ranq-forms' ) ); } $fields = self::input_fields( $form_id ); nocache_headers(); header( 'Content-Type: text/csv; charset=UTF-8' ); header( 'Content-Disposition: attachment; filename="' . self::filename( $form ) . '"' ); $out = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- this is not a file on disk but the output to the browser. // The mark at the start of the file. Without it Excel on Windows reads our // letters as rubbish, and "Đorđe" becomes "Ä‘orÄ‘e". echo "\xEF\xBB\xBF"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- three bytes of the mark, there is nothing to escape. $header = array( __( 'Entry', '2ranq-forms' ), __( 'Date', '2ranq-forms' ), ); foreach ( $fields as $field ) { $header[] = isset( $field['label'] ) && '' !== $field['label'] ? $field['label'] : $field['id']; } $header[] = __( 'IP address', '2ranq-forms' ); fputcsv( $out, $header ); $offset = 0; do { $rows = Ranq_Forms_Entries::get_for_form( $form_id, self::CHUNK, $offset ); foreach ( $rows as $row ) { fputcsv( $out, self::line( $row, $fields ) ); } $offset += self::CHUNK; // The output is pushed to the browser at once, so a large export does not wait in memory. flush(); } while ( count( $rows ) === self::CHUNK ); fclose( $out ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- output to the browser, not a file on disk. exit; } /** * One row. * * @param array $row The record out of the database. * @param array $fields The form fields. * * @return array */ private static function line( $row, $fields ) { $values = json_decode( isset( $row['fields'] ) ? $row['fields'] : '', true ); $values = is_array( $values ) ? $values : array(); $line = array( self::safe( $row['id'] ), self::safe( get_date_from_gmt( $row['created_at'], 'Y-m-d H:i' ) ), ); foreach ( $fields as $field ) { $key = $field['id']; $object = Ranq_Forms_Fields::get( $field['type'] ); $value = isset( $values[ $key ] ) ? $values[ $key ] : ''; if ( $object instanceof Ranq_Forms_Field ) { $value = $object->format_value( $value, $field ); } $line[] = self::safe( $value ); } $line[] = self::safe( isset( $row['ip'] ) ? $row['ip'] : '' ); return $line; } /** * Prepares one value for the spreadsheet. * * @param mixed $value The value. * * @return string */ private static function safe( $value ) { if ( is_array( $value ) ) { $value = implode( ', ', array_map( 'strval', $value ) ); } $value = (string) $value; // A formula in an entry is an attack, not a mistake. Excel runs whatever starts // with these characters the moment the file is opened, so an apostrophe goes in // front of them, which the spreadsheet does not show but the formula dies on. if ( '' !== $value && false !== strpos( "=+-@\t\r", $value[0] ) ) { $value = "'" . $value; } return $value; } /** * The fields that really take input. * * @param int $form_id The form id. * * @return array */ private static function input_fields( $form_id ) { $out = array(); foreach ( Ranq_Forms_Fields::get_form_fields( $form_id ) as $field ) { $object = isset( $field['type'] ) ? Ranq_Forms_Fields::get( $field['type'] ) : null; if ( $object instanceof Ranq_Forms_Field && $object->is_input() ) { $out[] = $field; } } return $out; } /** * The file name. * * @param WP_Post $form The form. * * @return string */ private static function filename( $form ) { $slug = sanitize_title( get_the_title( $form ) ); $slug = $slug ? $slug : 'form-' . $form->ID; return $slug . '-' . gmdate( 'Y-m-d' ) . '.csv'; } }