'GET', 'callback' => [ __CLASS__, 'list_connections' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ], [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'create_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ], ] ); register_rest_route( self::REST_NAMESPACE, '/smtp/connections/(?P[a-f0-9]{8})', [ [ 'methods' => 'PUT', 'callback' => [ __CLASS__, 'update_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ], [ 'methods' => 'DELETE', 'callback' => [ __CLASS__, 'delete_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ], ] ); register_rest_route( self::REST_NAMESPACE, '/smtp/connections/(?P[a-f0-9]{8})/default', [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'set_default_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ] ); // Test an already-saved connection. register_rest_route( self::REST_NAMESPACE, '/smtp/connections/(?P[a-f0-9]{8})/test', [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'test_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], 'args' => [ 'recipient' => [ 'type' => 'string', 'required' => true ], ], ] ); // Test connection settings that haven't been saved yet (the "Save // Connection" + "Send Test Email" buttons on the same form — a // site owner shouldn't have to save first just to test). register_rest_route( self::REST_NAMESPACE, '/smtp/test-unsaved', [ 'methods' => 'POST', 'callback' => [ __CLASS__, 'test_unsaved_connection' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], 'args' => [ 'recipient' => [ 'type' => 'string', 'required' => true ], ], ] ); register_rest_route( self::REST_NAMESPACE, '/smtp/providers', [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'list_providers' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ] ); register_rest_route( self::REST_NAMESPACE, '/smtp/logs', [ 'methods' => 'GET', 'callback' => [ __CLASS__, 'list_logs' ], 'permission_callback' => [ __CLASS__, 'can_manage' ], ] ); } public static function list_connections() { $connections = array_map( [ Smtp_Connection::class, 'to_public_array' ], Smtp_Connection::all() ); return rest_ensure_response( [ 'connections' => $connections, ] ); } public static function create_connection( \WP_REST_Request $request ) { $result = Smtp_Connection::create( self::request_to_data( $request ) ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( $result ); } public static function update_connection( \WP_REST_Request $request ) { $result = Smtp_Connection::update( $request->get_param( 'id' ), self::request_to_data( $request ) ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( $result ); } public static function delete_connection( \WP_REST_Request $request ) { $result = Smtp_Connection::delete( $request->get_param( 'id' ) ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( [ 'deleted' => true ] ); } public static function set_default_connection( \WP_REST_Request $request ) { $result = Smtp_Connection::set_default( $request->get_param( 'id' ) ); if ( is_wp_error( $result ) ) { return $result; } return rest_ensure_response( $result ); } public static function test_connection( \WP_REST_Request $request ) { $connection = Smtp_Connection::get( $request->get_param( 'id' ) ); if ( ! $connection ) { return new \WP_Error( 'chargeforms_smtp_not_found', __( 'SMTP connection not found.', 'chargeforms' ), [ 'status' => 404 ] ); } $result = Email_Test::send( $connection, sanitize_email( $request->get_param( 'recipient' ) ) ); self::record_test_result( $connection['id'], $result ); return rest_ensure_response( $result ); } /** * Tests connection settings straight from the request body — never * persisted. `password`, if omitted (editing an existing connection * without changing the password), falls back to the already-saved * connection's own credential when an `id` is also supplied. */ public static function test_unsaved_connection( \WP_REST_Request $request ) { $data = self::request_to_data( $request ); $existing_id = $request->get_param( 'id' ); $password_encrypted = ''; if ( $existing_id ) { $existing = Smtp_Connection::get( $existing_id ); if ( $existing && ! array_key_exists( 'password', $data ) ) { $password_encrypted = $existing['password_encrypted']; } } if ( array_key_exists( 'password', $data ) ) { $password_encrypted = Credential_Cipher::encrypt( (string) $data['password'] ); } $connection = array_merge( [ 'id' => 'unsaved', 'name' => $data['name'] ?? __( 'Unsaved connection', 'chargeforms' ), 'host' => $data['host'] ?? '', 'port' => (int) ( $data['port'] ?? 587 ), 'encryption' => $data['encryption'] ?? 'tls', 'auth_enabled' => ! empty( $data['auth_enabled'] ), 'username' => $data['username'] ?? '', 'from_email' => $data['from_email'] ?? '', 'from_name' => $data['from_name'] ?? '', 'reply_to' => $data['reply_to'] ?? '', 'force_from_email' => array_key_exists( 'force_from_email', $data ) ? ! empty( $data['force_from_email'] ) : true, 'force_from_name' => ! empty( $data['force_from_name'] ), 'return_path_match_from' => array_key_exists( 'return_path_match_from', $data ) ? ! empty( $data['return_path_match_from'] ) : true, ], [ 'password_encrypted' => $password_encrypted ] ); if ( ! $connection['host'] || ! is_email( $connection['from_email'] ) ) { return new \WP_Error( 'chargeforms_smtp_invalid', __( 'Enter a host and a valid From Email before testing.', 'chargeforms' ), [ 'status' => 400 ] ); } return rest_ensure_response( Email_Test::send( $connection, sanitize_email( $request->get_param( 'recipient' ) ) ) ); } private static function record_test_result( $id, array $result ) { $rows = Smtp_Connection::all(); foreach ( $rows as $i => $row ) { if ( $row['id'] === $id ) { $rows[ $i ]['last_tested_at'] = current_time( 'mysql', true ); $rows[ $i ]['last_test_status'] = $result['success'] ? 'success' : 'failed'; $rows[ $i ]['last_error'] = $result['success'] ? null : ( $result['message'] ?? null ); break; } } update_option( Smtp_Connection::OPTION_KEY, $rows ); } public static function list_providers() { return rest_ensure_response( Smtp_Provider_Registry::all() ); } public static function list_logs( \WP_REST_Request $request ) { return rest_ensure_response( Email_Log::query( [ 'page' => (int) $request->get_param( 'page' ) ?: 1, 'per_page' => (int) $request->get_param( 'per_page' ) ?: 20, 'status' => $request->get_param( 'status' ), 'search' => $request->get_param( 'search' ), ] ) ); } /** Pulls only the fields Smtp_Connection::sanitize() knows about out of the request body — never passes the raw request array straight through. */ private static function request_to_data( \WP_REST_Request $request ) { $data = []; $fields = [ 'name', 'provider', 'host', 'port', 'encryption', 'auth_enabled', 'username', 'from_email', 'from_name', 'reply_to', 'force_from_email', 'force_from_name', 'return_path_match_from', 'enabled' ]; foreach ( $fields as $field ) { if ( null !== $request->get_param( $field ) ) { $data[ $field ] = $request->get_param( $field ); } } // Only included when the client actually sent it — see // Smtp_Connection::sanitize()'s own doc comment on why omission // vs. explicit-empty-string are different things. if ( null !== $request->get_param( 'password' ) ) { $data['password'] = $request->get_param( 'password' ); } return $data; } }