403 ) ); } // phpcs:disable WordPress.Security.NonceVerification.Missing -- Verified above. $form_id = isset( $_POST['post_ID'] ) ? absint( wp_unslash( $_POST['post_ID'] ) ) : 0; $post = get_post( $form_id ); if ( ! $post instanceof \WP_Post || Form_Post_Type::POST_TYPE !== $post->post_type ) { wp_die( esc_html__( 'Form not found.', 'flexsolutions-form-builder' ), '', array( 'response' => 404 ) ); } if ( ! current_user_can( 'edit_post', $form_id ) ) { wp_die( esc_html__( 'Sorry, you are not allowed to preview this form.', 'flexsolutions-form-builder' ), '', array( 'response' => 403 ) ); } $posted = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized key by key in create(). // phpcs:enable WordPress.Security.NonceVerification.Missing wp_safe_redirect( self::url( self::create( $form_id, $posted ) ), 303 ); exit; } /** * Keeps what a save of these fields would write, and returns its token. * * @param int $form_id Form post ID. * @param array $posted Unslashed editor fields. */ public static function create( int $form_id, array $posted ): string { $title = isset( $posted['post_title'] ) && is_string( $posted['post_title'] ) ? sanitize_text_field( $posted['post_title'] ) : null; $token = bin2hex( random_bytes( 16 ) ); set_transient( self::TRANSIENT . $token, array( 'form' => $form_id, 'title' => $title, 'meta' => Form_Admin::posted_meta( $form_id, $posted ), ), self::TTL ); return $token; } /** * Front-end address of a preview. * * @param string $token Preview token. */ public static function url( string $token ): string { return add_query_arg( self::QUERY, rawurlencode( $token ), home_url( '/' ) ); } /** * Shows the preview page when the request carries a preview token. */ public static function maybe_render(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The random token is the key; it only shows a form. if ( ! isset( $_GET[ self::QUERY ] ) ) { return; } $token = sanitize_key( wp_unslash( $_GET[ self::QUERY ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $state = self::state( $token ); nocache_headers(); if ( ! headers_sent() ) { header( 'X-Robots-Tag: noindex, nofollow' ); header( 'Referrer-Policy: no-referrer' ); } if ( null === $state ) { wp_die( esc_html__( 'This preview has expired. Open it again from the form editor.', 'flexsolutions-form-builder' ), esc_html__( 'Preview expired', 'flexsolutions-form-builder' ), array( 'response' => 410 ) ); } show_admin_bar( false ); self::page( $state ); exit; } /** * A stored preview, or null when it is unknown, expired or its form is gone. * * @param string $token Preview token. * @return array{form:int,title:?string,meta:array}|null */ public static function state( string $token ): ?array { if ( 1 !== preg_match( '/^[a-f0-9]{32}$/', $token ) ) { return null; } $state = get_transient( self::TRANSIENT . $token ); if ( ! is_array( $state ) || ! isset( $state['form'], $state['meta'] ) || ! is_array( $state['meta'] ) ) { return null; } $post = get_post( (int) $state['form'] ); if ( ! $post instanceof \WP_Post || Form_Post_Type::POST_TYPE !== $post->post_type || 'trash' === $post->post_status ) { return null; } return array( 'form' => (int) $state['form'], 'title' => isset( $state['title'] ) && is_string( $state['title'] ) ? $state['title'] : null, 'meta' => $state['meta'], ); } /** * The form's markup with the unsaved meta in place of the stored meta. * * @param array{form:int,title:?string,meta:array} $state Stored preview. */ public static function render( array $state ): string { $form_id = $state['form']; $meta = $state['meta']; $overlay = static function ( mixed $value, int $object_id, string $meta_key, bool $single ) use ( $form_id, $meta ): mixed { if ( $object_id !== $form_id || ! array_key_exists( $meta_key, $meta ) ) { return $value; } if ( null === $meta[ $meta_key ] ) { return $single ? array( '' ) : array(); } return array( $meta[ $meta_key ] ); }; $post = get_post( $form_id ); if ( ! $post instanceof \WP_Post ) { return ''; } $post = clone $post; if ( null !== $state['title'] ) { $post->post_title = $state['title']; } add_filter( 'get_post_metadata', $overlay, 10, 4 ); try { $html = ( new Renderer( Form::from_post( $post ) ) )->render(); } finally { remove_filter( 'get_post_metadata', $overlay, 10 ); } return $html; } /** * Prints the preview page: the theme's head and footer around the form. * * @param array{form:int,title:?string,meta:array} $state Stored preview. */ private static function page( array $state ): void { $form = self::render( $state ); $sent = __( 'This is a preview, so the form was not sent.', 'flexsolutions-form-builder' ); wp_enqueue_style( 'flexsolutions-form-builder' ); wp_add_inline_style( 'flexsolutions-form-builder', '.tflexforms-preview-main { box-sizing: border-box; max-width: var(--wp--style--global--content-size, 46rem); margin-inline: auto; padding: 2rem 1rem; }' . ' .tflexforms-preview-note { margin: 0 0 1.5rem; padding: .5rem .75rem; border-inline-start: 4px solid currentColor; font-size: .875em; }' ); wp_enqueue_script( 'flexsolutions-form-builder' ); wp_add_inline_script( 'flexsolutions-form-builder', 'document.addEventListener( "submit", function ( event ) {' . ' var form = event.target, note = document.getElementById( "tflexforms-preview-sent" );' . ' if ( ! form.closest || ! form.closest( ".tflexforms" ) ) { return; }' . ' event.preventDefault(); event.stopImmediatePropagation();' . ' if ( form.reportValidity && ! form.reportValidity() ) { return; }' . ' note.hidden = false; note.textContent = ' . wp_json_encode( $sent, JSON_HEX_TAG | JSON_HEX_AMP ) . '; note.scrollIntoView( { block: "nearest" } );' . ' }, true );' ); ?> > >