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__( '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 ''; echo '
' . esc_html__( 'Email addresses are personal data. On import, each one is shown and must be ticked again.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Sender, spam protection and retention. Never passwords, API keys or test delivery.', 'accessible-forms-by-accessible-org' ) . '
' . 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 ''; } 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( 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' ) . '
' . esc_html__( 'Only someone who manages settings can import them, so they are ignored.', 'accessible-forms-by-accessible-org' ) . '
' . esc_html__( 'Tick each setting to change. Unticked settings keep their current value.', 'accessible-forms-by-accessible-org' ) . '
'; 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'] ) . ' |