1, 'version' => defined( 'BYLT_VERSION' ) ? BYLT_VERSION : '', 'site' => home_url(), 'exported' => gmdate( 'c' ), 'forms' => array(), 'entries' => array(), ); foreach ( $forms as $f ) { $out['forms'][] = array( 'ref' => $f['id'], 'name' => $f['name'], 'fields' => $f['fields'], 'settings' => array_diff_key( $f['settings'], array( 'page_id' => 1 ) ), // Page ids don't carry across sites. ); if ( $with_entries ) { $page = 1; do { $res = Bylt_Forms_Store::entries( array_merge( $entry_args, array( 'form_id' => $f['id'], 'page' => $page++, 'per_page' => 500 ) ) ); foreach ( $res['items'] as $e ) { $out['entries'][] = array( 'form' => $f['id'], 'status' => $e['status'], 'email' => $e['email'], 'data' => array_diff_key( $e['data'], array( '_files' => 1 ) ), // Uploaded files stay on this site. 'created_gmt' => $e['created_gmt'], 'page_url' => $e['page_url'], 'product_id' => $e['product_id'], ); } } while ( 500 === count( $res['items'] ) ); } } if ( ! $with_entries ) { unset( $out['entries'] ); } return wp_json_encode( $out, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ); } /** CSV of one form's entries: Entry ID, Submitted, Status, each field, Page, Product ID. */ public static function export_csv( $form_id, array $entry_args = array(), $handle = null ) { $form = Bylt_Forms_Store::get( $form_id ); if ( ! $form ) { return ''; } $own = null === $handle; $h = $own ? fopen( 'php://temp', 'w+' ) : $handle; // phpcs:ignore WordPress.WP.AlternativeFunctions $keys = array(); $head = array( 'Entry ID', 'Submitted', 'Status' ); foreach ( $form['fields'] as $f ) { if ( 'html' !== $f['type'] ) { $keys[] = $f['key']; $head[] = $f['label']; } } // Keys from removed fields that still appear in entries. $extra = array(); $rows = array(); $page = 1; do { $res = Bylt_Forms_Store::entries( array_merge( $entry_args, array( 'form_id' => $form_id, 'page' => $page++, 'per_page' => 500 ) ) ); foreach ( $res['items'] as $e ) { foreach ( array_keys( $e['data'] ) as $k ) { if ( '_' !== substr( (string) $k, 0, 1 ) && ! in_array( $k, $keys, true ) && ! in_array( $k, $extra, true ) ) { $extra[] = $k; } } $rows[] = $e; } } while ( 500 === count( $res['items'] ) ); $head = array_merge( $head, $extra, array( 'Page', 'Product ID' ) ); fputcsv( $h, self::safe_row( $head ) ); foreach ( $rows as $e ) { $line = array( $e['id'], get_date_from_gmt( $e['created_gmt'], 'Y-m-d H:i:s' ), $e['status'] ); foreach ( array_merge( $keys, $extra ) as $k ) { $line[] = isset( $e['data'][ $k ] ) ? Bylt_Forms_Mailer::text( $e['data'][ $k ] ) : ''; } $line[] = $e['page_url']; $line[] = $e['product_id'] ? $e['product_id'] : ''; fputcsv( $h, self::safe_row( $line ) ); } if ( ! $own ) { return ''; } rewind( $h ); $csv = stream_get_contents( $h ); fclose( $h ); // phpcs:ignore WordPress.WP.AlternativeFunctions return $csv; } /** Stop spreadsheet apps running a submitted value as a formula. */ private static function safe_row( array $row ) { return array_map( function ( $v ) { $v = (string) $v; return '' !== $v && in_array( $v[0], array( '=', '+', '-', '@', "\t", "\r" ), true ) && ! is_numeric( $v ) ? "'" . $v : $v; }, $row ); } /* --------------------------------------------------------------------- * Import * ------------------------------------------------------------------- */ private static function form_by_name( $name ) { foreach ( Bylt_Forms_Store::all() as $f ) { if ( 0 === strcasecmp( $f['name'], $name ) ) { return $f; } } return null; } private static function entry_exists( $form_id, $created, $email, array $data ) { global $wpdb; $t = Bylt_Forms_Store::table(); return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$t} WHERE form_id = %d AND created_gmt = %s AND email = %s AND data = %s LIMIT 1", $form_id, $created, $email, wp_json_encode( $data ) ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery } /** * Import a BYLTCommerce forms JSON file. * * @param string $mode 'merge' (a form with the same name is reused, entries go into it) or 'copy' (always add new forms). * @return array{forms_added: int, forms_reused: int, entries: int, skipped: int}|WP_Error */ public static function import_json( $json, $mode = 'merge' ) { $in = json_decode( (string) $json, true ); if ( ! is_array( $in ) || empty( $in['bylt_forms'] ) || ! isset( $in['forms'] ) || ! is_array( $in['forms'] ) ) { return new WP_Error( 'bylt_forms_file', __( 'That isn\'t a BYLTCommerce forms export file.', 'byltcommerce-toolkit' ) ); } $report = array( 'forms_added' => 0, 'forms_reused' => 0, 'entries' => 0, 'skipped' => 0 ); $map = array(); foreach ( $in['forms'] as $f ) { if ( ! is_array( $f ) || empty( $f['name'] ) ) { continue; } $existing = 'merge' === $mode ? self::form_by_name( $f['name'] ) : null; if ( $existing ) { $id = $existing['id']; ++$report['forms_reused']; } else { $name = $f['name']; if ( 'copy' === $mode && self::form_by_name( $name ) ) { /* translators: %s: form name */ $name = sprintf( __( '%s (imported)', 'byltcommerce-toolkit' ), $name ); } $id = Bylt_Forms_Store::save( array( 'name' => $name, 'fields' => isset( $f['fields'] ) ? (array) $f['fields'] : array(), 'settings' => isset( $f['settings'] ) ? (array) $f['settings'] : array(), ) ); ++$report['forms_added']; } if ( isset( $f['ref'] ) ) { $map[ (string) $f['ref'] ] = $id; } } foreach ( isset( $in['entries'] ) && is_array( $in['entries'] ) ? $in['entries'] : array() as $e ) { $ref = isset( $e['form'] ) ? (string) $e['form'] : ''; if ( ! isset( $map[ $ref ] ) || ! isset( $e['data'] ) || ! is_array( $e['data'] ) ) { ++$report['skipped']; continue; } $data = self::clean_data( $e['data'] ); $created = isset( $e['created_gmt'] ) && preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $e['created_gmt'] ) ? $e['created_gmt'] : gmdate( 'Y-m-d H:i:s' ); $email = isset( $e['email'] ) ? sanitize_email( $e['email'] ) : ''; if ( self::entry_exists( $map[ $ref ], $created, $email, $data ) ) { ++$report['skipped']; continue; } Bylt_Forms_Store::add_entry( array( 'form_id' => $map[ $ref ], 'status' => isset( $e['status'] ) ? $e['status'] : 'read', 'email' => $email, 'data' => $data, 'created_gmt' => $created, 'page_url' => isset( $e['page_url'] ) ? $e['page_url'] : '', 'product_id' => isset( $e['product_id'] ) ? (int) $e['product_id'] : 0, ) ); ++$report['entries']; } return $report; } private static function clean_data( array $data ) { $out = array(); foreach ( $data as $k => $v ) { $k = sanitize_key( $k ); if ( '' === $k ) { continue; } $out[ $k ] = is_array( $v ) ? array_values( array_map( 'sanitize_text_field', $v ) ) : sanitize_textarea_field( (string) $v ); } return $out; } /** * Import entries from a CSV into a form. Columns are matched to fields by key or label; * "Submitted" (site time) and "Status" are used when present; other columns are ignored. * * @return array{entries: int, skipped: int, matched: string[]}|WP_Error */ public static function import_csv( $form_id, $path ) { $form = Bylt_Forms_Store::get( $form_id ); if ( ! $form ) { return new WP_Error( 'bylt_forms_form', __( 'Choose the form to import into.', 'byltcommerce-toolkit' ) ); } $h = fopen( $path, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions if ( ! $h ) { return new WP_Error( 'bylt_forms_file', __( 'Couldn\'t read the file.', 'byltcommerce-toolkit' ) ); } $head = fgetcsv( $h ); if ( ! $head ) { fclose( $h ); // phpcs:ignore WordPress.WP.AlternativeFunctions return new WP_Error( 'bylt_forms_file', __( 'The file is empty.', 'byltcommerce-toolkit' ) ); } $head[0] = preg_replace( '/^\xEF\xBB\xBF/', '', (string) $head[0] ); // Excel's BOM. $cols = array(); $fields = array(); foreach ( $form['fields'] as $f ) { if ( 'html' !== $f['type'] ) { $fields[ $f['key'] ] = $f; } } $matched = array(); foreach ( $head as $i => $label ) { $l = strtolower( trim( (string) $label ) ); if ( in_array( $l, array( 'submitted', 'date', 'created_gmt' ), true ) ) { $cols[ $i ] = '_date'; continue; } if ( 'status' === $l ) { $cols[ $i ] = '_status'; continue; } foreach ( $fields as $k => $f ) { if ( strtolower( $k ) === $l || strtolower( $f['label'] ) === $l ) { $cols[ $i ] = $k; $matched[] = $f['label']; break; } } } if ( ! $matched ) { fclose( $h ); // phpcs:ignore WordPress.WP.AlternativeFunctions return new WP_Error( 'bylt_forms_cols', __( 'None of the column headings match this form\'s fields (use the field labels or keys).', 'byltcommerce-toolkit' ) ); } $report = array( 'entries' => 0, 'skipped' => 0, 'matched' => $matched ); while ( false !== ( $row = fgetcsv( $h ) ) ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition if ( ! array_filter( $row, 'bylt_filled' ) ) { continue; } $data = array(); $created = gmdate( 'Y-m-d H:i:s' ); $status = 'read'; foreach ( $cols as $i => $k ) { $v = isset( $row[ $i ] ) ? (string) $row[ $i ] : ''; $v = "'" === substr( $v, 0, 1 ) && strlen( $v ) > 1 && in_array( $v[1], array( '=', '+', '-', '@' ), true ) ? substr( $v, 1 ) : $v; // Undo our formula guard. if ( '_date' === $k ) { $t = strtotime( $v ); $created = $t ? get_gmt_from_date( gmdate( 'Y-m-d H:i:s', $t ) ) : $created; } elseif ( '_status' === $k ) { $status = in_array( $v, Bylt_Forms_Store::STATUSES, true ) ? $v : 'read'; } elseif ( 'checkboxes' === $fields[ $k ]['type'] ) { $data[ $k ] = '' === $v ? array() : array_map( 'trim', explode( ',', $v ) ); } else { $data[ $k ] = $v; } } $data = self::clean_data( $data ); $email = Bylt_Forms_Render::first_email( $form, $data ); if ( self::entry_exists( $form['id'], $created, $email, $data ) ) { ++$report['skipped']; continue; } Bylt_Forms_Store::add_entry( array( 'form_id' => $form['id'], 'status' => $status, 'email' => $email, 'data' => $data, 'created_gmt' => $created, ) ); ++$report['entries']; } fclose( $h ); // phpcs:ignore WordPress.WP.AlternativeFunctions return $report; } }