WP_REST_Server::READABLE, 'callback' => 'stacklar_rest_refresh_nonces', 'permission_callback' => '__return_true', 'args' => array( 'form_id' => array( 'required' => true, 'sanitize_callback' => 'absint', 'validate_callback' => static function ( $value ) { return absint( $value ) > 0; }, ), ), ) ); register_rest_route( 'stacklar/v1', '/action/(?P[a-z0-9_\\-]+)', array( 'methods' => WP_REST_Server::ALLMETHODS, 'callback' => 'stacklar_rest_action_dispatch', 'permission_callback' => '__return_true', 'args' => array( 'action' => array( 'sanitize_callback' => 'sanitize_key', ), ), ) ); } function stacklar_rest_refresh_nonces( WP_REST_Request $request ) { $form_id = absint( $request->get_param( 'form_id' ) ); $allowed = $form_id && function_exists( 'stacklar_can_execute_form' ) ? stacklar_can_execute_form( $form_id ) : false; if ( ! $allowed ) { return new WP_REST_Response( array( 'code' => 'form_not_available', 'message' => __( 'Form not found.', 'stacklar-forms' ), ), 404 ); } $config = function_exists( 'stacklar_build_form_actions_config' ) ? stacklar_build_form_actions_config( $form_id ) : array(); $protection = function_exists( 'stacklar_submission_protection_service' ) ? stacklar_submission_protection_service()->get_render_config( $form_id ) : array(); $response = new WP_REST_Response( array( 'rest_nonce' => wp_create_nonce( 'wp_rest' ), 'config_nonce' => wp_create_nonce( 'stacklar_config_' . $form_id ), 'autosave_nonce' => wp_create_nonce( 'stacklar_autosave_' . $form_id ), 'action_nonces' => function_exists( 'stacklar_build_form_action_nonces' ) ? stacklar_build_form_action_nonces( $form_id, $config ) : array(), 'protection' => array( 'issued_at' => isset( $protection['issued_at'] ) ? absint( $protection['issued_at'] ) : 0, 'signature' => isset( $protection['signature'] ) ? sanitize_text_field( (string) $protection['signature'] ) : '', ), ), 200 ); $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' ); $response->header( 'Pragma', 'no-cache' ); $response->header( 'Expires', 'Wed, 11 Jan 1984 05:00:00 GMT' ); $response->header( 'Vary', 'Cookie' ); return $response; } function stacklar_rest_action_dispatch( WP_REST_Request $request ) { $action = isset( $request['action'] ) ? sanitize_key( $request['action'] ) : ''; if ( '' === $action ) { return new WP_REST_Response( array( 'success' => false, 'data' => array( 'code' => 'missing_action', 'message' => __( 'Missing action.', 'stacklar-forms' ), ), ), 400 ); } $nonce = $request->get_header( 'X-WP-Nonce' ); if ( ! $nonce || ! wp_verify_nonce( $nonce, 'wp_rest' ) ) { return new WP_REST_Response( array( 'success' => false, 'data' => array( 'code' => 'invalid_rest_nonce', 'message' => __( 'Invalid request.', 'stacklar-forms' ), ), ), 403 ); } $handlers = stacklar_rest_get_handlers(); if ( empty( $handlers[ $action ] ) ) { return new WP_REST_Response( array( 'success' => false, 'data' => array( 'code' => 'invalid_action', 'message' => __( 'Action not found.', 'stacklar-forms' ), ), ), 404 ); } $handler = $handlers[ $action ]; if ( method_exists( $handler, 'reset_request_state' ) ) { $handler->reset_request_state(); } if ( method_exists( $handler, 'set_rest_request' ) ) { $handler->set_rest_request( true ); } $backup = stacklar_rest_prepare_globals( $request, $action ); try { $response = $handler->handle(); } catch ( Stacklar_Rest_Response_Exception $exception ) { $response = $exception->get_response(); } catch ( \Throwable $exception ) { stacklar_rest_restore_globals( $backup ); if ( method_exists( $handler, 'set_rest_request' ) ) { $handler->set_rest_request( false ); } if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- only with WP_DEBUG on. sprintf( '[Stacklar][REST] Action "%s" failed with %s: %s in %s:%d', $action, get_class( $exception ), $exception->getMessage(), $exception->getFile(), $exception->getLine() ) ); } return new WP_REST_Response( array( 'success' => false, 'data' => array( 'code' => 'rest_exception', 'message' => __( 'Internal server error.', 'stacklar-forms' ), ), ), 500 ); } stacklar_rest_restore_globals( $backup ); if ( method_exists( $handler, 'set_rest_request' ) ) { $handler->set_rest_request( false ); } if ( $response instanceof WP_REST_Response ) { return $response; } if ( is_wp_error( $response ) ) { return new WP_REST_Response( array( 'success' => false, 'data' => array( 'code' => $response->get_error_code(), 'message' => $response->get_error_message(), ), ), 500 ); } return rest_ensure_response( $response ); } // Swaps the superglobals for the duration of one action; the handler verifies // the nonce and sanitizes the values it reads. // phpcs:disable WordPress.Security.NonceVerification function stacklar_rest_prepare_globals( WP_REST_Request $request, $action ) { $backup = array( 'post' => $_POST, 'files' => $_FILES, 'request' => $_REQUEST, ); $params = $request->get_params(); if ( ! is_array( $params ) ) { $params = array(); } $params['action'] = $action; // REST params arrive unslashed, but action handlers follow the WordPress // superglobal contract and unslash once; re-slash so "\" survives. $_POST = wp_slash( $params ); $_REQUEST = $_POST; $files = $request->get_file_params(); $_FILES = is_array( $files ) ? $files : array(); return $backup; } function stacklar_rest_restore_globals( array $backup ) { $_POST = isset( $backup['post'] ) ? $backup['post'] : array(); $_FILES = isset( $backup['files'] ) ? $backup['files'] : array(); $_REQUEST = isset( $backup['request'] ) ? $backup['request'] : array(); } function stacklar_rest_get_handlers() { $handlers = array(); if ( function_exists( 'stacklar_action_handlers' ) ) { foreach ( stacklar_action_handlers() as $handler ) { if ( ! is_object( $handler ) || ! method_exists( $handler, 'get_action' ) ) { continue; } $action = sanitize_key( $handler->get_action() ); if ( '' === $action ) { continue; } $handlers[ $action ] = $handler; } } return $handlers; }