'POST', 'permission_callback' => array( __CLASS__, 'allowed' ), 'callback' => array( __CLASS__, 'render' ), ) ); } public static function allowed( $request ) { // Explicitly require the editor's REST nonce as well as administrator access. // The REST cookie authenticator also checks this nonce before dispatch. $nonce = $request->get_header( 'X-WP-Nonce' ); return current_user_can( 'manage_options' ) && is_string( $nonce ) && wp_verify_nonce( sanitize_text_field( $nonce ), 'wp_rest' ); } private static function invalid( $message ) { return new WP_Error( 'kconv_preview_invalid', $message, array( 'status' => 400 ) ); } public static function render( $request ) { if ( ! self::allowed( $request ) ) { return new WP_Error( 'forbidden', __( 'Form administration required.', 'killerconversions' ), array( 'status' => 403 ) ); } // Extension operations hold their guard until all reads/writes and rollback complete. try { $allowed = apply_filters( 'kconv_preview_operation_allowed', true, $request ); if ( is_wp_error( $allowed ) ) { return $allowed; } if ( true !== $allowed ) { return new WP_Error( 'kconv_operation_unavailable', __( 'This operation is unavailable. No saved data has changed.', 'killerconversions' ), array( 'status' => 403 ) ); } return self::render_candidate( $request ); } finally { do_action( 'kconv_preview_operation_finished', $request ); } } private static function render_candidate( $request ) { $available = apply_filters( 'kconv_preview_availability', true ); if ( is_wp_error( $available ) ) { return $available; } if ( true !== $available ) { return self::invalid( __( 'Preview is temporarily unavailable. Review the plugin requirements.', 'killerconversions' ) ); } if ( strlen( $request->get_body() ) > self::MAX_BYTES ) { return self::invalid( __( 'This preview is too large. Keep the form under 256 KB.', 'killerconversions' ) ); } $raw = $request->get_json_params(); if ( ! is_array( $raw ) || ! isset( $raw['fields'] ) || ! isset( $raw['title'] ) || ! is_string( $raw['title'] ) || strlen( $raw['title'] ) > 1000 ) { return self::invalid( __( 'Send a form title and fields to preview.', 'killerconversions' ) ); } $settings = isset( $raw['settings'] ) && is_array( $raw['settings'] ) ? $raw['settings'] : array(); // Delivery addresses, destinations, credentials and measurement are not preview inputs. $safe_settings = KCONV_Core::appearance_settings( $settings ); foreach ( array( 'submit_label', 'success_message' ) as $key ) { if ( isset( $settings[ $key ] ) && is_string( $settings[ $key ] ) ) { $safe_settings[ $key ] = $settings[ $key ]; } } $schema = KCONV_Core::sanitize_schema( array_merge( array_intersect_key( $raw, array_flip( array( 'estimate', 'steps', 'logic' ) ) ), array( 'fields' => $raw['fields'], 'settings' => $safe_settings ) ) ); if ( is_wp_error( $schema ) ) { return self::invalid( $schema->get_error_message() ); } $form = array_merge( array( 'id' => 0, 'title' => mb_substr( sanitize_text_field( $raw['title'] ), 0, 200 ), 'status' => 'draft' ), $schema ); $pro = array( 'rules' => array(), 'steps' => array() ); if ( isset( $raw['pro'] ) ) { if ( ! is_array( $raw['pro'] ) ) { return self::invalid( __( 'Form logic must contain valid rules and steps.', 'killerconversions' ) ); } foreach ( array( 'rules', 'steps' ) as $key ) { if ( isset( $raw['pro'][ $key ] ) ) { $pro[ $key ] = $raw['pro'][ $key ]; } } } if ( class_exists( 'KCONV_Pro' ) ) { $pro = KCONV_Pro::sanitize( $pro, $form ); if ( is_wp_error( $pro ) ) { return self::invalid( $pro->get_error_message() ); } } elseif ( ! empty( $pro['rules'] ) || ! empty( $pro['steps'] ) ) { return self::invalid( __( 'Activate Pro to preview this form logic.', 'killerconversions' ) ); } $styles = array( add_query_arg( 'ver', KCONV_VERSION, KCONV_URL . 'assets/forms.css' ) ); $scripts = array(); if ( KCONV_Conditions::has_fields( $form ) || ! empty( $pro['rules'] ) || ! empty( $pro['steps'] ) ) { $scripts[] = add_query_arg( 'ver', KCONV_VERSION, KCONV_URL . 'assets/conditions.js' ); } if ( class_exists( 'KCONV_Pro' ) && ( $pro['rules'] || $pro['steps'] ) ) { $styles[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/pro.css' ); $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/pro.js' ); } if ( class_exists( 'KCONV_Calculations' ) && KCONV_Calculations::has_fields( $form ) ) { $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/calculations.js' ); $styles[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/calculations.css' ); } if ( ! empty( $form['estimate'] ) || ! empty( $form['steps'] ) || ! empty( $form['logic'] ) ) { $scripts[] = add_query_arg( 'ver', KCONV_VERSION, KCONV_URL . 'assets/conditions.js' ); $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/steps.js' ); $styles[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/steps.css' ); if ( ! empty( $form['estimate'] ) ) { $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/estimates.js' ); $styles[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/estimates.css' ); } if ( class_exists( 'KCONV_Advanced_Logic' ) && ( ! empty( $form['logic'] ) || ( ! empty( $form['estimate'] ) && 'advanced' === $form['estimate']['mode'] ) ) ) { $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/advanced-logic.js' ); } if ( class_exists( 'KCONV_Advanced_Estimates' ) && ! empty( $form['estimate'] ) && 'advanced' === $form['estimate']['mode'] ) { $scripts[] = add_query_arg( 'ver', KCONV_PRO_VERSION, KCONV_PRO_URL . 'assets/advanced-estimates.js' ); } } if ( KCONV_Repeaters::has( $form ) ) { $scripts[] = add_query_arg( 'ver', KCONV_VERSION, KCONV_URL . 'assets/repeaters.js' ); $styles[] = add_query_arg( 'ver', KCONV_VERSION, KCONV_URL . 'assets/repeaters.css' ); } $scripts = array_values( array_unique( $scripts ) ); if ( $scripts ) { $scripts = array_merge( array( includes_url( 'js/dist/hooks.min.js' ), includes_url( 'js/dist/i18n.min.js' ) ), $scripts ); } $response = new WP_REST_Response( array( 'html' => KCONV_Core::render_form( $form, array( 'preview' => true, 'pro' => $pro ) ), 'styles' => $styles, 'scripts' => $scripts, 'success_message' => $schema['settings']['success_message'], ) ); $response->header( 'Cache-Control', 'no-store, private, max-age=0' ); $response->header( 'X-Content-Type-Options', 'nosniff' ); return $response; } }