PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/ai-form-builder/ai-helper.php +191 -64 0.0.13 → 2.12.8 View file →
@@ -66,17 +66,13 @@
66 66
67 67 // Get the response body.
68 68 $response_body = wp_remote_retrieve_body( $response );
69 69
70 - // If the response body is not a JSON, then abandon ship.
71 - if ( empty( $response_body ) || ! json_decode( $response_body ) ) {
72 - return [
73 - 'error' => __( 'The SureForms AI Middleware encountered an error.', 'sureforms' ),
74 - ];
75 - }
76 -
77 - // Return the response body.
78 - return json_decode( $response_body, true );
70 + return self::decode_json_response(
71 + $response_body,
72 + wp_remote_retrieve_response_code( $response ),
73 + 'generate/form'
74 + );
79 75 }
80 76
81 77 /**
82 78 * Get the SureForms Token from the SureForms AI Settings.
@@ -108,9 +104,8 @@
108 104 }
109 105 }
110 106
111 107 return $current_usage_details;
112 -
113 108 }
114 109
115 110 /**
116 111 * Get a response from the SureForms API server.
@@ -142,41 +137,17 @@
142 137
143 138 // Get the response body.
144 139 $response_body = wp_remote_retrieve_body( $response );
145 140
146 - // If the response body is not a JSON, then abandon ship.
147 - if ( empty( $response_body ) || ! json_decode( $response_body ) ) {
148 - return [
149 - 'error' => __( 'The SureForms API server encountered an error.', 'sureforms' ),
150 - ];
151 - }
152 -
153 - // Return the response body.
154 - return json_decode( $response_body, true );
141 + return self::decode_json_response(
142 + $response_body,
143 + wp_remote_retrieve_response_code( $response ),
144 + 'usage',
145 + __( 'The SureForms API server encountered an error.', 'sureforms' )
146 + );
155 147 }
156 148
157 149 /**
158 - * Get the User Token.
159 - *
160 - * @since 0.0.8
161 - * @return string The User Token.
162 - */
163 - private static function get_user_token() {
164 - // if the license is active then use the license key as the token.
165 - if ( defined( 'SRFM_PRO_VER' ) ) {
166 - $license_key = self::get_license_key();
167 - if ( ! empty( $license_key ) ) {
168 - return $license_key;
169 - }
170 - }
171 -
172 - $user_email = get_option( 'srfm_ai_auth_user_email' );
173 -
174 - // if the license is not active then use the user email/site url as the token.
175 - return ! empty( $user_email ) && is_array( $user_email ) ? $user_email['user_email'] : site_url();
176 - }
177 -
178 - /**
179 150 * Get the Error Message.
180 151 *
181 152 * @param array<string,mixed>|array<int|string,mixed>|\WP_Error $response The response from the SureForms API server.
182 153 * @since 0.0.10
@@ -182,9 +153,9 @@
182 153 * @since 0.0.10
183 154 * @return array<string, mixed> The Error Message.
184 155 */
185 156 public static function get_error_message( $response ) {
186 - $errors = isset( $response->errors ) ? $response->errors : [];
157 + $errors = $response->errors ?? [];
187 158
188 159 if ( empty( $errors )
189 160 && is_array( $response ) && isset( $response['body'] ) && is_string( $response['body'] )
190 161 ) {
@@ -200,21 +171,21 @@
200 171 // Error Codes with Messages.
201 172 switch ( $error_key ) {
202 173 case 'http_request_failed':
203 174 $title = __( 'HTTP Request Failed', 'sureforms' );
204 - $message = __( 'An error occurred while trying to connect to the SureForms API server. Please check your connection', 'sureforms' );
175 + $message = __( 'Unable to connect to SureForms API. Please check your connection.', 'sureforms' );
205 176 break;
206 177 case 'license_verification_failed':
207 178 $title = __( 'License Verification Failed', 'sureforms' );
208 - $message = __( 'An error occurred while trying to verify your license. Please check your license key', 'sureforms' );
179 + $message = __( 'Unable to verify license. Please check your license key.', 'sureforms' );
209 180 break;
210 181 case 'user_verification_failed':
211 182 $title = __( 'User Verification Failed', 'sureforms' );
212 - $message = __( 'An error occurred while trying to verify your email. Please check your email you have used to log in/ sign up on billing.sureforms.com.', 'sureforms' );
183 + $message = __( 'An error occurred while trying to verify your email. Please check your email you have used to log in or sign up on billing.sureforms.com.', 'sureforms' );
213 184 break;
214 185 case 'referer_mismatch':
215 186 $title = __( 'Referer Mismatch', 'sureforms' );
216 - $message = __( 'An error occurred while trying to verify your referer. Please check your referer.', 'sureforms' );
187 + $message = __( 'Unable to verify referer. Please check your referer.', 'sureforms' );
217 188 break;
218 189 case 'invalid_token':
219 190 $title = __( 'Invalid Website URL', 'sureforms' );
220 191 $message = __( 'AI Form Builder does not work on localhost. Please try on a live website.', 'sureforms' );
@@ -220,9 +191,9 @@
220 191 $message = __( 'AI Form Builder does not work on localhost. Please try on a live website.', 'sureforms' );
221 192 break;
222 193 case 'domain_verification_failed':
223 194 $title = __( 'Domain Verification Failed', 'sureforms' );
224 - $message = __( 'Domain Verification Failed on current site. Please try again on any another website.', 'sureforms' );
195 + $message = __( 'Domain Verification Failed on current site. Please try again on another website.', 'sureforms' );
225 196 break;
226 197 default:
227 198 $title = __( 'Unknown Error', 'sureforms' );
228 199 $message = __( 'An unknown error occurred.', 'sureforms' );
@@ -232,12 +203,185 @@
232 203 'code' => $error_key,
233 204 'title' => $title,
234 205 'message' => $message,
235 206 ];
207 + }
236 208
209 + /**
210 + * Check if the SureForms Pro license is active.
211 + *
212 + * @since 0.0.10
213 + * @return bool|string True if the SureForms Pro license is active, false otherwise.
214 + */
215 + public static function is_pro_license_active() {
216 + $licensing = self::get_licensing_instance();
217 + if ( ! $licensing || ! method_exists( $licensing, 'is_license_active' )
218 + ) {
219 + return '';
220 + }
221 + // Check if the SureForms Pro license is active.
222 + return $licensing->is_license_active();
237 223 }
238 224
239 225 /**
226 + * Sanitize an upstream error message before returning it to the client.
227 + *
228 + * The OpenAI / SureForms middleware sometimes echoes infrastructure details
229 + * (URLs, request IDs, model names, organization/user IDs, raw API keys)
230 + * inside error messages. The endpoints surfacing these messages are
231 + * capability-gated, but contributors-and-up shouldn't see infra leaks.
232 + *
233 + * Pass-through behaviour is preserved when the message has no sensitive
234 + * tokens — only matched patterns are stripped. Returns an empty string
235 + * if nothing useful remains, so callers can fall back to a canonical
236 + * translated message.
237 + *
238 + * @param mixed $raw Raw upstream message; non-strings are coerced.
239 + * @param string $endpoint Optional endpoint label; when set, the raw input
240 + * is passed through {@see self::log_ai_response_failure()}
241 + * so the unredacted form is preserved server-side
242 + * (subject to the usual WP_DEBUG / WP_DEBUG_LOG gates).
243 + * @param int|string $status_code Optional HTTP status, forwarded to the logger.
244 + * @since 2.8.2
245 + * @return string Sanitized message safe to return to the client.
246 + */
247 + public static function sanitize_ai_error_message( $raw, $endpoint = '', $status_code = '' ) {
248 + if ( ! is_string( $raw ) ) {
249 + return '';
250 + }
251 + $raw = trim( $raw );
252 + if ( '' === $raw ) {
253 + return '';
254 + }
255 +
256 + if ( '' !== $endpoint ) {
257 + self::log_ai_response_failure( $endpoint, $status_code, 'upstream_error', $raw );
258 + }
259 +
260 + $patterns = [
261 + // URLs (http / https / protocol-relative).
262 + '#https?://\S+#i',
263 + '#(?<=\s)//\S+#i',
264 + // OpenAI-shape opaque IDs: org-/user-/key-/sess-/req-/file-/chatcmpl-/asst-/run-/thread-.
265 + // Both '-' and '_' separators are observed in the wild (e.g. req-… and req_…).
266 + '/\b(?:org|user|key|sess|req|file|chatcmpl|asst|run|thread)[-_][A-Za-z0-9_]{6,}/i',
267 + // Generic "request id: …" / "request-id …" trailers — require a separator and a substantive id.
268 + '/\brequest[_\s-]?id[:\s]+[A-Za-z0-9_-]{4,}/i',
269 + // Bearer / API-key shapes.
270 + '/\bsk-[A-Za-z0-9_-]{12,}/i',
271 + '/\bBearer\s+[A-Za-z0-9._-]+/i',
272 + // Model identifiers that would otherwise leak the underlying provider.
273 + '/\bgpt-[A-Za-z0-9.-]+/i',
274 + ];
275 + $cleaned = (string) preg_replace( $patterns, '', $raw );
276 + // Collapse the gaps left by removed tokens.
277 + $cleaned = (string) preg_replace( '/\s+/', ' ', $cleaned );
278 + return trim( $cleaned, " \t\n\r\0\x0B.,;:" );
279 + }
280 +
281 + /**
282 + * Decode the response body from the SureForms AI Middleware, returning
283 + * a structured error payload when the body is empty or invalid JSON.
284 + *
285 + * @param string $response_body Raw HTTP response body.
286 + * @param int|string $status_code HTTP status code, used for debug logging.
287 + * @param string $endpoint Short endpoint label, used for debug logging.
288 + * @param string|null $error_fallback Translated fallback message on decode failure.
289 + * @since 2.8.2
290 + * @return array<mixed>
291 + */
292 + protected static function decode_json_response( $response_body, $status_code, $endpoint, $error_fallback = null ) {
293 + if ( null === $error_fallback ) {
294 + $error_fallback = __( 'The SureForms AI Middleware encountered an error.', 'sureforms' );
295 + }
296 +
297 + if ( '' === $response_body || null === $response_body ) {
298 + self::log_ai_response_failure( $endpoint, $status_code, 'empty_body', '' );
299 + return [ 'error' => $error_fallback ];
300 + }
301 +
302 + $decoded = json_decode( $response_body, true );
303 +
304 + if ( JSON_ERROR_NONE !== json_last_error() ) {
305 + self::log_ai_response_failure( $endpoint, $status_code, 'invalid_json', $response_body );
306 + return [ 'error' => $error_fallback ];
307 + }
308 +
309 + if ( ! is_array( $decoded ) ) {
310 + self::log_ai_response_failure( $endpoint, $status_code, 'non_array_json', $response_body );
311 + return [ 'error' => $error_fallback ];
312 + }
313 +
314 + return $decoded;
315 + }
316 +
317 + /**
318 + * Log an AI middleware response failure when WP_DEBUG and WP_DEBUG_LOG are both enabled.
319 + *
320 + * Newlines are collapsed to prevent log injection, and known sensitive JSON keys
321 + * (email, token, license_key, prompt, query) are redacted before logging.
322 + *
323 + * @param string $endpoint Short endpoint label.
324 + * @param int|string $status_code HTTP status code.
325 + * @param string $reason Failure reason identifier.
326 + * @param string $body Raw response body (will be truncated).
327 + * @since 2.8.2
328 + * @return void
329 + */
330 + protected static function log_ai_response_failure( $endpoint, $status_code, $reason, $body ) {
331 + if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
332 + return;
333 + }
334 +
335 + // Only write to the debug log file when WP_DEBUG_LOG is also enabled.
336 + // Without this guard, error_log() falls back to the host's PHP error log.
337 + if ( ! defined( 'WP_DEBUG_LOG' ) || ! WP_DEBUG_LOG ) {
338 + return;
339 + }
340 +
341 + $snippet = is_string( $body ) ? substr( $body, 0, 500 ) : '';
342 + // Collapse all whitespace (including CR/LF) to a single space to prevent log injection.
343 + $snippet = (string) preg_replace( '/\s+/', ' ', $snippet );
344 + // Redact known sensitive keys if echoed in the body.
345 + $snippet = (string) preg_replace(
346 + '/("(?:email|token|license_key|prompt|query)"\s*:\s*")[^"]*"/i',
347 + '$1[redacted]"',
348 + $snippet
349 + );
350 +
351 + error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Debug-only logging behind WP_DEBUG && WP_DEBUG_LOG.
352 + sprintf(
353 + '[SureForms AI] %s %s status=%s body=%s',
354 + $endpoint,
355 + $reason,
356 + (string) $status_code,
357 + $snippet
358 + )
359 + );
360 + }
361 +
362 + /**
363 + * Get the User Token.
364 + *
365 + * @since 0.0.8
366 + * @return string The User Token.
367 + */
368 + private static function get_user_token() {
369 + // if the license is active then use the license key as the token.
370 + if ( defined( 'SRFM_PRO_VER' ) ) {
371 + $license_key = self::get_license_key();
372 + if ( ! empty( $license_key ) ) {
373 + return $license_key;
374 + }
375 + }
376 +
377 + $user_email = get_option( 'srfm_ai_auth_user_email' );
378 +
379 + // if the license is not active then use the user email/site url as the token.
380 + return ! empty( $user_email ) && is_array( $user_email ) ? $user_email['user_email'] : site_url();
381 + }
382 +
383 + /**
240 384 * Get the Licensing Instance.
241 385 *
242 386 * @since 0.0.10
243 387 * @return object|null The Licensing Instance.
@@ -264,25 +408,8 @@
264 408 // Check if the SureForms Pro license is active.
265 409 $is_license_active = self::is_pro_license_active();
266 410 // If the license is active, get the license key.
267 411 $license_setup = $licensing->licensing_setup();
268 - $license_key = ( ! empty( $is_license_active ) && is_object( $license_setup ) && method_exists( $license_setup, 'settings' ) ) ? $license_setup->settings()->license_key : '';
269 - return $license_key;
270 - }
271 -
272 - /**
273 - * Check if the SureForms Pro license is active.
274 - *
275 - * @since 0.0.10
276 - * @return bool|string True if the SureForms Pro license is active, false otherwise.
277 - */
278 - public static function is_pro_license_active() {
279 - $licensing = self::get_licensing_instance();
280 - if ( ! $licensing || ! method_exists( $licensing, 'is_license_active' )
281 - ) {
282 - return '';
283 - }
284 - // Check if the SureForms Pro license is active.
285 - return $licensing->is_license_active();
412 + return ! empty( $is_license_active ) && is_object( $license_setup ) && method_exists( $license_setup, 'settings' ) ? $license_setup->settings()->license_key : '';
286 413 }
287 414
288 415 }