get_error_message() ); } $name = 'accessible-forms-' . gmdate( 'Y-m-d' ) . ( $with_recipients ? '-with-personal-data' : '' ) . '.json'; nocache_headers(); header( 'Content-Type: application/json; charset=utf-8' ); header( 'X-Content-Type-Options: nosniff' ); header( 'Content-Disposition: attachment; filename="' . $name . '"' ); echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON attachment, not HTML. exit(); case 'import_review': $package = Package::decode( self::uploaded() ); if ( is_wp_error( $package ) ) { Management::fail( $package->get_error_message() ); } set_transient( self::REVIEW . get_current_user_id(), array( 'package' => $package, 'forms' => FormTransfer::review( $package ), 'token' => wp_generate_password( 24, false ), ), 30 * MINUTE_IN_SECONDS ); delete_transient( self::RESULT . get_current_user_id() ); wp_safe_redirect( Management::url( self::TAB ) ); exit(); case 'import_apply': $review = get_transient( self::REVIEW . get_current_user_id() ); $token = sanitize_text_field( wp_unslash( $_POST['review_token'] ?? '' ) ); if ( ! is_array( $review ) || ! is_string( $review['token'] ?? null ) || ! hash_equals( $review['token'], $token ) ) { Management::fail( __( 'This review has expired or was already used. Choose the file again.', 'accessible-forms-by-accessible-org' ) ); } $package = $review['package']; if ( empty( $package['checksum_ok'] ) && empty( $_POST['accept_changed'] ) ) { Management::fail( __( 'This file was changed after it was exported. Confirm that you want to import it anyway, or export it again.', 'accessible-forms-by-accessible-org' ) ); } $choices = array_map( 'sanitize_key', (array) wp_unslash( $_POST['choice'] ?? array() ) ); $recipients = array(); foreach ( FormTransfer::review( $package ) as $row ) { $index = $row['index']; // Only addresses from the file that were ticked, plus ones typed here. $ticked = array_intersect( $row['recipients'], (array) wp_unslash( $_POST['recipients'][ $index ] ?? array() ) ); $typed = preg_split( '/[\s,;]+/', (string) wp_unslash( $_POST['recipient_extra'][ $index ] ?? '' ) ); $recipients[ $index ] = FormTransfer::emails( array_merge( $ticked, $typed ) ); } $results = FormTransfer::import( $package, $choices, $recipients, $review['forms'] ?? array() ); $settings = null; if ( ! empty( $package['settings'] ) && current_user_can( 'manage_accessible_forms_settings' ) ) { $settings = SettingsTransfer::apply( $package['settings'], array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['settings'] ?? array() ) ) ); $settings = is_wp_error( $settings ) ? $settings->get_error_message() : (int) $settings; } // Add-ons import their own sections and may add result lines. $results = (array) Extensions::filter( 'accessible_forms_import_applied', $results, $package, $_POST ); delete_transient( self::REVIEW . get_current_user_id() ); set_transient( self::RESULT . get_current_user_id(), array( 'forms' => $results, 'settings' => $settings, ), 10 * MINUTE_IN_SECONDS ); wp_safe_redirect( Management::url( self::TAB ) ); exit(); case 'import_cancel': delete_transient( self::REVIEW . get_current_user_id() ); wp_safe_redirect( Management::url( self::TAB ) ); exit(); } // phpcs:enable WordPress.Security.NonceVerification.Missing } /** File contents from the upload, or pasted text. */ private static function uploaded() { // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified by Management::action(). $file = $_FILES['af_import_file'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Only the server's temporary path and status are read. if ( is_array( $file ) && UPLOAD_ERR_NO_FILE !== (int) ( $file['error'] ?? UPLOAD_ERR_NO_FILE ) ) { if ( UPLOAD_ERR_OK !== (int) $file['error'] || ! is_uploaded_file( $file['tmp_name'] ) ) { Management::fail( __( 'The file could not be uploaded. Try again, or paste its contents instead.', 'accessible-forms-by-accessible-org' ) ); } if ( (int) $file['size'] > Package::MAX_BYTES ) { Management::fail( __( 'This file is larger than 1 MB. Export fewer forms at a time.', 'accessible-forms-by-accessible-org' ) ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading the server's temporary upload. return (string) file_get_contents( $file['tmp_name'] ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Raw JSON text; Package::decode() bounds and decodes it, and every value is sanitized on import. return isset( $_POST['json'] ) && is_string( $_POST['json'] ) ? wp_unslash( $_POST['json'] ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing } public static function render() { $user = get_current_user_id(); $result = get_transient( self::RESULT . $user ); if ( is_array( $result ) ) { self::results( $result ); delete_transient( self::RESULT . $user ); } $review = get_transient( self::REVIEW . $user ); if ( is_array( $review ) && is_array( $review['package'] ?? null ) ) { self::review( $review['package'], (string) $review['token'], $review['forms'] ?? array() ); return; } self::export_section(); self::import_section(); // Add-ons add their own tools, such as saved templates. Extensions::notify( 'accessible_forms_transfer_sections' ); } private static function export_section() { $forms = FormTransfer::exportable(); echo '

' . esc_html__( 'Export', 'accessible-forms-by-accessible-org' ) . '

'; echo '

' . esc_html__( 'Download forms as one file to import on another site, or to keep as a backup. A published form exports its published version. Entries, passwords, keys and team access are never included.', 'accessible-forms-by-accessible-org' ) . '

'; if ( ! $forms && ! current_user_can( 'manage_accessible_forms_settings' ) ) { echo '

' . esc_html__( 'There are no forms to export yet.', 'accessible-forms-by-accessible-org' ) . '

'; return; } Management::form_start( 'export_package' ); if ( $forms ) { $all = count( $forms ) <= Package::MAX_FORMS; echo '
' . esc_html__( 'Forms to export', 'accessible-forms-by-accessible-org' ) . ''; echo '

' . esc_html__( 'Each file can contain up to 100 forms and 1 MB. Choose fewer forms if the export exceeds these limits.', 'accessible-forms-by-accessible-org' ) . '

'; echo '

'; echo '

'; echo ''; if ( count( $forms ) >= FormTransfer::LIST_LIMIT ) { echo '

' . esc_html__( 'Only the first 500 forms by name are listed.', 'accessible-forms-by-accessible-org' ) . '

'; } echo '
'; echo '


' . esc_html__( 'Email addresses are personal data. On import, each one is shown and must be ticked again.', 'accessible-forms-by-accessible-org' ) . '

'; } if ( current_user_can( 'manage_accessible_forms_settings' ) ) { echo '


' . esc_html__( 'Sender, spam protection and retention. Never passwords, API keys or test delivery.', 'accessible-forms-by-accessible-org' ) . '

'; } // Add-ons may offer more sections, such as saved templates. Extensions::notify( 'accessible_forms_export_options' ); Management::submit( __( 'Download export file', 'accessible-forms-by-accessible-org' ), 'secondary' ); echo ''; } private static function import_section() { echo '

' . esc_html__( 'Import', 'accessible-forms-by-accessible-org' ) . '

'; echo '

' . esc_html__( 'Choose an export file. You will see what it contains and what would happen to each form before anything changes. Imported forms are drafts.', 'accessible-forms-by-accessible-org' ) . '

'; echo '
'; wp_referer_field(); echo '

' . esc_html__( 'A .json file up to 1 MB, made by Accessible Forms.', 'accessible-forms-by-accessible-org' ) . '

'; echo '
' . esc_html__( 'Paste the file’s contents instead', 'accessible-forms-by-accessible-org' ) . ''; Editor::input( 'json', __( 'Export file contents', 'accessible-forms-by-accessible-org' ), '', 'textarea' ); echo '
'; Management::submit( __( 'Review import', 'accessible-forms-by-accessible-org' ) ); echo '
'; } private static function review( array $package, $token, array $rows ) { $can_setting = current_user_can( 'manage_accessible_forms_settings' ); $settings = ! empty( $package['settings'] ) && is_array( $package['settings'] ) ? SettingsTransfer::review( $package['settings'] ) : array(); echo '

' . esc_html__( 'Review import', 'accessible-forms-by-accessible-org' ) . '

'; $made = is_string( $package['generator']['free'] ?? null ) ? $package['generator']['free'] : ''; $when = is_string( $package['created_at'] ?? null ) ? strtotime( $package['created_at'] ) : false; echo '

' . esc_html( trim( sprintf( /* translators: 1: number of forms, 2: version, 3: date. */ _n( 'This file has %1$s form. %2$s %3$s', 'This file has %1$s forms. %2$s %3$s', count( $rows ), 'accessible-forms-by-accessible-org' ), number_format_i18n( count( $rows ) ), /* translators: %s: plugin version. */ $made ? sprintf( __( 'Made by Accessible Forms %s', 'accessible-forms-by-accessible-org' ), $made ) : ( 1 === ( $package['source_version'] ?? 0 ) ? __( 'Made by an earlier version (one form, no recipients).', 'accessible-forms-by-accessible-org' ) : '' ), /* translators: %s: date. */ $when ? sprintf( __( 'on %s.', 'accessible-forms-by-accessible-org' ), wp_date( get_option( 'date_format' ), $when ) ) : '' ) ) ) . '

'; echo '

' . esc_html__( 'Nothing changes until you choose Import. Forms are imported as drafts; publish each one after checking it.', 'accessible-forms-by-accessible-org' ) . '

'; Management::form_start( 'import_apply', array( 'review_token' => $token ) ); if ( empty( $package['checksum_ok'] ) ) { echo '

' . esc_html__( 'This file was changed after it was exported, or it is incomplete. Import it only if you trust where it came from.', 'accessible-forms-by-accessible-org' ) . '

'; } foreach ( $rows as $row ) { self::review_row( $row ); } if ( $settings ) { self::review_settings( $settings, $can_setting ); } // Add-ons review their own sections, such as saved templates. Extensions::notify( 'accessible_forms_import_review', $package ); Management::submit( __( 'Import', 'accessible-forms-by-accessible-org' ) ); echo ''; Management::form_start( 'import_cancel' ); Management::submit( __( 'Cancel and choose another file', 'accessible-forms-by-accessible-org' ), 'secondary' ); echo '
'; } private static function review_row( array $row ) { $index = (int) $row['index']; $base = 'af-import-' . $index; echo '
' . esc_html( $row['title'] ) . ''; if ( $row['match'] ) { $status = sprintf( /* translators: %s: form name on this site. */ __( 'This is the same form as “%s” on this site.', 'accessible-forms-by-accessible-org' ), get_the_title( $row['match'] ) ); } elseif ( $row['same_name'] ) { $status = __( 'A different form on this site already has this name. The import is added beside it.', 'accessible-forms-by-accessible-org' ); } else { $status = __( 'New on this site.', 'accessible-forms-by-accessible-org' ); } echo '

' . esc_html( $status ) . '

'; $types = \AccessibleForms\Schema::field_types(); $fields = array(); foreach ( $row['schema']['fields'] as $field ) { $fields[] = sprintf( /* translators: 1: question label, 2: field type, 3: ", required" or nothing. */ __( '%1$s (%2$s%3$s)', 'accessible-forms-by-accessible-org' ), $field['label'], $types[ $field['type'] ]['label'] ?? $field['type'], $field['required'] ? __( ', required', 'accessible-forms-by-accessible-org' ) : '' ); } if ( $fields ) { echo '

' . esc_html__( 'Questions:', 'accessible-forms-by-accessible-org' ) . ' ' . esc_html( implode( '; ', $fields ) ) . '

'; } if ( $row['errors'] ) { echo '

' . esc_html__( 'This form cannot be imported:', 'accessible-forms-by-accessible-org' ) . '

'; } if ( $row['warnings'] ) { echo ''; } if ( $row['placements'] ) { $places = array(); foreach ( $row['placements'] as $place ) { $places[] = $place['title'] ? $place['title'] . ' (' . $place['path'] . ')' : $place['path']; } echo '

' . esc_html( sprintf( /* translators: %s: list of pages. */ __( 'On the original site it was placed on: %s. Place it here after publishing.', 'accessible-forms-by-accessible-org' ), implode( ', ', $places ) ) ) . '

'; } // Add-ons may describe their choice, such as what an update would change. Extensions::notify( 'accessible_forms_import_row', $row ); $choices = FormTransfer::choices( $row ); $default = $row['errors'] ? 'skip' : ( isset( $choices['update'] ) ? 'update' : 'new' ); echo '
' . esc_html__( 'What to do', 'accessible-forms-by-accessible-org' ) . ''; foreach ( $choices as $value => $label ) { $id = $base . '-choice-' . $value; $disabled = $row['errors'] && 'skip' !== $value; echo '

'; } echo '
'; if ( ! $row['errors'] ) { echo '
' . esc_html__( 'Notification recipients for a new form', 'accessible-forms-by-accessible-org' ) . ''; foreach ( $row['recipients'] as $position => $email ) { $id = $base . '-recipient-' . $position; echo '

'; } $help = $row['recipients'] ? __( 'Recipients from the file are not used until you tick them. Add others separated by commas. At least one is needed.', 'accessible-forms-by-accessible-org' ) : __( 'The file has no recipients. Add at least one, separated by commas.', 'accessible-forms-by-accessible-org' ); $user = wp_get_current_user(); Editor::input( 'recipient_extra[' . $index . ']', __( 'Other recipients', 'accessible-forms-by-accessible-org' ), $row['recipients'] ? '' : (string) $user->user_email, 'text', $help, array( 'autocomplete' => 'email' ) ); echo '
'; } echo '
'; } private static function review_settings( array $rows, $can ) { echo '
' . esc_html__( 'Settings in this file', 'accessible-forms-by-accessible-org' ) . ''; if ( ! $can ) { echo '

' . esc_html__( 'Only someone who manages settings can import them, so they are ignored.', 'accessible-forms-by-accessible-org' ) . '

'; return; } echo '

' . esc_html__( 'Tick each setting to change. Unticked settings keep their current value.', 'accessible-forms-by-accessible-org' ) . '

'; echo '
'; foreach ( $rows as $position => $row ) { $id = 'af-import-setting-' . $position; $disabled = $row['note'] || ! $row['changed']; echo ''; } echo '
' . esc_html__( 'Change', 'accessible-forms-by-accessible-org' ) . '' . esc_html__( 'Setting', 'accessible-forms-by-accessible-org' ) . '' . esc_html__( 'On this site', 'accessible-forms-by-accessible-org' ) . '' . esc_html__( 'In the file', 'accessible-forms-by-accessible-org' ) . '
'; if ( $row['note'] ) { echo '
' . esc_html( $row['note'] ) . ''; } elseif ( ! $row['changed'] ) { echo '
' . esc_html__( 'Already the same.', 'accessible-forms-by-accessible-org' ) . ''; } echo '
' . esc_html( $row['current'] ) . '' . esc_html( $row['incoming'] ) . '
'; } private static function results( array $result ) { echo '

' . esc_html__( 'Import finished', 'accessible-forms-by-accessible-org' ) . '

'; } private static function status_label( $status ) { $labels = array( 'publish' => __( 'Published', 'accessible-forms-by-accessible-org' ), 'future' => __( 'Scheduled', 'accessible-forms-by-accessible-org' ), 'draft' => __( 'Draft', 'accessible-forms-by-accessible-org' ), 'pending' => __( 'Pending review', 'accessible-forms-by-accessible-org' ), 'private' => __( 'Private', 'accessible-forms-by-accessible-org' ), ); return $labels[ $status ] ?? $status; } }