From 6ead99eeb80dc187bae20640a7291b49c7cda56f Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 9 Jun 2026 16:08:55 +0530 Subject: [PATCH 01/25] Phone: reliable per-visitor auto country detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Detect the visitor's country server-side and apply it to the phone field: - Helper::get_geo_country(): prefer a CDN/server country header (Cloudflare/CloudFront/mod_geoip) — free, instant, cache-safe and independent of the connecting IP — before any API call; then ipapi.co with an optional filterable key (srfm_ipapi_api_key) for reliable IP detection; degrade to the field's configured default country. - Reject private/reserved IPs, detect ipapi 200-with-error rate-limit bodies, short failure-cache TTL (srfm_geo_failure_ttl) so a blip self-heals, and a site-wide hourly cap on outbound calls. - Public REST route sureforms/v1/geo-country so detection is per-visitor and correct on full-page-cached sites (not baked into cached HTML). - Frontend applyAutoCountry() fetches the endpoint and applies the country via intl-tel-input, cached in sessionStorage per session. - phone-markup.php emits data-auto-country and passes the configured default as the fallback; frontend-assets.php localizes the endpoint URL. --- assets/js/unminified/blocks/phone.js | 85 +++++++++++++ inc/fields/phone-markup.php | 104 ++------------- inc/frontend-assets.php | 12 ++ inc/helper.php | 184 +++++++++++++++++++++++++++ inc/rest-api.php | 26 ++++ 5 files changed, 316 insertions(+), 95 deletions(-) diff --git a/assets/js/unminified/blocks/phone.js b/assets/js/unminified/blocks/phone.js index 7f4715283..ec433a8c8 100644 --- a/assets/js/unminified/blocks/phone.js +++ b/assets/js/unminified/blocks/phone.js @@ -43,6 +43,77 @@ function validateCountryWithFilters( return country; } +/** + * Apply per-visitor auto country detection to an intl-tel-input instance. + * + * Fetches the visitor's country from the same-origin geo-country REST endpoint + * and applies it via setCountry(), so auto-detection is correct per visitor even + * when the page HTML is full-page cached. Respects the field's country filters + * and never overrides a value the visitor already started typing. The result is + * cached in sessionStorage to avoid repeat requests within a session. + * + * @param {Object} iti The intl-tel-input instance. + * @param {HTMLElement} phoneNumber The phone input element. + * @param {Object} filters Country-filter settings for this field. + */ +function applyAutoCountry( iti, phoneNumber, filters ) { + const endpoint = + window.srfm_phone_data && window.srfm_phone_data.geo_endpoint; + if ( ! endpoint ) { + return; + } + + const setCountry = ( country ) => { + if ( ! country || typeof country !== 'string' ) { + return; + } + // Don't override a value the visitor already started entering. + if ( phoneNumber.value && phoneNumber.value.trim() !== '' ) { + return; + } + const validated = validateCountryWithFilters( + country.toLowerCase(), + filters.enableCountryFilter, + filters.countryFilterType, + filters.includeCountries, + filters.excludeCountries + ); + if ( validated ) { + iti.setCountry( validated ); + } + }; + + // Reuse a cached lookup for the session to avoid repeat requests. + let cached = null; + try { + cached = window.sessionStorage?.getItem( 'srfm_geo_country' ); + } catch { + cached = null; + } + if ( cached ) { + setCountry( cached ); + return; + } + + fetch( endpoint, { headers: { Accept: 'application/json' } } ) + .then( ( res ) => ( res.ok ? res.json() : null ) ) + .then( ( data ) => { + const country = data && data.country ? String( data.country ) : ''; + if ( ! country ) { + return; + } + try { + window.sessionStorage?.setItem( 'srfm_geo_country', country ); + } catch { + // sessionStorage may be unavailable (e.g. private mode) — ignore. + } + setCountry( country ); + } ) + .catch( () => { + // Network error — keep the server-provided default-country. + } ); +} + function initializePhoneField() { const phone = document.querySelectorAll( '.srfm-phone-block' ); @@ -146,6 +217,20 @@ function initializePhoneField() { // Dropdown selection goes through _selectListItem -> _setCountry directly, so it still works. iti._updateCountryFromNumber = () => false; + // Per-visitor auto country detection. The server bakes an initial + // `default-country`, but on full-page-cached sites that value is the first + // visitor's. When auto-country is enabled, fetch the current visitor's + // country from the same-origin REST endpoint (not part of the cached page + // HTML) and apply it — correct per visitor. + if ( phoneNumber.getAttribute( 'data-auto-country' ) === 'true' ) { + applyAutoCountry( iti, phoneNumber, { + enableCountryFilter, + countryFilterType, + includeCountries, + excludeCountries, + } ); + } + const countriesData = iti?.countryList.querySelectorAll( '.iti__country' ); diff --git a/inc/fields/phone-markup.php b/inc/fields/phone-markup.php index fe166502c..4b6f1bee0 100644 --- a/inc/fields/phone-markup.php +++ b/inc/fields/phone-markup.php @@ -103,7 +103,12 @@ public function __construct( $attributes ) { // Performance: The API is called only once per visitor IP and cached in a transient // for 24 hours — subsequent page loads for the same IP resolve instantly from cache. if ( $this->auto_country ) { - $this->default_country = $this->get_geo_country(); + // Pass the configured default as the fallback so a detection failure + // degrades to the user's chosen country instead of a hardcoded 'us'. + $fallback = ! empty( $this->default_country ) && is_string( $this->default_country ) + ? strtolower( $this->default_country ) + : 'us'; + $this->default_country = Helper::get_geo_country( $fallback ); } $this->set_unique_slug(); $this->set_field_name( $this->unique_slug ); @@ -132,6 +137,9 @@ class="srfm-input-common srfm-input-slug ); ?>" data-required="data_require_attr ); ?>" aria-required="data_require_attr ); ?>" default-country="default_country ); ?>" + auto_country ) { ?> + data-auto-country="true" + enable_country_filter ) { ?> data-enable-country-filter="true" data-country-filter-type="country_filter_type ); ?>" @@ -153,98 +161,4 @@ class="srfm-input-common srfm-input-slug ); ?>" return ob_get_clean(); } - /** - * Detect the visitor's 2-letter country code via server-side IP geolocation. - * - * Calls ipapi.co once per visitor IP and caches the result in a transient for - * 24 hours so subsequent page loads resolve instantly without any API call. - * - * Failure responses (network error, non-200, malformed body, invalid country code) - * are also cached as 'us' for 1 hour to prevent a thundering-herd retry storm - * if ipapi.co goes down or rate-limits us. - * - * Private/reserved IPs (e.g., 10.x, 192.168.x, 127.0.0.1, ::1) are rejected up - * front — ipapi.co cannot geolocate them, and accepting them would let spoofed - * X-Forwarded-For headers flood the transient cache. - * - * A site-wide hourly cap (default 40, filterable via `srfm_geo_api_hourly_cap`) - * bounds outbound calls so a determined attacker can't exhaust the ipapi free-tier - * quota (1,000/day) by rotating spoofed public IPs. - * - * Note on page caching: When a full-page cache plugin is active, the HTML - * (including default-country) is served from cache. The first visitor's country - * is baked into the cached page. This is an acceptable tradeoff — the alternative - * (client-side fetch) caused CORS failures and 429 rate limits. Sites needing - * per-visitor precision can set a specific default country per field. - * - * @since 2.8.0 - * @return string Lowercase 2-letter country code, defaults to 'us'. - */ - private function get_geo_country() { - $ip = Helper::get_visitor_ip(); - if ( empty( $ip ) ) { - return 'us'; - } - - // Reject private/reserved IPs: ipapi.co returns "Reserved IP Address" for them, - // and accepting them would let spoofed X-Forwarded-For headers flood the cache. - if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { - return 'us'; - } - - $cache_key = 'srfm_geo_' . md5( $ip ); - $cached = get_transient( $cache_key ); - if ( is_string( $cached ) && '' !== $cached ) { - return $cached; - } - - // Site-wide hourly cap on outbound ipapi calls. The counter rolls over every hour - // (key includes YmdH) so we never need to explicitly reset it. Default 40 stays - // well under ipapi's 1,000/day free tier; paid-tier sites can raise via filter. - $quota_key = 'srfm_geo_quota_' . gmdate( 'YmdH' ); - $quota_cap = Helper::get_integer_value( apply_filters( 'srfm_geo_api_hourly_cap', 40 ) ); - $count = Helper::get_integer_value( get_transient( $quota_key ) ); - if ( $count >= $quota_cap ) { - set_transient( $cache_key, 'us', HOUR_IN_SECONDS ); - return 'us'; - } - set_transient( $quota_key, $count + 1, HOUR_IN_SECONDS ); - - // ipapi.co's /json/ endpoint geolocates the *caller's* IP. Since this request - // originates from the WordPress server (not the visitor's browser), we must - // pass the visitor's IP explicitly via /{ip}/json/ — otherwise ipapi.co - // returns the hosting datacenter's country for every visitor. - $url = 'https://ipapi.co/' . rawurlencode( $ip ) . '/json/'; - $response = wp_remote_get( - $url, - [ - 'timeout' => 3, - 'user-agent' => 'SureForms/' . SRFM_VER . ' (+https://sureforms.com)', - ] - ); - - if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { - set_transient( $cache_key, 'us', HOUR_IN_SECONDS ); - return 'us'; - } - - $body = json_decode( wp_remote_retrieve_body( $response ), true ); - - if ( ! is_array( $body ) || empty( $body['country_code'] ) || ! is_string( $body['country_code'] ) ) { - set_transient( $cache_key, 'us', HOUR_IN_SECONDS ); - return 'us'; - } - - $country = strtolower( $body['country_code'] ); - - // Validate the external API response is a valid 2-letter country code. - if ( ! preg_match( '/^[a-z]{2}$/', $country ) ) { - set_transient( $cache_key, 'us', HOUR_IN_SECONDS ); - return 'us'; - } - - set_transient( $cache_key, $country, DAY_IN_SECONDS ); - - return $country; - } } diff --git a/inc/frontend-assets.php b/inc/frontend-assets.php index e7ced4e17..5548e52b6 100644 --- a/inc/frontend-assets.php +++ b/inc/frontend-assets.php @@ -258,6 +258,18 @@ public function enqueue_srfm_script( $block_type, $attr ) { $block_dependencies = [ SRFM_SLUG . "-{$block_name}-intl-input-deps" ]; } wp_enqueue_script( SRFM_SLUG . "-{$block_name}", $js_uri . $block_name . $file_prefix . '.js', $block_dependencies, SRFM_VER, true ); + + if ( 'phone' === $block_name ) { + // Geo-country endpoint for per-visitor auto country detection. + // Built with rest_url() so it is correct regardless of permalink structure. + wp_localize_script( + SRFM_SLUG . "-{$block_name}", + 'srfm_phone_data', + [ + 'geo_endpoint' => esc_url_raw( rest_url( 'sureforms/v1/geo-country' ) ), + ] + ); + } } if ( 'input' === $block_name && isset( $attr['inputMask'] ) && 'none' !== $attr['inputMask'] ) { diff --git a/inc/helper.php b/inc/helper.php index 0d234d647..06c452adf 100644 --- a/inc/helper.php +++ b/inc/helper.php @@ -2434,4 +2434,188 @@ public static function get_visitor_ip() { return ''; } + /** + * Read a visitor country code from a CDN / server geo header, if present. + * + * Many hosts sit behind Cloudflare, CloudFront or a geo-aware web server that + * injects the visitor's country as a request header. This is free, instant, + * per-visitor and works on full-page-cached sites, so we prefer it over an + * outbound API call. Returns '' when no usable header is present. + * + * @since x.x.x + * @return string Lowercase 2-letter country code, or '' when unavailable. + */ + private static function get_cdn_country() { + $headers = [ + 'HTTP_CF_IPCOUNTRY', // Cloudflare. + 'HTTP_CLOUDFRONT_VIEWER_COUNTRY', // AWS CloudFront. + 'GEOIP_COUNTRY_CODE', // Apache/Nginx mod_geoip / MaxMind. + 'HTTP_X_GEO_COUNTRY', // Some CDNs / reverse proxies. + 'HTTP_X_COUNTRY_CODE', // Some CDNs. + ]; + + foreach ( $headers as $header ) { + if ( empty( $_SERVER[ $header ] ) ) { + continue; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by the regex below. + $code = strtolower( trim( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ) ); + + // Cloudflare sends 'xx' for unknown and 't1' for Tor; reject non-ISO values. + if ( preg_match( '/^[a-z]{2}$/', $code ) && 'xx' !== $code && 't1' !== $code ) { + return $code; + } + } + + /** + * Filters the CDN-derived visitor country code, before the ipapi.co fallback. + * + * Lets sites short-circuit detection with a server-side country code (e.g. + * from a custom header) without any outbound API call. + * + * @since x.x.x + * + * @param string $code Lowercase 2-letter country code, or '' if none found. + */ + return apply_filters( 'srfm_cdn_country', '' ); + } + + /** + * TTL (in seconds) for caching a failed geo lookup. + * + * Short by default so a transient blip (rate-limit, timeout) self-heals on the + * next visit instead of pinning the fallback country for a full hour, while + * still preventing per-request retry storms. Filterable via `srfm_geo_failure_ttl`. + * + * @since x.x.x + * @return int + */ + private static function get_geo_failure_ttl() { + return self::get_integer_value( apply_filters( 'srfm_geo_failure_ttl', 5 * MINUTE_IN_SECONDS ) ); + } + + /** + * Detect the visitor's 2-letter country code via server-side IP geolocation. + * + * Prefers a CDN/server-provided country header (Cloudflare, CloudFront, mod_geoip) + * when present — free, instant and cache-safe. Otherwise calls ipapi.co once per + * visitor IP and caches the result in a transient for 24 hours so subsequent + * lookups for the same IP resolve instantly. Failures are cached for a short TTL + * (see get_geo_failure_ttl()) to avoid retry storms while still self-healing, and + * a site-wide hourly cap (filterable via `srfm_geo_api_hourly_cap`, default 40) + * bounds outbound calls. Private/reserved IPs are rejected up front. + * + * Intended to be called per-visitor (e.g. via the geo-country REST route) so + * the result is correct on full-page-cached sites instead of being baked into + * the cached HTML. + * + * Local testing: private/loopback IPs (e.g. 127.0.0.1) cannot be geolocated, so + * inject a public IP via the `srfm_visitor_ip` filter to exercise detection: + * + * add_filter( 'srfm_visitor_ip', static fn() => '8.8.8.8' ); // US; try 1.1.1.1 etc. + * + * @param string $fallback Country code returned when detection is unavailable. + * @since x.x.x + * @return string Lowercase 2-letter country code. + */ + public static function get_geo_country( $fallback = 'us' ) { + // Prefer a CDN/server-provided country header — free, instant, per-visitor + // and cache-safe. It is independent of the connecting IP (it still resolves + // when the visitor IP is private/loopback, e.g. local dev or behind a + // proxy), so it must be checked before the IP-based path below. + $cdn_country = self::get_cdn_country(); + if ( '' !== $cdn_country ) { + return $cdn_country; + } + + $ip = self::get_visitor_ip(); + if ( empty( $ip ) ) { + return $fallback; + } + + // Reject private/reserved IPs: ipapi.co cannot geolocate them, and accepting + // them would let spoofed X-Forwarded-For headers flood the transient cache. + if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) { + return $fallback; + } + + $cache_key = 'srfm_geo_' . md5( $ip ); + $cached = get_transient( $cache_key ); + if ( is_string( $cached ) && '' !== $cached ) { + return $cached; + } + + // Site-wide hourly cap on outbound ipapi calls; the counter rolls over + // every hour (key includes YmdH) so it never needs an explicit reset. + $quota_key = 'srfm_geo_quota_' . gmdate( 'YmdH' ); + $quota_cap = self::get_integer_value( apply_filters( 'srfm_geo_api_hourly_cap', 40 ) ); + $count = self::get_integer_value( get_transient( $quota_key ) ); + if ( $count >= $quota_cap ) { + self::srfm_log( $quota_cap, 'SRFM geo lookup skipped (hourly cap reached):' ); + set_transient( $cache_key, $fallback, self::get_geo_failure_ttl() ); + return $fallback; + } + set_transient( $quota_key, $count + 1, HOUR_IN_SECONDS ); + + // Pass the visitor's IP explicitly via /{ip}/json/ — the request originates + // from the server, so the bare /json/ endpoint would return the host's country. + $url = 'https://ipapi.co/' . rawurlencode( $ip ) . '/json/'; + + // ipapi.co's free (keyless) tier is heavily rate-limited, so unauthenticated + // lookups are best-effort and often fail to the configured fallback. Sites + // that need reliable IP-based detection can supply a paid ipapi.co key via + // this filter; CDN-fronted sites resolve earlier via get_cdn_country() and + // never reach this call. + $api_key = self::get_string_value( apply_filters( 'srfm_ipapi_api_key', '' ) ); + if ( '' !== $api_key ) { + $url = add_query_arg( 'key', rawurlencode( $api_key ), $url ); + } + + $response = wp_remote_get( + $url, + [ + 'timeout' => 3, + 'user-agent' => 'SureForms/' . SRFM_VER . ' (+https://sureforms.com)', + ] + ); + + if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { + $detail = is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_response_code( $response ); + self::srfm_log( $detail, 'SRFM geo lookup failed (transport):' ); + set_transient( $cache_key, $fallback, self::get_geo_failure_ttl() ); + return $fallback; + } + + $body = json_decode( wp_remote_retrieve_body( $response ), true ); + + // ipapi.co's free tier returns HTTP 200 with a JSON error body + // (e.g. {"error":true,"reason":"RateLimited"}) when throttled — treat as a failure. + if ( is_array( $body ) && ! empty( $body['error'] ) ) { + $reason = ! empty( $body['reason'] ) && is_string( $body['reason'] ) ? $body['reason'] : 'unknown'; + self::srfm_log( $reason, 'SRFM geo lookup failed (ipapi error):' ); + set_transient( $cache_key, $fallback, self::get_geo_failure_ttl() ); + return $fallback; + } + + if ( ! is_array( $body ) || empty( $body['country_code'] ) || ! is_string( $body['country_code'] ) ) { + self::srfm_log( wp_remote_retrieve_response_code( $response ), 'SRFM geo lookup failed (no country_code):' ); + set_transient( $cache_key, $fallback, self::get_geo_failure_ttl() ); + return $fallback; + } + + $country = strtolower( $body['country_code'] ); + + // Validate the external API response is a valid 2-letter country code. + if ( ! preg_match( '/^[a-z]{2}$/', $country ) ) { + self::srfm_log( $country, 'SRFM geo lookup failed (invalid country code):' ); + set_transient( $cache_key, $fallback, self::get_geo_failure_ttl() ); + return $fallback; + } + + set_transient( $cache_key, $country, DAY_IN_SECONDS ); + + return $country; + } + } diff --git a/inc/rest-api.php b/inc/rest-api.php index 43df5c0e0..5ed1a8663 100644 --- a/inc/rest-api.php +++ b/inc/rest-api.php @@ -84,6 +84,24 @@ public function sanitize_boolean_field( $value ) { return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); } + /** + * Return the visitor's detected country code. + * + * Public, read-only — resolves the country for the *current* request's IP via + * Helper::get_geo_country(), so the phone field can fetch it per-visitor and + * work on full-page-cached sites. Outbound geolocation calls are bounded by + * the hourly cap inside Helper::get_geo_country(). + * + * @since x.x.x + * @return \WP_REST_Response + */ + public function get_geo_country() { + return new \WP_REST_Response( + [ 'country' => Helper::get_geo_country() ], + 200 + ); + } + /** * Get the data for generating entries chart. * @@ -1385,6 +1403,14 @@ private function get_endpoints() { 'callback' => [ AI_Auth::get_instance(), 'handle_access_key' ], 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], ], + // Public route: returns the visitor's detected country code. Called + // per-visitor from the phone field so auto-country detection works + // on full-page-cached sites (the value isn't baked into cached HTML). + 'geo-country' => [ + 'methods' => 'GET', + 'callback' => [ $this, 'get_geo_country' ], + 'permission_callback' => '__return_true', + ], // This route is to get the form submissions for the last 30 days. 'entries-chart-data' => [ 'methods' => 'GET', From a18bfe671ee365b62734cb0d716276c768301437 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 9 Jun 2026 16:34:30 +0530 Subject: [PATCH 02/25] Phone: add on-device Intl country fallback for auto-detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server-side IP detection can't work on localhost (loopback) and ipapi.co's keyless tier is rate-limited, so auto-country fell back to US. Add a layered, privacy-safe fallback: - REST geo-country endpoint now returns { country, detected }; detected is false when the server has no confident result (no CDN header, no successful IP lookup). - Frontend detectCountryFromBrowser() derives the ISO region from the browser locale via Intl.Locale — purely on-device, no network/IP/third-party, so it works offline, on localhost and on full-page-cached pages (none of the CORS/rate-limit/privacy issues that moved the IP lookup server-side). - applyAutoCountry() applies the local guess immediately and only lets a confident server result override it; sessionStorage key bumped so stale fallback values don't mask the local guess. --- assets/js/unminified/blocks/phone.js | 74 +++++++++++++++++++++------- inc/rest-api.php | 10 +++- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/assets/js/unminified/blocks/phone.js b/assets/js/unminified/blocks/phone.js index ec433a8c8..64b3b440a 100644 --- a/assets/js/unminified/blocks/phone.js +++ b/assets/js/unminified/blocks/phone.js @@ -43,26 +43,55 @@ function validateCountryWithFilters( return country; } +/** + * Detect a likely country for the visitor using only on-device browser data. + * + * Reads the ISO region from the browser's locale(s) via Intl — no network + * request, no IP, no third-party call — so it works offline, on localhost and + * on full-page-cached pages, and never raises the CORS / rate-limit / privacy + * concerns that a browser geo-IP request would. + * + * @return {string} Lowercase 2-letter country code, or '' when undeterminable. + */ +function detectCountryFromBrowser() { + try { + const locales = + Array.isArray( navigator.languages ) && navigator.languages.length + ? navigator.languages + : [ navigator.language ]; + for ( const locale of locales ) { + if ( ! locale ) { + continue; + } + const region = new Intl.Locale( locale ).region; + if ( region && /^[A-Z]{2}$/.test( region ) ) { + return region.toLowerCase(); + } + } + } catch { + // Intl.Locale unsupported or a malformed locale — fall through. + } + return ''; +} + /** * Apply per-visitor auto country detection to an intl-tel-input instance. * - * Fetches the visitor's country from the same-origin geo-country REST endpoint - * and applies it via setCountry(), so auto-detection is correct per visitor even - * when the page HTML is full-page cached. Respects the field's country filters - * and never overrides a value the visitor already started typing. The result is - * cached in sessionStorage to avoid repeat requests within a session. + * Resolution order, most-to-least authoritative: + * 1. A browser-local Intl guess applied immediately — works offline, on + * localhost and on full-page-cached pages, with no flash and no network. + * 2. The same-origin geo-country REST endpoint, used to refine the guess only + * when the server *confidently* detected (CDN header or a successful IP + * lookup); cached in sessionStorage for the session. + * + * Respects the field's country filters and never overrides a value the visitor + * already started typing. * * @param {Object} iti The intl-tel-input instance. * @param {HTMLElement} phoneNumber The phone input element. * @param {Object} filters Country-filter settings for this field. */ function applyAutoCountry( iti, phoneNumber, filters ) { - const endpoint = - window.srfm_phone_data && window.srfm_phone_data.geo_endpoint; - if ( ! endpoint ) { - return; - } - const setCountry = ( country ) => { if ( ! country || typeof country !== 'string' ) { return; @@ -83,10 +112,20 @@ function applyAutoCountry( iti, phoneNumber, filters ) { } }; - // Reuse a cached lookup for the session to avoid repeat requests. + // 1. Immediate, network-free local guess so the flag is sensible even on + // localhost, offline, or before any request resolves. + setCountry( detectCountryFromBrowser() ); + + const endpoint = + window.srfm_phone_data && window.srfm_phone_data.geo_endpoint; + if ( ! endpoint ) { + return; + } + + // 2. Prefer a confident server detection. Reuse a cached one for the session. let cached = null; try { - cached = window.sessionStorage?.getItem( 'srfm_geo_country' ); + cached = window.sessionStorage?.getItem( 'srfm_geo_country_detected' ); } catch { cached = null; } @@ -98,19 +137,20 @@ function applyAutoCountry( iti, phoneNumber, filters ) { fetch( endpoint, { headers: { Accept: 'application/json' } } ) .then( ( res ) => ( res.ok ? res.json() : null ) ) .then( ( data ) => { - const country = data && data.country ? String( data.country ) : ''; - if ( ! country ) { + // Only override the local guess when the server is confident. + if ( ! data || ! data.detected || ! data.country ) { return; } + const country = String( data.country ); try { - window.sessionStorage?.setItem( 'srfm_geo_country', country ); + window.sessionStorage?.setItem( 'srfm_geo_country_detected', country ); } catch { // sessionStorage may be unavailable (e.g. private mode) — ignore. } setCountry( country ); } ) .catch( () => { - // Network error — keep the server-provided default-country. + // Network error — keep the local guess. } ); } diff --git a/inc/rest-api.php b/inc/rest-api.php index 5ed1a8663..a9699afe3 100644 --- a/inc/rest-api.php +++ b/inc/rest-api.php @@ -96,8 +96,16 @@ public function sanitize_boolean_field( $value ) { * @return \WP_REST_Response */ public function get_geo_country() { + // Pass an empty fallback so '' unambiguously means "not confidently + // detected" (no CDN header and no successful IP lookup). The frontend then + // falls back to a privacy-safe, network-free Intl guess in the browser. + $country = Helper::get_geo_country( '' ); + return new \WP_REST_Response( - [ 'country' => Helper::get_geo_country() ], + [ + 'country' => $country, + 'detected' => '' !== $country, + ], 200 ); } From d31ed4863351a77b65a7831f3fcf1ea676a5e2f5 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Tue, 9 Jun 2026 16:44:16 +0530 Subject: [PATCH 03/25] Phone: use device timezone (not language) for on-device country guess Intl.Locale region reflects the browser *language*, not physical location, so an en-US browser in India guessed US. Make the device timezone the primary on-device signal via an IANA timezone -> country map (e.g. Asia/Kolkata -> in), falling back to the locale region only when the timezone is unknown. Still purely on-device: no network, no IP, no third party. --- assets/js/unminified/blocks/phone.js | 48 +++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/assets/js/unminified/blocks/phone.js b/assets/js/unminified/blocks/phone.js index 64b3b440a..e8013594e 100644 --- a/assets/js/unminified/blocks/phone.js +++ b/assets/js/unminified/blocks/phone.js @@ -43,17 +43,57 @@ function validateCountryWithFilters( return country; } +/** + * IANA timezone → ISO 3166-1 alpha-2 country (lowercase). + * + * The device timezone reflects the visitor's physical location far better than + * their browser language, so it is the primary on-device signal. Covers the + * common canonical zones; anything unlisted falls back to the locale region. + */ +const TIMEZONE_TO_COUNTRY = { + // North America. + 'America/New_York': 'us', 'America/Detroit': 'us', 'America/Chicago': 'us', 'America/Denver': 'us', 'America/Phoenix': 'us', 'America/Los_Angeles': 'us', 'America/Anchorage': 'us', 'America/Adak': 'us', 'America/Boise': 'us', 'America/Indiana/Indianapolis': 'us', 'Pacific/Honolulu': 'us', + 'America/Toronto': 'ca', 'America/Vancouver': 'ca', 'America/Edmonton': 'ca', 'America/Winnipeg': 'ca', 'America/Halifax': 'ca', 'America/St_Johns': 'ca', 'America/Regina': 'ca', + 'America/Mexico_City': 'mx', 'America/Tijuana': 'mx', 'America/Monterrey': 'mx', 'America/Cancun': 'mx', + // Central & South America. + 'America/Bogota': 'co', 'America/Lima': 'pe', 'America/Caracas': 've', 'America/Santiago': 'cl', 'America/Argentina/Buenos_Aires': 'ar', 'America/Sao_Paulo': 'br', 'America/Bahia': 'br', 'America/Fortaleza': 'br', 'America/Manaus': 'br', 'America/Montevideo': 'uy', 'America/Asuncion': 'py', 'America/La_Paz': 'bo', 'America/Guayaquil': 'ec', 'America/Panama': 'pa', 'America/Costa_Rica': 'cr', 'America/Guatemala': 'gt', 'America/El_Salvador': 'sv', 'America/Tegucigalpa': 'hn', 'America/Managua': 'ni', 'America/Santo_Domingo': 'do', 'America/Havana': 'cu', 'America/Jamaica': 'jm', 'America/Puerto_Rico': 'pr', 'America/Port_of_Spain': 'tt', + // Europe. + 'Europe/London': 'gb', 'Europe/Dublin': 'ie', 'Europe/Lisbon': 'pt', 'Atlantic/Canary': 'es', 'Europe/Madrid': 'es', 'Europe/Paris': 'fr', 'Europe/Brussels': 'be', 'Europe/Amsterdam': 'nl', 'Europe/Luxembourg': 'lu', 'Europe/Berlin': 'de', 'Europe/Zurich': 'ch', 'Europe/Vienna': 'at', 'Europe/Rome': 'it', 'Europe/Malta': 'mt', 'Europe/Copenhagen': 'dk', 'Europe/Oslo': 'no', 'Europe/Stockholm': 'se', 'Europe/Helsinki': 'fi', 'Europe/Reykjavik': 'is', 'Europe/Warsaw': 'pl', 'Europe/Prague': 'cz', 'Europe/Bratislava': 'sk', 'Europe/Budapest': 'hu', 'Europe/Vilnius': 'lt', 'Europe/Riga': 'lv', 'Europe/Tallinn': 'ee', 'Europe/Athens': 'gr', 'Europe/Bucharest': 'ro', 'Europe/Sofia': 'bg', 'Europe/Belgrade': 'rs', 'Europe/Zagreb': 'hr', 'Europe/Ljubljana': 'si', 'Europe/Sarajevo': 'ba', 'Europe/Skopje': 'mk', 'Europe/Tirane': 'al', 'Europe/Chisinau': 'md', 'Europe/Kyiv': 'ua', 'Europe/Kiev': 'ua', 'Europe/Minsk': 'by', 'Europe/Moscow': 'ru', 'Europe/Istanbul': 'tr', + // Middle East. + 'Asia/Jerusalem': 'il', 'Asia/Beirut': 'lb', 'Asia/Amman': 'jo', 'Asia/Damascus': 'sy', 'Asia/Baghdad': 'iq', 'Asia/Riyadh': 'sa', 'Asia/Kuwait': 'kw', 'Asia/Qatar': 'qa', 'Asia/Dubai': 'ae', 'Asia/Muscat': 'om', 'Asia/Bahrain': 'bh', 'Asia/Tehran': 'ir', + // South & Central Asia. + 'Asia/Kabul': 'af', 'Asia/Karachi': 'pk', 'Asia/Kolkata': 'in', 'Asia/Calcutta': 'in', 'Asia/Colombo': 'lk', 'Asia/Kathmandu': 'np', 'Asia/Dhaka': 'bd', 'Asia/Thimphu': 'bt', 'Asia/Almaty': 'kz', 'Asia/Tashkent': 'uz', 'Asia/Baku': 'az', 'Asia/Yerevan': 'am', 'Asia/Tbilisi': 'ge', + // East & Southeast Asia. + 'Asia/Yangon': 'mm', 'Asia/Bangkok': 'th', 'Asia/Ho_Chi_Minh': 'vn', 'Asia/Phnom_Penh': 'kh', 'Asia/Vientiane': 'la', 'Asia/Jakarta': 'id', 'Asia/Makassar': 'id', 'Asia/Kuala_Lumpur': 'my', 'Asia/Singapore': 'sg', 'Asia/Manila': 'ph', 'Asia/Hong_Kong': 'hk', 'Asia/Macau': 'mo', 'Asia/Taipei': 'tw', 'Asia/Shanghai': 'cn', 'Asia/Urumqi': 'cn', 'Asia/Tokyo': 'jp', 'Asia/Seoul': 'kr', 'Asia/Ulaanbaatar': 'mn', + // Africa. + 'Africa/Abidjan': 'ci', 'Africa/Accra': 'gh', 'Africa/Addis_Ababa': 'et', 'Africa/Algiers': 'dz', 'Africa/Cairo': 'eg', 'Africa/Casablanca': 'ma', 'Africa/Johannesburg': 'za', 'Africa/Lagos': 'ng', 'Africa/Nairobi': 'ke', 'Africa/Tripoli': 'ly', 'Africa/Tunis': 'tn', 'Africa/Khartoum': 'sd', 'Africa/Dar_es_Salaam': 'tz', 'Africa/Kampala': 'ug', 'Africa/Kinshasa': 'cd', 'Africa/Maputo': 'mz', 'Africa/Windhoek': 'na', 'Africa/Harare': 'zw', 'Africa/Lusaka': 'zm', 'Africa/Dakar': 'sn', + // Oceania. + 'Australia/Sydney': 'au', 'Australia/Melbourne': 'au', 'Australia/Brisbane': 'au', 'Australia/Perth': 'au', 'Australia/Adelaide': 'au', 'Australia/Darwin': 'au', 'Australia/Hobart': 'au', 'Pacific/Auckland': 'nz', 'Pacific/Fiji': 'fj', 'Pacific/Guam': 'gu', 'Pacific/Port_Moresby': 'pg', +}; + /** * Detect a likely country for the visitor using only on-device browser data. * - * Reads the ISO region from the browser's locale(s) via Intl — no network - * request, no IP, no third-party call — so it works offline, on localhost and - * on full-page-cached pages, and never raises the CORS / rate-limit / privacy - * concerns that a browser geo-IP request would. + * Primary signal is the device timezone (physical location); the browser locale + * region (language) is a weaker secondary fallback. Both are read locally via + * Intl — no network request, no IP, no third-party call — so detection works + * offline, on localhost and on full-page-cached pages, and never raises the + * CORS / rate-limit / privacy concerns of a browser geo-IP request. * * @return {string} Lowercase 2-letter country code, or '' when undeterminable. */ function detectCountryFromBrowser() { + // 1. Timezone → country (reflects physical location). + try { + const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; + if ( tz && TIMEZONE_TO_COUNTRY[ tz ] ) { + return TIMEZONE_TO_COUNTRY[ tz ]; + } + } catch { + // Intl.DateTimeFormat unsupported — fall through to locale. + } + + // 2. Locale region (language) — weaker, but better than nothing. try { const locales = Array.isArray( navigator.languages ) && navigator.languages.length From a5b48f9d67236fb90ab050a9d99c1694d8efde6c Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Wed, 10 Jun 2026 14:21:54 +0530 Subject: [PATCH 04/25] Fix PHP Insights ordering and add geo country test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder the new private static geo helpers after the public methods (PHP Insights OrderedClassElements), and cover the two flagged functions: Helper::get_geo_country() — CDN header preference with no outbound call, placeholder rejection with private-IP fallback, srfm_cdn_country filter short-circuit, API resolution with transient caching, and rate-limit error-body fallback with failure caching — and Rest_Api::get_geo_country() — detected and not-detected response shapes. --- inc/helper.php | 122 +++++++++++++------------- tests/unit/inc/test-helper.php | 141 +++++++++++++++++++++++++++++++ tests/unit/inc/test-rest-api.php | 56 ++++++++++++ 3 files changed, 258 insertions(+), 61 deletions(-) diff --git a/inc/helper.php b/inc/helper.php index 06c452adf..89a3c8649 100644 --- a/inc/helper.php +++ b/inc/helper.php @@ -2434,67 +2434,6 @@ public static function get_visitor_ip() { return ''; } - /** - * Read a visitor country code from a CDN / server geo header, if present. - * - * Many hosts sit behind Cloudflare, CloudFront or a geo-aware web server that - * injects the visitor's country as a request header. This is free, instant, - * per-visitor and works on full-page-cached sites, so we prefer it over an - * outbound API call. Returns '' when no usable header is present. - * - * @since x.x.x - * @return string Lowercase 2-letter country code, or '' when unavailable. - */ - private static function get_cdn_country() { - $headers = [ - 'HTTP_CF_IPCOUNTRY', // Cloudflare. - 'HTTP_CLOUDFRONT_VIEWER_COUNTRY', // AWS CloudFront. - 'GEOIP_COUNTRY_CODE', // Apache/Nginx mod_geoip / MaxMind. - 'HTTP_X_GEO_COUNTRY', // Some CDNs / reverse proxies. - 'HTTP_X_COUNTRY_CODE', // Some CDNs. - ]; - - foreach ( $headers as $header ) { - if ( empty( $_SERVER[ $header ] ) ) { - continue; - } - - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by the regex below. - $code = strtolower( trim( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ) ); - - // Cloudflare sends 'xx' for unknown and 't1' for Tor; reject non-ISO values. - if ( preg_match( '/^[a-z]{2}$/', $code ) && 'xx' !== $code && 't1' !== $code ) { - return $code; - } - } - - /** - * Filters the CDN-derived visitor country code, before the ipapi.co fallback. - * - * Lets sites short-circuit detection with a server-side country code (e.g. - * from a custom header) without any outbound API call. - * - * @since x.x.x - * - * @param string $code Lowercase 2-letter country code, or '' if none found. - */ - return apply_filters( 'srfm_cdn_country', '' ); - } - - /** - * TTL (in seconds) for caching a failed geo lookup. - * - * Short by default so a transient blip (rate-limit, timeout) self-heals on the - * next visit instead of pinning the fallback country for a full hour, while - * still preventing per-request retry storms. Filterable via `srfm_geo_failure_ttl`. - * - * @since x.x.x - * @return int - */ - private static function get_geo_failure_ttl() { - return self::get_integer_value( apply_filters( 'srfm_geo_failure_ttl', 5 * MINUTE_IN_SECONDS ) ); - } - /** * Detect the visitor's 2-letter country code via server-side IP geolocation. * @@ -2618,4 +2557,65 @@ public static function get_geo_country( $fallback = 'us' ) { return $country; } + /** + * Read a visitor country code from a CDN / server geo header, if present. + * + * Many hosts sit behind Cloudflare, CloudFront or a geo-aware web server that + * injects the visitor's country as a request header. This is free, instant, + * per-visitor and works on full-page-cached sites, so we prefer it over an + * outbound API call. Returns '' when no usable header is present. + * + * @since x.x.x + * @return string Lowercase 2-letter country code, or '' when unavailable. + */ + private static function get_cdn_country() { + $headers = [ + 'HTTP_CF_IPCOUNTRY', // Cloudflare. + 'HTTP_CLOUDFRONT_VIEWER_COUNTRY', // AWS CloudFront. + 'GEOIP_COUNTRY_CODE', // Apache/Nginx mod_geoip / MaxMind. + 'HTTP_X_GEO_COUNTRY', // Some CDNs / reverse proxies. + 'HTTP_X_COUNTRY_CODE', // Some CDNs. + ]; + + foreach ( $headers as $header ) { + if ( empty( $_SERVER[ $header ] ) ) { + continue; + } + + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Validated by the regex below. + $code = strtolower( trim( sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ) ); + + // Cloudflare sends 'xx' for unknown and 't1' for Tor; reject non-ISO values. + if ( preg_match( '/^[a-z]{2}$/', $code ) && 'xx' !== $code && 't1' !== $code ) { + return $code; + } + } + + /** + * Filters the CDN-derived visitor country code, before the ipapi.co fallback. + * + * Lets sites short-circuit detection with a server-side country code (e.g. + * from a custom header) without any outbound API call. + * + * @since x.x.x + * + * @param string $code Lowercase 2-letter country code, or '' if none found. + */ + return apply_filters( 'srfm_cdn_country', '' ); + } + + /** + * TTL (in seconds) for caching a failed geo lookup. + * + * Short by default so a transient blip (rate-limit, timeout) self-heals on the + * next visit instead of pinning the fallback country for a full hour, while + * still preventing per-request retry storms. Filterable via `srfm_geo_failure_ttl`. + * + * @since x.x.x + * @return int + */ + private static function get_geo_failure_ttl() { + return self::get_integer_value( apply_filters( 'srfm_geo_failure_ttl', 5 * MINUTE_IN_SECONDS ) ); + } + } diff --git a/tests/unit/inc/test-helper.php b/tests/unit/inc/test-helper.php index 40b572b74..257bb5ed3 100644 --- a/tests/unit/inc/test-helper.php +++ b/tests/unit/inc/test-helper.php @@ -2638,6 +2638,133 @@ public function test_get_visitor_ip_reads_x_real_ip() { $this->assertEquals( '198.51.100.25', Helper::get_visitor_ip() ); } + // --- get_geo_country --- + + /** + * Test get_geo_country prefers a CDN-provided country header and never + * makes an outbound API call when one is present. + */ + public function test_get_geo_country_prefers_cdn_header() { + $this->clear_geo_server_vars(); + $_SERVER['HTTP_CF_IPCOUNTRY'] = 'DE'; + + $requests = 0; + $stub = static function () use ( &$requests ) { + $requests++; + return new \WP_Error( 'unexpected', 'No HTTP request expected.' ); + }; + add_filter( 'pre_http_request', $stub ); + + $this->assertSame( 'de', Helper::get_geo_country() ); + $this->assertSame( 0, $requests, 'A CDN header must short-circuit the API call.' ); + + remove_filter( 'pre_http_request', $stub ); + $this->clear_geo_server_vars(); + } + + /** + * Test get_geo_country rejects Cloudflare's 'XX' (unknown) placeholder and + * returns the fallback when the visitor IP is private/loopback. + */ + public function test_get_geo_country_rejects_cdn_placeholder_and_falls_back() { + $this->clear_geo_server_vars(); + $_SERVER['HTTP_CF_IPCOUNTRY'] = 'XX'; + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // Private IP → no API call. + + $this->assertSame( 'us', Helper::get_geo_country() ); + $this->assertSame( 'in', Helper::get_geo_country( 'in' ) ); + + $this->clear_geo_server_vars(); + } + + /** + * Test the srfm_cdn_country filter can short-circuit detection before the + * ipapi.co fallback. + */ + public function test_get_geo_country_uses_cdn_country_filter() { + $this->clear_geo_server_vars(); + + $filter = static function () { + return 'fr'; + }; + add_filter( 'srfm_cdn_country', $filter ); + + $this->assertSame( 'fr', Helper::get_geo_country() ); + + remove_filter( 'srfm_cdn_country', $filter ); + $this->clear_geo_server_vars(); + } + + /** + * Test get_geo_country resolves the country via the IP API and caches the + * result in a transient so a second lookup makes no further request. + */ + public function test_get_geo_country_resolves_via_api_and_caches() { + $this->clear_geo_server_vars(); + $_SERVER['REMOTE_ADDR'] = '203.0.113.77'; + delete_transient( 'srfm_geo_' . md5( '203.0.113.77' ) ); + + $requests = 0; + $stub = static function () use ( &$requests ) { + $requests++; + return [ + 'headers' => [], + 'body' => (string) wp_json_encode( [ 'country_code' => 'IN' ] ), + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + ]; + }; + add_filter( 'pre_http_request', $stub ); + + $this->assertSame( 'in', Helper::get_geo_country() ); + $this->assertSame( 'in', Helper::get_geo_country(), 'Second lookup must be served from the transient cache.' ); + $this->assertSame( 1, $requests, 'Only the first lookup may hit the API.' ); + + remove_filter( 'pre_http_request', $stub ); + delete_transient( 'srfm_geo_' . md5( '203.0.113.77' ) ); + $this->clear_geo_server_vars(); + } + + /** + * Test get_geo_country returns the fallback when ipapi.co answers HTTP 200 + * with an error body (free-tier rate limiting), and caches the failure so + * the next call does not retry immediately. + */ + public function test_get_geo_country_falls_back_on_api_error_body() { + $this->clear_geo_server_vars(); + $_SERVER['REMOTE_ADDR'] = '203.0.113.88'; + delete_transient( 'srfm_geo_' . md5( '203.0.113.88' ) ); + + $requests = 0; + $stub = static function () use ( &$requests ) { + $requests++; + return [ + 'headers' => [], + 'body' => (string) wp_json_encode( + [ + 'error' => true, + 'reason' => 'RateLimited', + ] + ), + 'response' => [ + 'code' => 200, + 'message' => 'OK', + ], + ]; + }; + add_filter( 'pre_http_request', $stub ); + + $this->assertSame( 'us', Helper::get_geo_country() ); + $this->assertSame( 'us', Helper::get_geo_country(), 'Failure must be cached for the failure TTL.' ); + $this->assertSame( 1, $requests, 'A cached failure must not retry the API.' ); + + remove_filter( 'pre_http_request', $stub ); + delete_transient( 'srfm_geo_' . md5( '203.0.113.88' ) ); + $this->clear_geo_server_vars(); + } + // --------------------------------------------------------------- // SRFM-2709: get_sureforms_website_url() UTM behavior // --------------------------------------------------------------- @@ -2717,6 +2844,20 @@ public function test_get_sureforms_website_url_handles_non_array_utm_args() { $this->assertStringNotContainsString( 'utm_', $url ); } + /** + * Clear CDN geo headers and IP-related $_SERVER variables for a clean test state. + */ + private function clear_geo_server_vars() { + $this->clear_ip_server_vars(); + unset( + $_SERVER['HTTP_CF_IPCOUNTRY'], + $_SERVER['HTTP_CLOUDFRONT_VIEWER_COUNTRY'], + $_SERVER['GEOIP_COUNTRY_CODE'], + $_SERVER['HTTP_X_GEO_COUNTRY'], + $_SERVER['HTTP_X_COUNTRY_CODE'] + ); + } + /** * Clear all IP-related $_SERVER variables for a clean test state. */ diff --git a/tests/unit/inc/test-rest-api.php b/tests/unit/inc/test-rest-api.php index 53d301290..2adf010d0 100644 --- a/tests/unit/inc/test-rest-api.php +++ b/tests/unit/inc/test-rest-api.php @@ -45,6 +45,62 @@ public function test_sanitize_boolean_field_falsy_values() { $this->assertFalse( $this->rest_api->sanitize_boolean_field( null ) ); } + // --------------------------------------------------------------- + // get_geo_country() + // --------------------------------------------------------------- + + /** + * With a CDN country header present the endpoint reports the detected + * country; with no header and no usable visitor IP it reports an empty + * country with detected=false so the frontend can apply its own fallback. + */ + public function test_get_geo_country() { + $geo_server_vars = [ + 'HTTP_CF_IPCOUNTRY', + 'HTTP_CLOUDFRONT_VIEWER_COUNTRY', + 'GEOIP_COUNTRY_CODE', + 'HTTP_X_GEO_COUNTRY', + 'HTTP_X_COUNTRY_CODE', + 'HTTP_CLIENT_IP', + 'HTTP_X_FORWARDED_FOR', + 'HTTP_X_REAL_IP', + 'HTTP_X_FORWARDED', + 'HTTP_FORWARDED_FOR', + 'HTTP_FORWARDED', + 'REMOTE_ADDR', + ]; + foreach ( $geo_server_vars as $server_var ) { + unset( $_SERVER[ $server_var ] ); + } + + // CDN header present → detected country. + $_SERVER['HTTP_CF_IPCOUNTRY'] = 'DE'; + + $response = $this->rest_api->get_geo_country(); + $this->assertInstanceOf( \WP_REST_Response::class, $response ); + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( + [ + 'country' => 'de', + 'detected' => true, + ], + $response->get_data() + ); + + // No CDN header and no usable visitor IP → not detected, empty country. + unset( $_SERVER['HTTP_CF_IPCOUNTRY'] ); + + $response = $this->rest_api->get_geo_country(); + $this->assertSame( 200, $response->get_status() ); + $this->assertSame( + [ + 'country' => '', + 'detected' => false, + ], + $response->get_data() + ); + } + // --------------------------------------------------------------- // sanitize_entry_ids() // --------------------------------------------------------------- From 7e378f03a758f438a384df8cf0f87f77adcad277 Mon Sep 17 00:00:00 2001 From: Vansh Kapoor Date: Fri, 12 Jun 2026 10:16:16 +0530 Subject: [PATCH 05/25] fix: update Cloudflare Turnstile 'Get Keys' link to dashboard URL The 'Get Keys' button pointed to https://www.cloudflare.com/en-gb/products/turnstile/, a marketing page whose locale path now returns a 404 and never led to key creation. Point it to the Cloudflare dashboard Turnstile page where users can create a widget and retrieve their site and secret keys. --- src/admin/settings/components/security/Turnstile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin/settings/components/security/Turnstile.js b/src/admin/settings/components/security/Turnstile.js index dec22ca38..194e0bc4c 100644 --- a/src/admin/settings/components/security/Turnstile.js +++ b/src/admin/settings/components/security/Turnstile.js @@ -49,7 +49,7 @@ const Turnstile = ( { securitytabOptions, updateGlobalSettings } ) => {
-
- ); -} -``` - ---- - -## Component Patterns - -### Functional Components with Hooks - -**Always use functional components** (not class components): - -```javascript -// ✅ GOOD - Functional component -function MyComponent({ prop1, prop2 }) { - const [state, setState] = useState(null); - - useEffect(() => { - // Side effects - }, []); - - return
{/* JSX */}
; -} - -// ❌ BAD - Class component (deprecated pattern) -class MyComponent extends React.Component { - // Don't use class components in SureForms -} -``` - -### Props Destructuring - -```javascript -// ✅ GOOD - Destructure in signature -function Button({ label, onClick, disabled = false, variant = 'primary' }) { - return ( - - ); -} - -// ❌ BAD - Access via props object -function Button(props) { - return ; -} -``` - -### Conditional Rendering - -```javascript -function FormField({ label, required, error }) { - return ( -
- - - {/* Ternary for if/else */} - {error ? ( - {error} - ) : ( - Optional - )} - - {/* Conditional with && */} - {!error && Valid} -
- ); -} -``` - -### Event Handlers - -```javascript -function FormContainer() { - // useCallback for stable reference - const handleSubmit = useCallback((event) => { - event.preventDefault(); - // Handle submit - }, []); - - // Arrow function for inline with parameters - const handleFieldChange = useCallback((fieldId, value) => { - setFormData((prev) => ({ - ...prev, - [fieldId]: value, - })); - }, []); - - return ( -
- handleFieldChange('name', e.target.value)} - /> -
- ); -} -``` - ---- - -## Styling - -### TailwindCSS (Preferred) - -Use Tailwind utility classes for rapid development: - -```javascript -function Card({ title, children }) { - return ( -
-

- {title} -

-
- {children} -
-
- ); -} -``` - -**Responsive Design:** - -```javascript -
- {/* Full width mobile, half on tablet, third on desktop */} -
- -
- {/* Responsive text size */} -
-``` - -### Custom SCSS (When Needed) - -**Use BEM methodology** with `.srfm-` prefix: - -```scss -// style.scss -.srfm-custom-field { - // Block - &__label { - // Element - @apply block text-sm font-medium text-gray-700 mb-2; - - .required { - // Nested element - @apply text-red-500 ml-1; - } - } - - &__input { - // Element - @apply w-full px-4 py-2 border border-gray-300 rounded-md; - @apply focus:outline-none focus:ring-2 focus:ring-blue-500; - - &:disabled { - // Modifier via pseudo-class - @apply bg-gray-100 cursor-not-allowed; - } - } - - &--error { - // Block modifier - .srfm-custom-field__input { - @apply border-red-500; - } - } -} -``` - -### Conditional Classes (classnames) - -```javascript -import classnames from 'classnames'; - -function Button({ variant, size, disabled, className }) { - const classes = classnames( - 'srfm-button', - `srfm-button--${variant}`, - `srfm-button--${size}`, - { - 'srfm-button--disabled': disabled, - }, - className // Allow override - ); - - return ; -} -``` - ---- - -## Form Validation - -### Client-Side Validation (Frontend) - -**Inline validation:** - -```javascript -import { useState } from 'react'; - -function EmailField({ value, onChange, required }) { - const [error, setError] = useState(''); - - const validateEmail = (email) => { - const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return regex.test(email); - }; - - const handleBlur = () => { - if (required && !value) { - setError(__('Email is required', 'sureforms')); - } else if (value && !validateEmail(value)) { - setError(__('Please enter a valid email', 'sureforms')); - } else { - setError(''); - } - }; - - return ( -
- - {error && {error}} -
- ); -} -``` - -**Form-level validation:** - -```javascript -function FormContainer() { - const [formData, setFormData] = useState({}); - const [errors, setErrors] = useState({}); - - const validate = () => { - const newErrors = {}; - - if (!formData.name) { - newErrors.name = 'Name is required'; - } - - if (!formData.email || !isValidEmail(formData.email)) { - newErrors.email = 'Valid email is required'; - } - - if (formData.age && formData.age < 18) { - newErrors.age = 'Must be 18 or older'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validate()) { - return; - } - - // Submit form - try { - await submitForm(formData); - toast.success('Form submitted!'); - } catch (error) { - toast.error('Submission failed'); - } - }; - - return ( -
- {/* Form fields */} - -
- ); -} -``` - ---- - -## Internationalization (i18n) - -### Translation Functions - -```javascript -import { __, _n, sprintf } from '@wordpress/i18n'; - -// Simple translation -const label = __('Submit Form', 'sureforms'); - -// Plural forms -const message = _n( - 'One field is required', - '%d fields are required', - count, - 'sureforms' -); - -// String interpolation -const greeting = sprintf( - __('Hello, %s!', 'sureforms'), - userName -); -``` - -### Best Practices - -**✅ DO:** -```javascript -// Use sprintf for variables -const msg = sprintf(__('Form %s saved', 'sureforms'), formName); - -// Separate strings for different contexts -const saveButton = __('Save', 'sureforms'); -const saveForm = __('Save Form', 'sureforms'); -``` - -**❌ DON'T:** -```javascript -// Never use template literals in translations -const msg = __(`Form ${formName} saved`, 'sureforms'); // ❌ WRONG - -// Never concatenate -const msg = __('Form ', 'sureforms') + formName + __(' saved', 'sureforms'); // ❌ WRONG -``` - ---- - -## Performance Optimization - -### Code Splitting - -```javascript -import { lazy, Suspense } from 'react'; - -const HeavyComponent = lazy(() => import('./HeavyComponent')); - -function App() { - return ( - }> - - - ); -} -``` - -### Memoization - -```javascript -import { useMemo, useCallback } from 'react'; - -function DataTable({ data, onRowClick }) { - // Memoize expensive calculations - const sortedData = useMemo(() => { - console.log('Sorting data...'); - return data.sort((a, b) => a.name.localeCompare(b.name)); - }, [data]); // Only recompute when data changes - - // Memoize callbacks - const handleRowClick = useCallback( - (row) => { - onRowClick(row); - }, - [onRowClick] - ); - - return {/* Render sortedData */}
; -} -``` - -### React.memo - -```javascript -import { memo } from 'react'; - -// Prevent re-renders if props haven't changed -const ExpensiveComponent = memo(function ExpensiveComponent({ data }) { - console.log('Rendering...'); - return
{/* Complex rendering */}
; -}); -``` - ---- - -## Testing - -### Component Testing (Jest + React Testing Library) - -```javascript -import { render, screen, fireEvent } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import FormField from '../FormField'; - -describe('FormField', () => { - it('renders with label', () => { - render(); - expect(screen.getByText('Name')).toBeInTheDocument(); - }); - - it('shows required indicator', () => { - render(); - expect(screen.getByText('*')).toBeInTheDocument(); - }); - - it('calls onChange when value changes', () => { - const handleChange = jest.fn(); - render(); - - const input = screen.getByRole('textbox'); - fireEvent.change(input, { target: { value: 'test' } }); - - expect(handleChange).toHaveBeenCalledWith('test'); - }); - - it('displays error message', () => { - render(); - expect(screen.getByText('This field is required')).toBeInTheDocument(); - }); -}); -``` - ---- - -## Summary - -**Key Takeaways:** - -1. **Use React 18 functional components** with hooks -2. **Gutenberg blocks** are registered in JavaScript, rendered in PHP -3. **State management** uses three layers: local, WordPress Data Store, React Query -4. **Styling** prefers TailwindCSS utilities, custom SCSS for complex needs -5. **Validation** happens client-side (UX) and server-side (security) -6. **i18n** uses `@wordpress/i18n` functions, no template literals -7. **Performance** optimized with memoization, code splitting, virtualization - ---- - -**Related Documentation:** -- [CLAUDE.md](../../CLAUDE.md) - AI agent guide -- [GETTING-STARTED.md](../01-getting-started/GETTING-STARTED.md) - Setup -- [CODING-STANDARDS.md](../03-development/CODING-STANDARDS.md) - Code style -- [TESTING.md](../08-testing/TESTING.md) - Testing guide diff --git a/docs/FORM_MIGRATION_OVERVIEW.md b/docs/FORM_MIGRATION_OVERVIEW.md deleted file mode 100644 index dd7fac4d6..000000000 --- a/docs/FORM_MIGRATION_OVERVIEW.md +++ /dev/null @@ -1,77 +0,0 @@ -# SureForms — Form Migration (Overview) - -> Looking for the full technical plan? See `FORM_MIGRATION_PLAN.md` in this folder. - -## What this feature does - -Lets users move their existing forms into SureForms from the other popular form-builder plugins, in one click — no copy-pasting, no rebuilding. - -## Which plugins we'll support - -| Source plugin | Approx. user base | Status | -|---|---|---| -| Contact Form 7 | 10M+ sites | **Available in v1** | -| WPForms | 6M+ sites | Coming next | -| Gravity Forms | ~650K sites | Coming next | -| Ninja Forms | 600K+ sites | Later release | -| Caldera Forms | ~50K sites (unmaintained plugin) | Later release | - -Combined audience: roughly **17 million sites** that could move to SureForms. - -## What an import gives you - -When a user clicks "Import", we bring across: - -1. **The form's fields** — text, email, phone, dropdown, checkbox, radio, file URL, number, textarea, consent checkboxes, etc. They show up as native SureForms blocks, ready to edit in the Gutenberg editor. -2. **The form's title** — same as the original. -3. **A working admin notification email** — recipient is the site admin, subject mentions the form name, body uses `{all_data}` so submissions go straight to your inbox. -4. **A success message** — a clean "Thank you" confirmation shown after submission. - -The imported form works on day one. Users can polish it in SureForms' editor. - -## What doesn't transfer yet (and why) - -| Thing | Why not | -|---|---| -| File-upload fields | SureForms doesn't have a file-upload block yet — coming separately. | -| Quiz / signature / rating fields | No SureForms equivalent yet. | -| The exact email-body text from CF7 | CF7 uses a different placeholder syntax (`[your-name]` vs SureForms' smart tags). Trying to translate it gets unreliable, so we ship a clean default instead. WPForms and Gravity emails will come through fully when those are added. | -| Multi-step / page breaks | Waiting on a SureForms multi-step block. | - -When a field can't be migrated, the import screen lists it under **"Some fields were skipped"** so the user knows what to add by hand. - -## How users will find it - -A new **Migration** tab inside **SureForms → Settings**, at the bottom of the sidebar. The workflow is four short steps: - -1. **Choose a source** — tiles show each detected plugin and how many forms it has. -2. **Pick which forms** — checkbox list; "Select all" if you want everything. -3. **Preview** — see what the migrated form will look like before anything is saved. -4. **Confirm & import** — done. The result screen has direct "Edit in SureForms" links. - -If a user re-runs the import for the same source form, we **update** the existing SureForms version instead of creating a duplicate. - -## What's already built - -- The Migration tab is live in **SureForms → Settings → Migration** -- Contact Form 7 imports work end to end (fields + notification + confirmation) -- "Preview before importing" works — nothing gets saved until the user confirms -- Skipped fields are surfaced clearly to the user - -## What's next - -1. **WPForms importer** — including its full email-notification and confirmation rules -2. **Gravity Forms importer** — including notifications, scheduling, entry limits -3. **Ninja Forms + Caldera Forms importers** -4. **Entry migration** — bringing across historical form submissions (capped at 1,000 per form to keep imports fast) -5. **Filling in field gaps** — once SureForms ships file-upload, multi-step, and signature blocks, the migrator will start carrying those over too - -## Why we chose this approach - -We modelled the migrator on **Fluent Forms**' approach, which has been running in production on 700K+ sites for years. Same flow (source → forms → preview → import), same safety net (re-imports update rather than duplicate), and the same honest stance on edge cases like CF7 email templates. - -## Where to find things (for engineers) - -- Full technical plan with code structure, REST endpoints, field-mapping tables: `FORM_MIGRATION_PLAN.md` -- PHP code: `inc/migrator/` -- Admin tab UI: `src/admin/settings/migration/` diff --git a/docs/FORM_MIGRATION_PLAN.md b/docs/FORM_MIGRATION_PLAN.md deleted file mode 100644 index 9d7c2a9a8..000000000 --- a/docs/FORM_MIGRATION_PLAN.md +++ /dev/null @@ -1,458 +0,0 @@ -# SureForms — Form Migration Implementation Plan - -## 1. Goal - -Build a one-click migrator that imports forms (and optionally entries) from the 5 major WordPress form plugins into SureForms, modelled on Fluent Forms' production-tested migrator architecture (~5,400 LOC across 5 sources, deployed on 700K+ sites). - -**Sources to support in v1**: Contact Form 7, WPForms (Lite + Pro), Gravity Forms, Ninja Forms, Caldera Forms. - -## 2. Why Fluent Forms' pattern - -Fluent's full migrator source was read end-to-end (`fluentform/app/Services/Migrator/`). Their architecture is: - -- **One abstract base class** with the import loop, idempotency map, and a ~1,200-line canonical field dictionary. -- **Thin per-source subclasses** (500–900 LOC each) that only do (a) `source_type → fluent_id` mapping and (b) source-specific args formatting. -- **4 AJAX endpoints** for: list sources, list forms in source, import forms, import entries. -- **Template Method pattern** — base class drives the flow, subclasses fill in 5 abstract methods (`exist`, `get_source_forms`, `get_source_form_id`, `get_source_form_name`, `build_form_content`, `get_form_metas`). - -It's clean, proven, and easy to extend. SureForms will adopt the same shape, adapted for: - -1. **REST instead of AJAX** — SureForms already uses `/sureforms/v1/` namespace (`inc/rest-api.php`); AJAX would be inconsistent. -2. **Block markup output instead of JSON** — SureForms forms are Gutenberg block strings in `post_content` of the `sureforms_form` CPT, not JSON in a custom table. So the importer's final stage is to serialize block markup, not assemble a flat field array. -3. **15 SureForms blocks as targets** — instead of Fluent's ~25 field types. - -## 3. Sources to import — confirmed field type maps - -These are lifted directly from Fluent's `fieldTypes()` methods in each migrator class. Total source field types we need to handle: **110** (with overlap). - -### 3.1 Contact Form 7 (13 form-tag types → SureForms blocks) -Source: `wpcf7_contact_form` post type, `_form` meta (shortcode template). - -| CF7 form-tag | SureForms block | Notes | -|---|---|---| -| `text`, `text*` | `srfm/input` | type=text | -| `email`, `email*` | `srfm/email` | | -| `url`, `url*` | `srfm/url` | | -| `tel`, `tel*` | `srfm/phone` | | -| `number`, `number*` | `srfm/number` | | -| `range` | `srfm/number` | slider mode | -| `date` | `srfm/input` | type=date (no native date block) | -| `textarea`, `textarea*` | `srfm/textarea` | | -| `select`, `select*` | `srfm/dropdown` | `multiple` → multi-select | -| `checkbox`, `checkbox*` | `srfm/checkbox` | | -| `radio` | `srfm/multi-choice` | | -| `acceptance` | `srfm/gdpr` | tnc_html → content | -| `file`, `file*` | unsupported in v1 | no native `srfm/file` — flag | -| `quiz`, `captchar`, `hidden` | skipped | legacy / not in v1 scope | - -Detection: `defined('WPCF7_PLUGIN')`. Entries: requires Flamingo plugin. - -### 3.2 WPForms (22 field types → SureForms blocks) -Source: CPT `wpforms`, field data as JSON in `post_content`. - -| WPForms type | SureForms block | Notes | -|---|---|---| -| `text` | `srfm/input` | | -| `email` | `srfm/email` | | -| `url` | `srfm/url` | | -| `phone` | `srfm/phone` | | -| `number`, `number-slider` | `srfm/number` | | -| `textarea`, `richtext` | `srfm/textarea` | rich → fallback | -| `select`, `multi_select` | `srfm/dropdown` | | -| `radio` | `srfm/multi-choice` | | -| `checkbox` | `srfm/checkbox` | | -| `name` | `srfm/input × 3` | split first/middle/last | -| `address` | `srfm/address` | structured fields | -| `date-time` | `srfm/input` | type=date until native block | -| `password` | `srfm/input` | type=password | -| `hidden` | `srfm/input` | type=hidden | -| `html`, `divider` | structural — core/html or section break | | -| `pagebreak` | unsupported in v1 | no native multi-step yet | -| `file-upload`, `rating`, `layout` | unsupported in v1 | flag | - -Detection: `function_exists('wpforms')`. Entries: WPForms Pro only. - -### 3.3 Gravity Forms (22 field types → SureForms blocks) -Source: custom tables `gf_form` + `gf_form_meta` (serialized PHP array). - -| Gravity type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `text` | `srfm/input` | | -| `name` | `srfm/input × 3` | first/middle/last sub-inputs | -| `hidden` | `srfm/input` (type=hidden) | | -| `textarea` | `srfm/textarea` | | -| `website` | `srfm/url` | | -| `phone` | `srfm/phone` | | -| `select`, `multiselect` | `srfm/dropdown` | | -| `checkbox` | `srfm/checkbox` | | -| `radio` | `srfm/multi-choice` | | -| `date`, `time` | `srfm/input` (type=date/time) | | -| `number` | `srfm/number` | | -| `address` | `srfm/address` | | -| `consent` | `srfm/gdpr` | | -| `captcha` | unsupported | external service | -| `html` | core/html (or skip) | | -| `section` | section break / divider | | -| `page` | unsupported in v1 | multi-step | -| `list` | unsupported in v1 | repeater not in srfm | -| `fileupload` | unsupported in v1 | no srfm/file | - -Detection: `class_exists('GFForms')`. Entries: `gf_entry`, `gf_entry_meta` tables. - -### 3.4 Ninja Forms (27 field type aliases → SureForms blocks) -Source: custom tables `nf3_forms`, `nf3_fields`, `nf3_field_meta` (JOIN-based). - -| Ninja type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `textbox`, `firstname`, `lastname`, `address`, `city`, `zip` | `srfm/input` | (Ninja splits address into individual text fields) | -| `liststate`, `listcountry` | `srfm/dropdown` | | -| `textarea` | `srfm/textarea` | | -| `phone` | `srfm/phone` | | -| `select`, `listselect` | `srfm/dropdown` | | -| `listmultiselect` | `srfm/dropdown` (multi) | | -| `radio`, `listradio`, `listimage`, `toggle_switch` | `srfm/multi-choice` | | -| `checkbox`, `listcheckbox` | `srfm/checkbox` | | -| `date` | `srfm/input` (type=date) | | -| `number` | `srfm/number` | | -| `hidden` | `srfm/input` (type=hidden) | | -| `html` | core/html | | -| `hr` | section divider | | -| `starrating` | unsupported in v1 | no srfm/rating | -| `repeater` | unsupported in v1 | | -| `recaptcha` | spam protection — emit as form-level setting | | -| `submit` | handled by `srfm/inline-button` | | - -Detection: `defined('NF_SERVER_URL')`. Entries: `nf_sub` CPT. - -### 3.5 Caldera Forms (26 field types → SureForms blocks) -Source: `Caldera_Forms_Forms::get_forms()` static API. Caldera is unmaintained — migration is a one-shot rescue. - -| Caldera type | SureForms block | Notes | -|---|---|---| -| `email` | `srfm/email` | | -| `text` | `srfm/input` | | -| `hidden` | `srfm/input` (type=hidden) | | -| `textarea`, `paragraph`, `wysiwyg` | `srfm/textarea` | | -| `url` | `srfm/url` | | -| `phone`, `phone_better` | `srfm/phone` | | -| `select`, `dropdown` | `srfm/dropdown` | | -| `filtered_select2` | `srfm/dropdown` (multi) | | -| `radio`, `toggle_switch` | `srfm/multi-choice` | | -| `checkbox` | `srfm/checkbox` | | -| `date_picker`, `date` | `srfm/input` (type=date) | | -| `number`, `range`, `calculation` | `srfm/number` | | -| `range_slider` | `srfm/number` (slider) | | -| `gdpr` | `srfm/gdpr` | | -| `section_break`, `html` | structural | | -| `color_picker`, `star_rating`, `file`, `cf2_file`, `advanced_file` | unsupported in v1 | flag | -| `button` | handled by `srfm/inline-button` | | - -Detection: `defined('CFCORE_VER')`. Caldera abandoned in 2022 — entries critical. - -## 4. SureForms target blocks (the destination schema) - -From `find sureforms/inc/blocks/*/block.json` — **15 blocks** total: - -| Block | Type | Used for | -|---|---|---| -| `srfm/form` | wrapper | Outer form container | -| `srfm/input` | field | Text/email/url/phone/password/date/hidden (HTML type via attr) | -| `srfm/email` | field | Email input | -| `srfm/textarea` | field | Paragraph text | -| `srfm/url` | field | URL input | -| `srfm/number` | field | Numeric input + slider mode | -| `srfm/phone` | field | International phone | -| `srfm/dropdown` | field | Select / multi-select | -| `srfm/multi-choice` | field | Radio / multiple choice | -| `srfm/checkbox` | field | Checkbox group | -| `srfm/address` | field | Structured address (line1/2/city/state/zip/country) | -| `srfm/gdpr` | field | Consent checkbox | -| `srfm/payment` | field | Stripe payment | -| `srfm/payment-history` | display | Past payments table | -| `srfm/inline-button` | structural | Submit / action button | - -**Gaps acknowledged**: no native rich-text, file upload, signature, rating, date-picker, multi-step (page break), or repeater blocks. Fields mapped to these in source plugins are flagged as `unsupported_fields[]` in the v1 response and surfaced in the import UI as warnings. They will be added in subsequent releases. - -## 5. Code layout (mirrors Fluent's, adapted to SureForms conventions) - -``` -sureforms/inc/migrator/ -├── class-migrator-bootstrap.php # ~200 LOC — REST routes, capability checks, factory -├── class-base-migrator.php # ~800 LOC — abstract import loop + idempotency + helpers -├── class-block-templates.php # ~600 LOC — canonical srfm/* block markup templates -├── importers/ -│ ├── class-cf7-importer.php # ~600 LOC — shortcode regex parser -│ ├── class-wpforms-importer.php # ~700 LOC — JSON decode -│ ├── class-gravity-importer.php # ~800 LOC — serialized array parser -│ ├── class-ninja-importer.php # ~600 LOC — JOIN across nf3_* tables -│ └── class-caldera-importer.php # ~500 LOC — Caldera static API -└── views/admin-page.php # ~150 LOC — admin page mount point - -sureforms/src/admin/migrator/ # React UI (~800 LOC) -├── MigratorApp.jsx # Top-level component -├── SourceList.jsx # Tile per detected source -├── FormSelector.jsx # Pick forms to import -├── ImportPreview.jsx # Dry-run rendered output -├── ImportResult.jsx # Success + unsupported warnings -└── EntryImporter.jsx # Per-form entry import -``` - -**Total estimate: ~5,750 LOC** for all 5 sources (close to Fluent's ~5,400 LOC, with the extra slack accounting for SureForms' block-markup serialization). - -## 6. REST API design - -Namespace: `/wp-json/sureforms/v1/migrator/` (extending the existing `/sureforms/v1/` namespace from `inc/rest-api.php`). - -| Method | Route | Body / Query | Returns | -|---|---|---|---| -| GET | `/migrator/sources` | — | `[{key, title, installed, form_count, supports_entries}]` | -| GET | `/migrator/sources/{key}/forms` | — | `[{id, name, field_count, imported_srfm_id, last_imported_at}]` | -| POST | `/migrator/sources/{key}/import` | `{form_ids: [], dry_run: bool, behavior: {}, post_status?: 'draft'\|'publish'}` | `{imported: [{srfm_id, edit_url, source_id}], failed: [], skipped: [], unsupported_fields: [], preview?: {}}` | -| POST | `/migrator/sources/{key}/forms/{id}/entries` _(planned — P3, not yet registered)_ | `{limit?: 1000}` | `{count_imported, count_skipped, count_total}` | - -**Security model** (per `sureforms/CLAUDE.md`): -- Every route's `permission_callback` requires `current_user_can('manage_options')`. -- REST cookie nonce (`X-WP-Nonce`) for all routes — these are admin-only, no public access. -- Sanitize `form_ids` via `array_map('absint', ...)`, `key` via allowlist `['cf7','wpforms','gravity','ninja','caldera']`. - -## 7. Canonical block template dictionary - -Analogue of Fluent's 1,200-line `defaultFieldConfig()` array, but emitting Gutenberg block markup strings instead of field-config arrays. - -One method per target block in `class-block-templates.php`: - -```php -final class SRFM_Block_Templates { - - public static function input( array $args ): string { - $defaults = [ - 'label' => '', - 'placeholder' => '', - 'required' => false, - 'field_type' => 'text', // text|email|number|password|date|hidden - 'help_text' => '', - 'default_val' => '', - 'min_length' => '', - 'max_length' => '', - 'css_class' => '', - ]; - $attrs = wp_parse_args( $args, $defaults ); - // Match the schema in src/blocks/input/block.json - $json = wp_json_encode( self::strip_empty( [ - 'label' => $attrs['label'], - 'placeholder' => $attrs['placeholder'], - 'required' => $attrs['required'], - 'fieldType' => $attrs['field_type'], - 'help' => $attrs['help_text'], - 'defaultValue' => $attrs['default_val'], - 'minLength' => $attrs['min_length'], - 'maxLength' => $attrs['max_length'], - 'className' => $attrs['css_class'], - ] ) ); - return "\n"; - } - - public static function email( array $args ): string { /* ... */ } - public static function dropdown( array $args ): string { /* ... */ } - public static function checkbox( array $args ): string { /* ... */ } - // ... one per srfm/* block - - public static function form_wrapper( string $inner_content ): string { - return "\n{$inner_content}\n"; - } -} -``` - -The base migrator's import loop concatenates the per-field strings, wraps them in `form_wrapper()`, and persists via `wp_insert_post(['post_type' => 'sureforms_form', 'post_content' => $markup])`. - -## 8. Base migrator contract (abstract methods) - -```php -abstract class SRFM_Base_Migrator { - - protected string $key; // 'cf7' | 'wpforms' | 'gravity' | 'ninja' | 'caldera' - protected string $title; // Display name - protected array $unsupported_fields = []; - - // Per-source plumbing (abstract — subclasses implement) - abstract public function exist(): bool; - abstract protected function get_source_forms(): array; - abstract protected function get_source_form_id( array $form ); - abstract protected function get_source_form_name( array $form ): string; - abstract protected function build_form_content( array $form ): string; // concatenated block markup - abstract protected function get_form_metas( array $form ): array; // notifications, confirmations - // NOTE: get_entries() is planned for the entries-migration phase (P3); not shipped yet. - - // Shared (in base): - public function list_forms(): array; - public function count_source_forms(): int; - public function import_forms( array $selected_ids = [], bool $dry_run = false, array $behavior = [], string $post_status = 'publish' ): array; - protected function find_existing_srfm_id( $source_id ): int; - protected function record_import_mapping( int $srfm_id, $source_id ): void; -} -``` - -## 9. Idempotent re-imports - -Same pattern as Fluent: a single `wp_option('srfm_imported_forms_map')` storing: - -```php -[ - 123 => [ 'source_id' => 'cf7-45', 'source_key' => 'cf7', 'last_imported' => '2026-05-22 10:00:00' ], - 124 => [ 'source_id' => '8', 'source_key' => 'wpforms', 'last_imported' => '2026-05-22 10:01:00' ], -] -``` - -`is_already_imported()`: -1. Look up `source_key` + `source_id` in the map. -2. If found and the matching `sureforms_form` post still exists → **update** (not insert). -3. If the SureForms form was deleted → prune the stale entry, fresh insert. - -## 10. Unsupported field tracking - -Same pattern as Fluent: -- `$this->unsupported_fields[]` is pushed each time a source field's type isn't in the source's `field_type_map`, capturing the field's user-facing label (not internal id). -- Returned in the REST response as `unsupported_fields: []`. -- Admin UI renders these as a yellow callout: "These fields couldn't be migrated to SureForms — add them manually after import: Field A, Field B, Field C." - -## 11. Entry migration - -- **Form-by-form, not bulk** — same as Fluent. -- **Hard cap of 1,000 entries per form**, override via the `srfm/entry_migration_max_limit` filter. -- **Idempotent**: import deletes existing SureForms entries for that form first (via the same `resetEntries()` pattern Fluent uses) before inserting. -- **Files**: copy via `WP_Filesystem_Direct::copy()` into `wp-content/uploads/sureforms/migrated/`. - -## 12. Admin UI flow - -Lives at **WordPress → SureForms → Tools → Migrator** (new sub-tab next to existing export/import). - -``` -[ Step 1: Select source ] Detected plugins shown as tiles with logo + install count - ▼ click "Migrate from Contact Form 7" - -[ Step 2: Select forms ] Table of forms in source (name | fields | already-imported badge) - ▼ bulk select + "Preview" - -[ Step 3: Dry-run preview ] Server returns block markup; UI renders in a code pane - + warning callout for unsupported fields - ▼ "Confirm & Import" - -[ Step 4: Result ] "Imported X forms." Links: [Edit in SureForms] [Import entries] - Unsupported-fields list (if any) -``` - -The dry-run preview is the **one improvement over Fluent's UX** — it answers the user's "is this going to break anything?" anxiety before they commit. - -## 13. Implementation phasing - -Four mergeable PRs, each ~7–10 working days: - -| Phase | Deliverable | LOC | -|---|---|---| -| **P1: Foundation** | `Base_Migrator`, `Block_Templates`, REST routes, admin page shell, CF7 importer end-to-end | ~2,400 | -| **P2: WPForms + Gravity** | Two more importers + dry-run preview UI | ~1,700 | -| **P3: Ninja + Caldera** | Final two importers + entry import flow | ~1,300 | -| **P4: Polish** | Field-mapping overrides, entry import UI, docs, E2E tests | ~350 | - -Total: ~5,750 LOC delivered across ~4–5 weeks. - -## 14. Testing approach - -- **Unit tests** (PHPUnit, per `composer test`): per importer, fixture-driven — store sample source-form payloads in `tests/fixtures/migrator/{source}/*.json` and assert the produced block markup string matches a golden snapshot. -- **Integration tests**: a real CF7 / WPForms / Gravity form created via their PHP APIs in a `tests/Integration/Migrator/` test, imported via `Base_Migrator::import_forms()`, then assert a `sureforms_form` post exists with the right block content. -- **Playwright E2E** (per `npm run play:run`): full admin flow — open Migrator, pick CF7, select a form, dry-run, confirm import, verify the new form renders correctly on the front-end. -- **Manual exploratory**: all 5 source plugins are already installed locally, so the test environment is real, not mocked. - -## 15. Verification (when is this done) - -1. All 5 importers exist under `sureforms/inc/migrator/importers/` and pass their PHPUnit fixtures. -2. REST endpoints respond correctly: `GET /sureforms/v1/migrator/sources` lists the 5 sources with `installed: true` for whichever plugins are active. -3. Admin page at SureForms → Tools → Migrator renders the tile-based source picker. -4. Dry-run for a non-trivial CF7 form (text + email + textarea + radio + acceptance) produces valid Gutenberg block markup that renders correctly in the SureForms editor. -5. Re-importing the same source form updates the existing SureForms form (not a duplicate). -6. Entry migration imports up to 1,000 entries for a WPForms Pro form and the entries appear in SureForms' Entries admin. -7. Unsupported-field warnings appear in the UI for source fields without a SureForms target (e.g. CF7 `[file]`, WPForms `rating`). -8. PHPStan level 9 passes (`composer phpstan`) and PHPCS passes (`composer lint`). - -## 16. Reference source files (Fluent Forms — production reference) - -| Fluent file | Our equivalent | Why useful | -|---|---|---| -| `app/Services/Migrator/Bootstrap.php` | `class-migrator-bootstrap.php` | AJAX → REST route mapping | -| `app/Services/Migrator/Classes/BaseMigrator.php` | `class-base-migrator.php` | Import loop, idempotency, file migration helpers | -| `app/Services/Migrator/Classes/ContactForm7Migrator.php` | `class-cf7-importer.php` | Battle-tested shortcode regex parser (`formatAsFluentField()`) | -| `app/Services/Migrator/Classes/WpFormsMigrator.php` | `class-wpforms-importer.php` | JSON-decode + 22-entry field map | -| `app/Services/Migrator/Classes/GravityFormsMigrator.php` | `class-gravity-importer.php` | Serialized-array parsing + Name/Address sub-input handling | -| `app/Services/Migrator/Classes/NinjaFormsMigrator.php` | `class-ninja-importer.php` | JOIN across `nf3_*` tables via Ninja's PHP API | -| `app/Services/Migrator/Classes/CalderaMigrator.php` | `class-caldera-importer.php` | `Caldera_Forms_Forms` static API | - -Fluent Forms is GPL-2.0+ — we can lift regexes and parsing patterns directly with attribution comments. The `defaultFieldConfig()` array structure should be re-written for SureForms blocks, not copied (different output format). - -## 16a. Metadata migration - -Form metadata (email notifications, confirmation messages, compliance flags, integrations) lives **outside** the block-markup pipeline — it's persisted as `sureforms_form` post meta. The migrator handles it via the abstract `Base_Migrator::get_form_metas()` method which returns a `meta_key => meta_value` array passed to `wp_insert_post()` / `wp_update_post()` via `meta_input`. - -### What every importer must produce - -A SureForms-shaped payload. The two keys every importer should populate are: - -| SureForms meta key | Schema (registered in `inc/post-types.php`) | -|---|---| -| `_srfm_email_notification` | Array of objects: `{id, status, is_raw_format, name, email_to, email_reply_to, from_name, from_email, email_cc, email_bcc, subject, email_body}` | -| `_srfm_form_confirmation` | Array of objects: `{id, confirmation_type, page_url, custom_url, message, submission_action, enable_query_params, query_params}` | - -Other meta keys exist (`_srfm_compliance`, `_srfm_conditional_logic`, `_srfm_forms_styling`, `_srfm_integrations_webhooks`) but are not required for a usable imported form — they default to SureForms' built-in `register_post_meta` defaults if not provided. - -### What each source can give us (and what we ship in v1) - -| Source | Notifications | Confirmations | Other meta | Status in v1 | -|---|---|---|---|---| -| **CF7** | `_mail`, `_mail_2` use `[field-name]` shortcodes — incompatible with SureForms smart tags | `_messages` array (success/error text) | None | **Stub only** — admin notification to `{admin_email}` with `{all_data}` body, generic success message. Form is usable; users can refine in Single Form Settings. (Matches Fluent's CF7 approach for the same reason.) | -| **WPForms** | `settings.notifications[]` — fully structured (subject/body/recipient/CC/BCC/replyTo/fromName) | `settings.confirmations[]` — multi-confirmation with conditional logic | Webhooks | **Full migration planned in P2** | -| **Gravity** | Notifications, conditional notifications, routing | Confirmations, scheduling, login-required, entry limits | Advanced validation | **Full migration planned in P2** | -| **Ninja** | `Actions` API — type `email`, `successmessage`, `redirect`, `save` | Same Actions API | — | **Full migration planned in P3** | -| **Caldera** | `mailer` config in form settings | `successMessage` field | — | **Full migration planned in P3** | - -### Why CF7 is special - -CF7's email body uses `[your-name]`, `[your-email]` style shortcodes that reference the form-tag `name` attribute. Translating these to SureForms' `{slug_field_id}` smart-tag system requires either (a) maintaining a per-form name→slug mapping during import, or (b) rewriting the body string with regex substitutions that match the field tokens we emitted. Both approaches add ~400 LOC and break easily on edge cases (multi-line bodies, mixed shortcodes/text, CF7 special mail tags like `[_remote_ip]`). - -Fluent Forms — with 700K+ installs and 5+ years of migrator iteration — landed on the same stub-only conclusion for CF7. v1 matches that decision; if user demand surfaces, CF7 mail-template translation is a separate scoped feature. - -### Code pointers (live) - -- **Abstract method**: `Base_Migrator::get_form_metas()` — `inc/migrator/base-migrator.php` -- **Wire-up**: `Base_Migrator::insert_form_post()` / `update_form_post()` both accept a `$metas` arg and pass it to `wp_insert_post()` via `meta_input` -- **CF7 implementation**: `Cf7_Importer::get_form_metas()` — `inc/migrator/importers/cf7-importer.php` -- **SureForms meta schemas** (the contract we have to match): `inc/post-types.php` lines 911-1010 (notification), 1086-1182 (confirmation) - -### Verification - -After importing a CF7 form via the Migration tab, open the resulting SureForms form's settings: -- **Email Notifications** tab → should show one enabled notification named "Admin Notification Email" with subject "New submission: {form title}" and body `{all_data}`. -- **Form Confirmation** tab → should show a centered "Thank you" success message, "Same page" confirmation type. - -If those panels are empty, the meta payload didn't persist — check `wp_options.srfm_imported_forms_map` and `wp_postmeta` for the form id. - -## 17. Out of scope for v1 - -- Polls / Quizzes (Forminator, Gravity surveys) — no SureForms equivalent. -- Formidable Views — front-end data display is its own feature. -- WS Form — niche, defer. -- Multi-step / page break preservation — wait for native `srfm/page-break` block. -- File-upload, signature, rating, repeater fields — wait for native blocks (flagged as unsupported until then). -- Bulk source-selection ("import everything from everywhere") — one source at a time in v1. - -## 18. Sources verified live (May 2026) - -All wordpress.org plugin pages were fetched live during research: - -- [Contact Form 7](https://wordpress.org/plugins/contact-form-7/) — 10M+ installs, v6.1.6 -- [WPForms Lite](https://wordpress.org/plugins/wpforms-lite/) — 6M+ installs, v1.10.0.5 -- [Gravity Forms docs](https://docs.gravityforms.com/form-fields/) — ~655K live sites, commercial -- [Ninja Forms](https://wordpress.org/plugins/ninja-forms/) — 600K+ installs, v3.14.4 -- Caldera Forms — unmaintained since 2022, ~50K legacy sites - -**Combined migration pool: ~17.3M active sites** — the addressable target for v1. diff --git a/docs/wiki/AI-Form-Builder.md b/docs/wiki/AI-Form-Builder.md deleted file mode 100644 index a35a46a4e..000000000 --- a/docs/wiki/AI-Form-Builder.md +++ /dev/null @@ -1,103 +0,0 @@ -# AI Form Builder - -SureForms includes an AI-powered form generator that creates complete forms from natural language descriptions. - -## Architecture - -``` -inc/ai-form-builder/ -|-- ai-form-builder.php # Main AI builder class -|-- ai-auth.php # Authentication and billing -|-- ai-helper.php # AI utility functions -|-- field-mapping.php # Maps AI response to Gutenberg blocks -``` - -## Authentication Flow - -The `AI_Auth` class handles authentication with the SureForms middleware API: - -1. User initiates authentication from admin settings -2. `GET /initiate-auth` REST endpoint generates an auth URL -3. User is redirected to the billing portal (`SRFM_BILLING_PORTAL`) -4. After authentication, an encrypted access key is returned -5. `POST /handle-access-key` decrypts and stores the key in WordPress options -6. Subsequent AI requests use this key for authorization - -### Constants - -| Constant | URL | Purpose | -|----------|-----|---------| -| `SRFM_AI_MIDDLEWARE` | `https://credits.startertemplates.com/sureforms/` | AI credits API | -| `SRFM_MIDDLEWARE_BASE_URL` | `https://api.sureforms.com/` | API middleware | -| `SRFM_BILLING_PORTAL` | `https://billing.sureforms.com/` | Billing portal | - -## Form Generation Process - -### Step 1: User Input - -The user provides a natural language description of the form they want (e.g., "Create a contact form with name, email, phone, and message fields"). - -### Step 2: AI Generation - -`POST /generate-form` sends the description to the AI middleware: - -``` -User prompt -> REST API -> AI_Form_Builder::generate_ai_form() - -> External AI middleware API call - -> AI generates form structure as JSON - -> Returns field definitions -``` - -### Step 3: Field Mapping - -`POST /map-fields` converts the AI response to SureForms Gutenberg blocks: - -``` -AI JSON response -> Field_Mapping::generate_gutenberg_fields_from_questions() - -> Maps each AI field to a SureForms block type - -> Generates block attributes - -> Creates Gutenberg block markup - -> Returns blocks ready for the editor -``` - -### Field Type Mapping - -The `Field_Mapping` class maps AI-suggested field types to SureForms blocks: - -| AI Field Type | SureForms Block | -|---------------|----------------| -| text / name | `srfm/input` | -| email | `srfm/email` | -| phone / tel | `srfm/phone` | -| textarea / message | `srfm/textarea` | -| number | `srfm/number` | -| url / website | `srfm/url` | -| select / dropdown | `srfm/dropdown` | -| checkbox | `srfm/checkbox` | -| radio / multiple choice | `srfm/multichoice` | -| address | `srfm/address` | - -## REST API Endpoints - -| Method | Endpoint | Handler | Description | -|--------|----------|---------|-------------| -| POST | `/generate-form` | `AI_Form_Builder::generate_ai_form` | Send prompt to AI, get form structure | -| POST | `/map-fields` | `Field_Mapping::generate_gutenberg_fields_from_questions` | Convert AI fields to blocks | -| GET | `/initiate-auth` | `AI_Auth::get_auth_url` | Get billing portal auth URL | -| POST | `/handle-access-key` | `AI_Auth::handle_access_key` | Save decrypted access key | - -All endpoints require `manage_options` capability. - -## AI Helper - -The `AI_Helper` class provides utility functions for: -- API key management -- Credit balance checking -- Request formatting -- Error handling for AI middleware responses - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- All API endpoints -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block types the AI maps to -- [Architecture Overview](Architecture-Overview) -- AI classes in bootstrap flow diff --git a/docs/wiki/Admin-Dashboard.md b/docs/wiki/Admin-Dashboard.md deleted file mode 100644 index 9c16aea85..000000000 --- a/docs/wiki/Admin-Dashboard.md +++ /dev/null @@ -1,121 +0,0 @@ -# Admin Dashboard - -The SureForms admin dashboard is a React single-page application (SPA) integrated into the WordPress admin area. - -## Architecture - -### Entry Point - -The admin React app is bootstrapped in `src/admin/editor-scripts.js`, which serves as the Webpack entry point. It mounts the React app to a DOM container registered by the PHP `Admin` class. - -### Tech Stack - -| Technology | Purpose | -|------------|---------| -| React 18 | UI rendering | -| react-router-dom 6 | Client-side routing | -| @tanstack/react-query 5 | Server state management and data fetching | -| @wordpress/api-fetch | REST API client with nonce handling | -| @wordpress/data | WordPress data store integration | -| @bsf/force-ui | BSF component library (buttons, modals, inputs, etc.) | -| Tailwind CSS 3 | Utility-first styling | -| react-hot-toast | Toast notifications | - -### Admin Pages - -The React app includes the following pages/routes: - -| Route | Component | Description | -|-------|-----------|-------------| -| Dashboard | `src/admin/dashboard/` | Overview with charts and stats | -| Forms | `src/admin/forms/` | Forms listing with search, filter, bulk actions | -| Entries | `src/admin/entries/` | Entry listing and management | -| Settings | `src/admin/settings/` | Global plugin settings | -| Payments | `src/admin/payment/` | Payment transactions listing | -| Single Form Settings | `src/admin/single-form-settings/` | Per-form configuration | - -### Shared Components - -Common admin components are in `src/admin/components/`: - -- `PageHeader` -- Consistent page header with title and actions -- `TemplatePicker` -- Form template selection UI -- Loading skeletons (via `react-loading-skeleton`) -- Modal dialogs and confirmation prompts - -## WordPress Integration - -### Menu Registration - -The PHP `Admin` class (`admin/admin.php`) registers the WordPress admin menu: - -```php -add_menu_page( - 'SureForms', - 'SureForms', - 'manage_options', - 'sureforms_menu', - [ $this, 'render_admin_page' ], - 'dashicons-feedback', - 30 -); -``` - -### Asset Enqueuing - -Admin assets are enqueued only on SureForms admin pages: - -- React app bundle (from `assets/build/`) -- Tailwind CSS styles -- Localized data via `wp_localize_script()` providing: - - REST API URL and nonce - - Current user data - - Plugin settings - - Form data - -## Data Fetching - -### @tanstack/react-query - -Used for server state management with automatic caching, refetching, and invalidation: - -```javascript -const { data, isLoading } = useQuery({ - queryKey: ['entries', filters], - queryFn: () => apiFetch({ path: '/sureforms/v1/entries/list', ... }), -}); -``` - -### @wordpress/api-fetch - -WordPress API client with automatic nonce injection: - -```javascript -import apiFetch from '@wordpress/api-fetch'; - -apiFetch({ path: '/sureforms/v1/form-data' }) - .then(data => { /* handle response */ }); -``` - -## Force UI Component Library - -`@bsf/force-ui` is BSF's internal component library providing: - -- Buttons, inputs, selects, toggles -- Modals and dialogs -- Tables and data display -- Navigation components -- Consistent design system - -Components are imported directly: - -```javascript -import { Button, Modal, Input } from '@bsf/force-ui'; -``` - -## Related Pages - -- [State Management](State-Management) -- WordPress data store -- [Frontend Assets](Frontend-Assets) -- Asset loading pipeline -- [REST API Reference](REST-API-Reference) -- API endpoints used by dashboard -- [Environment Configuration](Environment-Configuration) -- Build setup diff --git a/docs/wiki/Architecture-Overview.md b/docs/wiki/Architecture-Overview.md deleted file mode 100644 index 523f18cc8..000000000 --- a/docs/wiki/Architecture-Overview.md +++ /dev/null @@ -1,294 +0,0 @@ -# Architecture Overview - -This page describes the high-level architecture of the SureForms WordPress plugin (v2.5.1), covering the bootstrap flow, namespace conventions, directory layout, design patterns, and data flow. - -## High-Level Architecture Diagram - -``` -+------------------------------------------------------+ -| WordPress Core | -| (hooks: plugins_loaded, init, rest_api_init, etc.) | -+---------------------------+--------------------------+ - | - +--------v---------+ - | sureforms.php | - | (entry point, | - | constants) | - +--------+---------+ - | - +--------v---------+ - | plugin-loader.php| - | (Plugin_Loader) | - | - autoloader | - | - hook setup | - | - class init | - +--------+---------+ - | - +------------------+------------------+ - | | | - plugins_loaded(99) init admin_init - load_plugin() load_classes() activation_redirect() - | | - +------v------+ +------v------+ - | Core Classes| | Admin/Block | - | Post_Types | | Register | - | Form_Submit | | Admin | - | Rest_Api | | Payments | - | Helper | | Duplicate | - | ...20+ more | +-------------+ - +------+------+ - | - +------v------+ +-------------+ +-------------+ - | Database | | REST API | | Frontend | - | Register | | /sureforms/ | | Assets | - | Entries tbl | | v1/... | | Form Markup | - | Payments tbl| +-------------+ +-------------+ - +-------------+ -``` - -## Plugin Bootstrap Flow - -### 1. Entry Point: sureforms.php - -The main plugin file defines all global constants and then requires `plugin-loader.php`: - -```php -define( 'SRFM_FILE', __FILE__ ); -define( 'SRFM_DIR', plugin_dir_path( SRFM_FILE ) ); -define( 'SRFM_URL', plugins_url( '/', SRFM_FILE ) ); -define( 'SRFM_VER', '2.5.1' ); -define( 'SRFM_FORMS_POST_TYPE', 'sureforms_form' ); -define( 'SRFM_ENTRIES', 'sureforms_entries' ); -define( 'SRFM_PAYMENTS', 'sureforms_payments' ); -require_once 'plugin-loader.php'; -``` - -### 2. Plugin Loader: plugin-loader.php - -The `SRFM\Plugin_Loader` class is the central bootstrap orchestrator: - -1. Loads the Action Scheduler library from `inc/lib/action-scheduler/action-scheduler.php` -2. Registers the PSR-4-style autoloader via `spl_autoload_register()` -3. Hooks into WordPress lifecycle events: - - `plugins_loaded` (default priority) -- `load_textdomain()` - - `plugins_loaded` (priority 99) -- `load_plugin()` (main class initialization) - - `init` -- `load_classes()` (block registration, admin, payments) - - `admin_init` -- `activation_redirect()` -4. Instantiates `Analytics` singleton immediately -5. Registers activation hook (`Activator::activate()`) -6. Registers deactivation hook (clears scheduled events) - -The `Plugin_Loader` fires the `srfm_core_loaded` action after first instantiation. - -### 3. Class Initialization Order - -**On `plugins_loaded` (priority 99) via `load_plugin()`:** - -| Order | Class | Responsibility | -|-------|-------|---------------| -| 1 | `Post_Types` | Registers `sureforms_form` CPT and all post meta | -| 2 | `Form_Submit` | Registers `/submit-form` and `/refresh-nonces` REST endpoints | -| 3 | `Gutenberg_Hooks` | Block editor integration hooks | -| 4 | `Frontend_Assets` | Frontend CSS/JS enqueue | -| 5 | `Helper` | Utility/helper functions | -| 6 | `Activator` | First-run setup and migrations | -| 7 | `Admin_Ajax` | WordPress AJAX handlers | -| 8 | `Forms_Data` | Form data queries and listing | -| 9 | `Export` | Import/export functionality | -| 10 | `Smart_Tags` | Template tag processing | -| 11 | `Generate_Form_Markup` | Form HTML rendering | -| 12 | `Create_New_Form` | New form creation flow with default meta | -| 13 | `Global_Settings` | Plugin-wide settings management | -| 14 | `Email_Summary` | Scheduled email digest reports | -| 15 | `Compliance_Settings` | GDPR/privacy per-form settings | -| 16 | `Events_Scheduler` | Cron-based scheduled events | -| 17 | `AI_Form_Builder` | AI-powered form generation | -| 18 | `Field_Mapping` | AI response to Gutenberg block mapping | -| 19 | `Background_Process` | Background task processing via Action Scheduler | -| 20 | `Page_Builders` | Third-party page builder integrations | -| 21 | `Rest_Api` | Admin REST endpoint registration (20+ routes) | -| 22 | `AI_Helper` | AI utility functions | -| 23 | `AI_Auth` | AI authentication and billing portal | -| 24 | `Updater` | Plugin update logic | -| 25 | `Onboarding` | First-run onboarding wizard | -| 26 | `DatabaseRegister` | Custom table creation/migration | -| 27 | `Form_Restriction` | Submission limits, scheduling, and entry caps | -| 28 | `Astra` | Astra theme compatibility layer | - -**On `init` via `load_classes()`:** - -| Class | Condition | Responsibility | -|-------|-----------|---------------| -| `Blocks\Register` | Always | Gutenberg block type registration | -| `Admin` | `is_admin()` only | Admin menu pages, assets, and screens | -| `Notice_Manager` | `is_admin()` only | Admin notice display and management | -| `Payments` | Always | Payment gateway setup (Stripe) | -| `Duplicate_Form` | Always | Form duplication handler | - -## Namespace and Autoloading - -### Namespace Structure - -The root namespace is `SRFM`. All classes follow `SRFM\{directory}\{ClassName}`: - -``` -SRFM\ -> plugin-loader.php -SRFM\Inc\Helper -> inc/helper.php -SRFM\Inc\Rest_Api -> inc/rest-api.php -SRFM\Inc\Form_Submit -> inc/form-submit.php -SRFM\Inc\Database\Base -> inc/database/base.php -SRFM\Inc\Database\Tables\Entries -> inc/database/tables/entries.php -SRFM\Inc\AI_Form_Builder\* -> inc/ai-form-builder/ -SRFM\Inc\Traits\Get_Instance -> inc/traits/get-instance.php -SRFM\Admin\Admin -> admin/admin.php -``` - -### Autoloader Logic - -The autoloader in `Plugin_Loader::autoload()`: - -1. Strips the root `SRFM\` namespace prefix -2. Converts `CamelCase` to `kebab-case` -3. Replaces underscores with hyphens -4. Replaces namespace separators with directory separators -5. Converts to lowercase, prepends `SRFM_DIR`, appends `.php` -6. Rejects any path containing `..` (directory traversal protection) - -## Directory Layout - -``` -sureforms/ -|-- sureforms.php # Plugin entry point, all SRFM_* constants -|-- plugin-loader.php # Autoloader, bootstrap, hook registration -|-- inc/ # Core PHP classes (SRFM\Inc namespace) -| |-- helper.php # Static utility methods -| |-- rest-api.php # REST API endpoint registration -| |-- form-submit.php # Public form submission handler -| |-- post-types.php # sureforms_form CPT and post meta -| |-- entries.php # Entry CRUD operations -| |-- blocks/ # Gutenberg block server-side rendering -| |-- database/ # Custom DB table management -| |-- fields/ # Form field type definitions and validation -| |-- payments/ # Payment gateway integrations (Stripe) -| |-- email/ # Email template rendering -| |-- ai-form-builder/ # AI-powered form generation -| |-- global-settings/ # Plugin-wide settings -| |-- single-form-settings/ # Per-form configuration -| |-- compatibility/ # Theme/plugin compatibility -| |-- page-builders/ # Page builder integrations -| |-- traits/ # Shared PHP traits (Get_Instance) -| |-- lib/ # Bundled third-party libraries (do not modify) -|-- src/ # JS/React source (compiled by wp-scripts) -| |-- admin/ # Admin dashboard React app -| |-- blocks/ # Gutenberg block editor components -| |-- components/ # Shared React components -| |-- store/ # WordPress @wordpress/data store -| |-- utils/ # JS utilities -|-- modules/ # Feature modules (Gutenberg, Quick Action Sidebar) -|-- admin/ # Admin PHP classes and assets -|-- sass/ # SASS source files (compiled to assets/css/) -|-- assets/ # Compiled and static assets -|-- templates/ # PHP templates (BladeOne engine) -|-- tests/ # PHPUnit, Playwright, and Docker setup -``` - -## Design Patterns - -### Singleton via Get_Instance Trait - -Nearly every class uses the `Get_Instance` trait from `inc/traits/get-instance.php`: - -```php -trait Get_Instance { - private static $instance = null; - public static function get_instance() { - if ( null === self::$instance ) { - self::$instance = new self(); - } - return self::$instance; - } -} -``` - -### Abstract Base Class (Database) - -The database layer uses `SRFM\Inc\Database\Base` providing: - -- Table creation via `dbDelta()` -- Schema versioning with integer versions stored in options -- Column migration (`maybe_add_new_columns()`, `maybe_rename_columns()`) -- CRUD wrappers (`use_insert()`, `use_update()`, `use_delete()`) -- Query builder with parameterized WHERE supporting `=`, `!=`, `>`, `<`, `LIKE`, `IN` -- In-memory result caching keyed by MD5 of SQL query -- Schema-driven JSON encoding/decoding for array columns - -### Hook-Based Initialization - -Classes register WordPress hooks in their constructors, invoked during singleton instantiation in `load_plugin()` or `load_classes()`. - -### Filter-Driven Extensibility - -| Filter | Purpose | -|--------|---------| -| `srfm_rest_api_endpoints` | Extend REST API routes | -| `srfm_register_post_meta` | Register additional form post meta | -| `srfm_form_submit_data` | Modify form data before processing | -| `srfm_entry_value` | Transform entry field values | -| `srfm_is_special_block` | Declare custom blocks as special | -| `srfm_handle_special_block` | Custom processing for special blocks | -| `srfm_form_confirmation_params` | Extend form confirmation settings | -| `srfm_normalize_csv_field_value` | Custom CSV export formatting | - -## Data Flow - -``` -1. FORM CREATION - Admin creates form in Gutenberg block editor - -> Saved as sureforms_form CPT (wp_posts) - -> Settings stored as post meta (_srfm_* keys) - -> Block content in post_content column - -2. FORM DISPLAY - Form embedded via shortcode, Gutenberg block, or Instant Form URL - -> Generate_Form_Markup::get_form_markup() renders HTML - -> Frontend_Assets enqueues CSS/JS - -> Submission nonce generated - -3. FORM SUBMISSION - User submits via JavaScript fetch() - -> POST /wp-json/sureforms/v1/submit-form - -> Nonce verification, field validation - -> CAPTCHA validation (reCAPTCHA, hCaptcha, Turnstile) - -> Honeypot spam check - -4. ENTRY STORAGE - -> JSON-encoded form data inserted into wp_srfm_entries - -> Email notifications sent - -> Payment processed if applicable (wp_srfm_payments) - -> Confirmation response returned - -5. POST-SUBMISSION - -> srfm_form_submit and srfm_after_submission_process actions fired -``` - -## Key Constants - -| Constant | Value | Purpose | -|----------|-------|---------| -| `SRFM_FILE` | `__FILE__` | Main plugin file path | -| `SRFM_DIR` | Plugin dir path | File system operations | -| `SRFM_URL` | Plugin URL | Asset URL generation | -| `SRFM_VER` | `2.5.1` | Version for cache busting | -| `SRFM_SLUG` | `srfm` | Short identifier slug | -| `SRFM_FORMS_POST_TYPE` | `sureforms_form` | Custom post type slug | -| `SRFM_ENTRIES` | `sureforms_entries` | Entries identifier | -| `SRFM_PAYMENTS` | `sureforms_payments` | Payments identifier | -| `SRFM_PRO_RECOMMENDED_VER` | `2.5.1` | Minimum Pro version | - -## Related Pages - -- [Database Schema](Database-Schema) -- Custom tables, post types, and meta keys -- [Environment Configuration](Environment-Configuration) -- Development setup, build tools, and linting -- [REST API Reference](REST-API-Reference) -- Full endpoint documentation -- [Form Submission Flow](Form-Submission-Flow) -- Detailed submission pipeline -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- All actions and filters diff --git a/docs/wiki/Block-Editor-Controls.md b/docs/wiki/Block-Editor-Controls.md deleted file mode 100644 index 7d5cd1e33..000000000 --- a/docs/wiki/Block-Editor-Controls.md +++ /dev/null @@ -1,109 +0,0 @@ -# Block Editor Controls - -SureForms provides custom inspector controls and utilities for the Gutenberg block editor experience. - -## Directory Structure - -``` -src/srfm-controls/ -|-- addCommonData.js # Inject common data into blocks -|-- generateCSS.js # Dynamic CSS generation for blocks -|-- block-icons.js # Block icon definitions -|-- fonts.js # Font management utilities -|-- maybeGetColorForVariable.js # CSS variable color resolution -|-- parseIcon.js # SVG icon parser -|-- renderIcon.js # Icon rendering component - -src/blocks-attributes/ -|-- getMetakeys.js # Form meta key retrieval -|-- getFormId.js # Get current form ID -|-- getBlocksDefaultAttributes.js # Block attribute defaults -``` - -## CSS Generation System - -`generateCSS.js` dynamically generates CSS for block styling: - -- Converts block attributes to CSS properties -- Supports responsive breakpoints (desktop, tablet, mobile) -- Generates inline styles injected into the page -- Handles CSS variable references - -This system allows each block instance to have unique styling without class conflicts. - -## Block Icons - -`block-icons.js` provides SVG icon components for all SureForms blocks, used in: - -- Block inserter panel -- Block toolbar -- Block settings sidebar - -Additional icon sets from Spectra are available via `spectra-icons-v*.php` files. - -Icon utilities: -- `parseIcon.js` -- Parses SVG string into React-renderable format -- `renderIcon.js` -- React component for rendering parsed icons - -## Common Data Injection - -`addCommonData.js` injects shared data into block components: - -- Form-level settings accessible to all child blocks -- Common attributes and defaults -- Shared configuration values - -## Meta Key Integration - -`getMetakeys.js` retrieves form post meta keys for use in block controls: - -- Provides meta key names for form settings -- Used by inspector controls to read/write form meta -- Integrates with `@wordpress/data` store - -## Block Attribute Defaults - -`getBlocksDefaultAttributes.js` provides default attribute values for all block types: - -- Ensures consistent defaults across block instances -- Used during block registration -- Prevents undefined attribute errors - -## Form ID Resolution - -`getFormId.js` resolves the current form's post ID: - -- Used by blocks to identify their parent form -- Required for meta key operations -- Supports nested block contexts - -## Field Preview - -`src/blocks/FieldsPreview.jsx` renders a preview of form fields in the block editor: - -- Shows a visual representation of the field in editor mode -- Responds to attribute changes in real-time -- Handles edit vs preview states - -## Font Management - -`fonts.js` manages font loading for form styling: - -- Font family selection in block settings -- Google Fonts integration -- Font weight and style options - -## Color Variable Resolution - -`maybeGetColorForVariable.js` resolves WordPress CSS custom properties: - -- Maps color slugs to CSS variable references -- Supports theme color palette integration -- Falls back to raw color values when variables are not available - -## Related Pages - -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block types and registration -- [State Management](State-Management) -- WordPress data store -- [Frontend Assets](Frontend-Assets) -- Editor asset loading -- [Form Fields Architecture](Form-Fields-Architecture) -- Field types diff --git a/docs/wiki/Changelog.md b/docs/wiki/Changelog.md deleted file mode 100644 index 058c89f45..000000000 --- a/docs/wiki/Changelog.md +++ /dev/null @@ -1,39 +0,0 @@ -# Changelog - -Recent changes to SureForms. For the full changelog, see [sureforms.com/whats-new](https://sureforms.com/whats-new/). - -## 2.5.1 - 16th February 2026 - -- **New:** Added option to change currency position (e.g., $100, 100$, $ 100, 100 $) -- **Fix:** Fixed the payment block amount not updating correctly with conditional logic - -## 2.5.0 - 2nd February 2026 - -- **Improvement:** Added translation support for the country list in the phone number field -- **Fix:** Fixed dropdown placeholder and GDPR label translation on the frontend - -## 2.4.0 - 20th January 2026 - -- **New:** Added form scheduling restrictions with start and end date/time -- **New:** Added Previous/Next navigation for single entry page -- **Improvement:** Improved multi-choice block settings UI -- **Improvement:** Updated Syria flag in the Phone field -- **Fix:** Fixed HTML block content corruption when duplicating forms -- **Fix:** Fixed multi-choice options being translated in entries -- **Fix:** Fixed reCAPTCHA v3 validation triggering multiple times on the same page -- **Fix:** Fixed Spectra button styles being affected by SureForms CSS - -## Version Bumping - -When preparing a release, update the version in: - -1. `sureforms.php` -- Plugin header `Version:` and `SRFM_VER` constant -2. `readme.txt` -- `Stable tag:` and changelog section -3. `package.json` -- `version` field - -See [Deployment Guide](Deployment-Guide) for the full release process. - -## Related Pages - -- [Deployment Guide](Deployment-Guide) -- Release workflow and CI/CD -- [Contributing Guide](Contributing-Guide) -- Development workflow diff --git a/docs/wiki/Contributing-Guide.md b/docs/wiki/Contributing-Guide.md deleted file mode 100644 index 441736c7e..000000000 --- a/docs/wiki/Contributing-Guide.md +++ /dev/null @@ -1,205 +0,0 @@ -# Contributing Guide - -Thank you for your interest in contributing to SureForms! This guide covers the development workflow, coding standards, and submission process. - -## Prerequisites - -- **PHP** 7.4+ -- **Node.js** 24.16.0 (managed via [Volta](https://volta.sh/)) -- **Composer** for PHP dependencies -- **Git** for version control -- **Docker** (optional, for wp-env test environment) - -## Getting Started - -1. Fork the repository on GitHub -2. Clone your fork: - ```bash - git clone https://github.com/YOUR_USERNAME/sureforms.git - cd sureforms - ``` -3. Install dependencies: - ```bash - composer install - npm install - ``` -4. Create a feature branch from `next-release`: - ```bash - git checkout next-release - git pull origin next-release - git checkout -b feature/your-feature-name - ``` - -## Git Workflow - -``` -feature/branch --> next-release --> master --> tag/release - (development) (production) -``` - -- **Feature branches** are created from `next-release` -- **Pull requests** target `next-release` -- **Hotfixes** may target `master` directly -- **Release merges** go from `next-release` into `master` - -## Development Workflow - -### Running the Dev Server - -```bash -npm start # Start wp-scripts dev server (hot reload) -``` - -### Building - -```bash -npm run build # Full production build (JS + SASS + minify) -npm run build:script # Build JS only -npm run build:sass # Build SASS only -``` - -### Linting - -Run linters before submitting a PR: - -```bash -# JavaScript -npm run lint-js # ESLint check -npm run lint-js:fix # Auto-fix JS issues - -# CSS -npm run lint-css # Stylelint check -npm run lint-css:fix # Auto-fix CSS issues - -# Formatting -npm run pretty # Prettier check -npm run pretty:fix # Auto-format - -# PHP -vendor/bin/phpcs # PHP CodeSniffer (WPCS) -vendor/bin/phpcbf # Auto-fix PHPCS issues -``` - -### Testing - -All tests should pass before submitting a PR: - -```bash -# PHP unit tests -vendor/bin/phpunit - -# JavaScript unit tests -npm run test:unit - -# E2E tests (requires wp-env) -npm run play:up # Start environment -npm run play:run # Run Playwright tests -``` - -See [Testing Guide](Testing-Guide) for detailed test setup. - -## Coding Standards - -### PHP Standards - -SureForms follows **WordPress Coding Standards (WPCS)** enforced via `phpcs.xml`: - -- **Escaping**: Use `esc_html()`, `esc_attr()`, `esc_url()`, `wp_kses_post()` for all output -- **Nonces**: Use `wp_nonce_field()` / `check_ajax_referer()` for form submissions and AJAX -- **Capabilities**: Always check `current_user_can()` before privileged operations -- **Sanitization**: Use `sanitize_text_field()`, `absint()`, `wp_kses()` for all input -- **Prefixes**: `SRFM_` for constants, `srfm_` for function names and handles -- **Text domain**: `sureforms` for all translatable strings -- **PHP compatibility**: 7.4+ (enforced via `PHPCompatibilityWP`) - -### JavaScript Standards - -- Follow `@wordpress/scripts` ESLint configuration -- Use `@wordpress/data` store for state management -- Use `@wordpress/api-fetch` for REST API calls -- Import WordPress dependencies from `@wordpress/*` packages -- Use Force UI (`@bsf/force-ui`) components in admin dashboard pages - -### CSS Standards - -- **Admin dashboard**: Tailwind CSS 3.x utility classes -- **Block frontend**: SASS files in `sass/` directory -- **Class prefix**: `srfm-` for all frontend CSS classes -- Do not edit generated CSS in `assets/css/` -- edit SASS source files instead - -## Pull Request Process - -### PR Checklist - -Before submitting your PR, verify: - -- [ ] Code follows the coding standards (PHP, JS, CSS) -- [ ] All linters pass without errors -- [ ] Tests pass (PHPUnit, Jest) -- [ ] New features include tests -- [ ] Translatable strings use the `sureforms` text domain -- [ ] No debug code, `console.log`, or `var_dump` statements -- [ ] No secrets or credentials committed -- [ ] Build completes without errors (`npm run build`) - -### PR Title Format - -PR titles are validated by CI. Use a clear, descriptive title that summarizes the change. - -### CI Checks - -Pull requests automatically run: - -1. **PHPUnit** -- PHP unit tests -2. **PHPStan** -- Static analysis -3. **PHP Insights** -- Code quality -4. **PR Title Validation** -- Title format check -5. **E2E Tests** -- When the PR is labeled with `e2e` - -All checks must pass before merge. - -### Code Review - -- PRs require at least one approving review -- Address all review comments -- Keep PRs focused on a single feature or fix -- Large changes should be discussed in an issue first - -## Directory Guide for Contributors - -| Directory | Purpose | Edit? | -|-----------|---------|-------| -| `src/` | JS/React source code | Yes | -| `sass/` | SASS stylesheets | Yes | -| `inc/` | PHP classes and logic | Yes | -| `tests/` | Test files | Yes | -| `build/` | Compiled JS output | No (generated) | -| `assets/css/` | Compiled CSS output | No (generated) | -| `assets/js/minified/` | Minified JS | No (generated) | -| `inc/lib/` | Bundled third-party libraries | No | -| `node_modules/` | NPM packages | No | -| `vendor/` | Composer packages | No | - -## Adding a New Gutenberg Block - -See [Gutenberg Blocks](Gutenberg-Blocks) for the full guide on adding blocks, including: - -- Block registration (server-side and client-side) -- `block.json` schema -- Editor and frontend rendering - -## Adding a New REST Endpoint - -See [REST API Reference](REST-API-Reference) for endpoint patterns and the registration system. - -## Reporting Issues - -- Use [GitHub Issues](https://github.com/brainstormforce/sureforms/issues) for bug reports and feature requests -- Security vulnerabilities should be reported through the [Bug Bounty Program](https://brainstormforce.com/bug-bounty-program/) - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup details -- [Architecture Overview](Architecture-Overview) -- Codebase structure -- [Testing Guide](Testing-Guide) -- Test framework details -- [Deployment Guide](Deployment-Guide) -- Release process diff --git a/docs/wiki/Data-Export-Import.md b/docs/wiki/Data-Export-Import.md deleted file mode 100644 index fc07271ae..000000000 --- a/docs/wiki/Data-Export-Import.md +++ /dev/null @@ -1,120 +0,0 @@ -# Data Export & Import - -SureForms supports exporting and importing forms and entries for backup, migration, and data analysis. - -## Form Export - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/export` - -**Parameters:** -- `form_ids` (required) -- Array of form IDs to export - -**Response:** JSON file containing form data including: -- Form post data (title, content, status) -- All form post meta (`_srfm_*` keys) -- Block content (Gutenberg blocks) - -### Export Format - -Forms are exported as a JSON structure containing all data needed to recreate the form on another site. - -## Form Import - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/import` - -**Parameters:** -- File upload containing exported JSON data - -**Process:** -1. Validates the uploaded JSON structure -2. Creates new `sureforms_form` posts -3. Restores all post meta keys -4. Assigns new post IDs (no ID conflicts) -5. Returns count of imported forms - -## Form Duplication - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/duplicate` - -**Parameters:** -- `form_id` (required) -- ID of the form to duplicate - -**Process:** - -Handled by `Duplicate_Form` class (`inc/duplicate-form.php`): - -1. Reads the original form post and all meta -2. Creates a new post with "(Copy)" appended to the title -3. Copies all `_srfm_*` post meta to the new form -4. Returns the new form ID - -## Entry Export - -### REST Endpoint - -`POST /wp-json/sureforms/v1/entries/export` - -**Parameters:** -- `entry_ids` -- Specific entry IDs to export (optional) -- `form_id` -- Export all entries for a specific form -- `status` -- Filter by status (all, read, unread) -- `search` -- Search filter -- `date_from` / `date_to` -- Date range filter - -**Response:** CSV file (or ZIP for large exports) - -### Export Format - -The `Export` class (`inc/export.php`) generates CSV with: -- One row per entry -- Columns for each form field -- Submission metadata (date, status, IP) -- Field values normalized via `srfm_normalize_csv_field_value` filter - -### CSV Field Value Normalization - -The `srfm_normalize_csv_field_value` filter allows customizing how field values appear in CSV exports: - -```php -add_filter( 'srfm_normalize_csv_field_value', function( $value, $field ) { - // Custom formatting for specific field types - return $value; -}, 10, 2 ); -``` - -## Form Lifecycle Management - -### REST Endpoint - -`POST /wp-json/sureforms/v1/forms/manage` - -**Parameters:** -- `form_ids` (required) -- Array of form IDs -- `action` (required) -- `trash`, `restore`, or `delete` - -| Action | Behavior | -|--------|----------| -| `trash` | Move forms to trash (recoverable) | -| `restore` | Restore trashed forms | -| `delete` | Permanently delete forms | - -## Create New Form - -The `Create_New_Form` class (`inc/create-new-form.php`) handles new form creation with default meta values: - -- Sets default styling options -- Configures default submit button text -- Initializes email notification defaults -- Applies default form confirmation settings - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- All export/import endpoints -- [Database Schema](Database-Schema) -- Entry and form data structure -- [Admin Dashboard](Admin-Dashboard) -- UI for export/import operations diff --git a/docs/wiki/Database-Schema.md b/docs/wiki/Database-Schema.md deleted file mode 100644 index c0a47044b..000000000 --- a/docs/wiki/Database-Schema.md +++ /dev/null @@ -1,173 +0,0 @@ -# Database Schema - -SureForms uses two custom database tables alongside the WordPress `sureforms_form` custom post type. All custom tables use the `wp_srfm_` prefix. - -## Table Relationships - -``` -+-------------------+ +-------------------+ -| wp_posts | | wp_srfm_entries | -| (sureforms_form) |<---------| form_id | -| ID (PK) | | | ID (PK) | -| post_content | | | user_id | -| post_meta (_srfm)| | | form_data (JSON) | -+-------------------+ | | status | - | +--------+-----------+ - | | - | +--------v-----------+ - | | wp_srfm_payments | - +----| form_id | - | id (PK) | - | entry_id (FK) | - | transaction_id | - | total_amount | - +--------------------+ -``` - -## Custom Tables - -### wp_srfm_entries - -Stores all form submissions. Table version: 1. - -| Column | Type | Default | Description | -|--------|------|---------|-------------| -| `ID` | BIGINT(20) UNSIGNED | AUTO_INCREMENT | Primary key | -| `form_id` | BIGINT(20) UNSIGNED | -- | Reference to sureforms_form post ID | -| `user_id` | BIGINT(20) UNSIGNED | 0 | WordPress user ID (0 = anonymous) | -| `status` | VARCHAR(10) | `unread` | Entry status: `unread`, `read`, `trash` | -| `type` | VARCHAR(20) | -- | Form type: `quiz`, `standard`, etc. | -| `form_data` | LONGTEXT | `[]` | JSON-encoded submitted field values | -| `submission_info` | LONGTEXT | `[]` | JSON-encoded browser/device/IP metadata | -| `notes` | LONGTEXT | `[]` | JSON-encoded admin notes | -| `logs` | LONGTEXT | `[]` | JSON-encoded activity log entries | -| `extras` | LONGTEXT | `[]` | JSON-encoded misc additional data | -| `created_at` | TIMESTAMP | CURRENT_TIMESTAMP | Submission timestamp | -| `updated_at` | TIMESTAMP | CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | - -**Indexes:** -- `PRIMARY KEY (ID)` -- `INDEX idx_form_id (form_id)` -- `INDEX idx_user_id (user_id)` -- `INDEX idx_form_id_created_at_status (form_id, created_at, status)` -- composite index for list queries - -**Log Entry Structure:** -```json -{ - "title": "Form Submitted", - "messages": ["Email notification sent", "Entry stored"], - "timestamp": "2024-01-15 10:30:00" -} -``` - -### wp_srfm_payments - -Stores payment transaction records linked to form entries. Table version: 1. Added in v2.0.0. - -| Column | Type | Default | Description | -|--------|------|---------|-------------| -| `id` | BIGINT(20) UNSIGNED | AUTO_INCREMENT | Primary key | -| `form_id` | BIGINT(20) UNSIGNED | -- | Reference to sureforms_form post | -| `block_id` | VARCHAR(255) | `''` | Payment block identifier | -| `status` | VARCHAR(50) | `pending` | Payment status (see below) | -| `total_amount` | DECIMAL(26,8) | `0.00000000` | Total amount after discount | -| `refunded_amount` | DECIMAL(26,8) | `0.00000000` | Total refunded amount | -| `currency` | VARCHAR(10) | `''` | ISO 4217 currency code | -| `entry_id` | BIGINT(20) UNSIGNED | 0 | Reference to entries table | -| `gateway` | VARCHAR(20) | `''` | Payment gateway: `stripe` | -| `type` | VARCHAR(30) | `''` | Payment type: `payment`, `subscription`, `renewal` | -| `mode` | VARCHAR(20) | `''` | Mode: `test` or `live` | -| `transaction_id` | VARCHAR(50) | `''` | Gateway transaction ID | -| `customer_id` | VARCHAR(50) | `''` | Gateway customer ID | -| `subscription_id` | VARCHAR(50) | `''` | Subscription ID (if recurring) | -| `subscription_status` | VARCHAR(20) | `''` | Subscription status | -| `parent_subscription_id` | BIGINT(20) UNSIGNED | 0 | Parent subscription payment ID | -| `payment_data` | LONGTEXT | `[]` | JSON-encoded payment details (includes refunds) | -| `extra` | LONGTEXT | `[]` | JSON-encoded additional data | -| `log` | LONGTEXT | `[]` | JSON-encoded payment log | -| `created_at` | TIMESTAMP | CURRENT_TIMESTAMP | Creation timestamp | -| `updated_at` | TIMESTAMP | CURRENT_TIMESTAMP ON UPDATE | Last update timestamp | -| `srfm_txn_id` | VARCHAR(100) | `''` | Custom transaction ID format | -| `customer_email` | VARCHAR(255) | `''` | Customer email | -| `customer_name` | VARCHAR(255) | `''` | Customer name | - -**Valid Payment Statuses:** -`pending`, `succeeded`, `failed`, `canceled`, `requires_action`, `requires_payment_method`, `processing`, `refunded`, `partially_refunded` - -**Valid Subscription Statuses:** -`active`, `canceled`, `past_due`, `unpaid`, `trialing`, `incomplete`, `incomplete_expired`, `paused` - -**Supported Currencies:** -USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, SEK, NZD, MXN, SGD, HKD, NOK, TRY, RUB, INR, BRL, ZAR, KRW - -## WordPress Post Type: sureforms_form - -Forms are stored as a custom post type (`sureforms_form`) in the standard `wp_posts` table. Block content is in `post_content`, and form configuration is stored as post meta with `_srfm_` prefixed keys. - -### Key Post Meta Keys - -| Meta Key | Type | Description | -|----------|------|-------------| -| `_srfm_forms_styling` | string | Form styling mode | -| `_srfm_color` | string | Primary form color | -| `_srfm_bg_color` | string | Background color | -| `_srfm_fontsize` | string | Font size | -| `_srfm_label_color` | string | Label text color | -| `_srfm_input_color` | string | Input text color | -| `_srfm_submit_button_text` | string | Submit button text | -| `_srfm_submit_alignment` | string | Button alignment | -| `_srfm_submit_width` | string | Button width | -| `_srfm_submit_width_backend` | string | Backend button width | -| `_srfm_is_inline_button` | boolean | Inline button mode | -| `_srfm_email_notification` | array | Email notification settings | -| `_srfm_form_confirmation` | array | Confirmation settings (message/redirect) | -| `_srfm_captcha` | string | CAPTCHA type (recaptcha_v2, v3, hcaptcha, turnstile) | -| `_srfm_honeypot` | boolean | Honeypot spam protection | -| `_srfm_is_page_break` | boolean | Multi-step form enabled | -| `_srfm_single_page_form_title` | boolean | Show title on instant form | -| `_srfm_instant_form_settings` | array | Instant form configuration | -| `_srfm_page_form_logo` | string | Form logo URL | -| `_srfm_submit_type` | string | Submission type | - -Post meta is extensible via the `srfm_register_post_meta` filter. - -## Database Base Class - -All custom tables extend `SRFM\Inc\Database\Base`, which provides: - -### Schema Versioning -- Each table has a `$table_version` integer -- Versions stored in `srfm_database_table_versions` option -- On version mismatch, `start_db_upgrade()` runs migrations (new columns, renames) - -### Query Builder -```php -// Simple equality -$results = $instance->get_results(['form_id' => 123]); - -// Complex conditions with operators -$results = $instance->get_results([ - [ - ['key' => 'status', 'compare' => '!=', 'value' => 'trash'], - ['key' => 'created_at', 'compare' => '>', 'value' => '2024-01-01'], - ] -]); - -// IN operator -$results = $instance->get_results([ - [['key' => 'ID', 'compare' => 'IN', 'value' => [1, 2, 3]]] -]); -``` - -### Caching -Results are cached in-memory using MD5 of the SQL query as the cache key. Cache is reset on insert, update, or delete operations. - -### Data Encoding -Array-type columns (defined in `get_schema()`) are automatically JSON-encoded on write and decoded on read. Number types are cast to integers. - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin bootstrap and class initialization -- [Payment Integration](Payment-Integration) -- Stripe payment flow -- [Form Submission Flow](Form-Submission-Flow) -- Entry creation pipeline -- [REST API Reference](REST-API-Reference) -- API endpoints for entries and payments diff --git a/docs/wiki/Deployment-Guide.md b/docs/wiki/Deployment-Guide.md deleted file mode 100644 index 1511856ec..000000000 --- a/docs/wiki/Deployment-Guide.md +++ /dev/null @@ -1,109 +0,0 @@ -# Deployment Guide - -## Release Process Overview - -SureForms follows a branch-based release workflow with automated CI/CD via GitHub Actions. - -## Git Workflow - -``` -feature/branch --> next-release --> master --> tag/release - (development) (production) -``` - -1. Feature branches are created from `next-release` -2. PRs target `next-release` for review and merge -3. `next-release` is merged to `master` for releases -4. Tags trigger release artifacts - -## Build Steps - -### Production Build - -```bash -# Full production build (JS + SASS + minify) -npm run build - -# Individual steps: -npm run build:script # Build JS via wp-scripts -npm run build:sass # Compile SASS via Grunt -``` - -The build process: -1. Webpack bundles JS/React source from `src/` to `assets/build/` -2. Grunt compiles SASS from `sass/` to `assets/css/` -3. Grunt minifies CSS to `assets/css/minified/` -4. Grunt minifies JS to `assets/js/minified/` -5. PostCSS applies Autoprefixer - -### i18n - -```bash -npm run makepot # Generate .pot translation file -``` - -## CI/CD Pipelines - -### GitHub Actions Workflows - -| Workflow | Trigger | Purpose | -|----------|---------|---------| -| `phpunit.yml` | Pull requests | Run PHP unit tests | -| `playwright.yml` | PR labeled `e2e` | Run E2E browser tests | -| `code-analysis.yml` | Pull requests | PHPStan static analysis | -| `insights.yml` | Pull requests | PHP Insights code quality | -| `pr-title.yml` | Pull requests | Validate PR title format | -| `push-to-deploy.yml` | Push to master | Deploy to production | -| `push-asset-readme-update.yml` | Push to master | Update readme/assets | -| `release-tag-draft.yml` | Tag push | Create draft GitHub release | -| `release-pr-template.yml` | Release PRs | Apply release PR template | -| `claude-code-review.yml` | Pull requests | AI-powered code review | - -### Test Pipeline - -Pull requests automatically run: -1. PHPUnit tests (PHP 8.2, WordPress trunk) -2. PHPStan static analysis -3. PHP Insights code quality -4. PR title validation -5. E2E tests (when labeled `e2e`) - -### Deployment Pipeline - -On push to `master`: -1. `push-to-deploy.yml` triggers deployment -2. `push-asset-readme-update.yml` updates WordPress.org assets - -### Release Pipeline - -1. Merge `next-release` into `master` -2. Create a version tag (e.g., `v2.5.1`) -3. `release-tag-draft.yml` creates a draft GitHub release -4. Review and publish the release - -## Pre-Deployment Checklist - -- [ ] All tests pass (PHPUnit, Playwright, Jest) -- [ ] PHPStan analysis clean -- [ ] PHPCS standards pass -- [ ] Production build completes without errors -- [ ] Version bumped in `sureforms.php` and `readme.txt` -- [ ] `SRFM_VER` constant updated -- [ ] `SRFM_PRO_RECOMMENDED_VER` checked -- [ ] Changelog updated in `readme.txt` -- [ ] Translation .pot file regenerated -- [ ] No debug code or console.log statements -- [ ] Database migrations tested (if any) - -## Version Bumping - -Update version in these files: -1. `sureforms.php` -- Plugin header `Version:` and `SRFM_VER` constant -2. `readme.txt` -- `Stable tag:` and changelog section -3. `package.json` -- `version` field - -## Related Pages - -- [Testing Guide](Testing-Guide) -- Test setup and execution -- [Contributing Guide](Contributing-Guide) -- Development workflow -- [Environment Configuration](Environment-Configuration) -- Build tools diff --git a/docs/wiki/Email-Notifications.md b/docs/wiki/Email-Notifications.md deleted file mode 100644 index 642751bcb..000000000 --- a/docs/wiki/Email-Notifications.md +++ /dev/null @@ -1,86 +0,0 @@ -# Email Notifications - -SureForms sends email notifications on form submission and supports scheduled email summary digests. - -## Email System Overview - -``` -Form Submission - -> Form_Submit::handle_form_submission() - -> Email_Template (inc/email/email-template.php) - -> Smart_Tags resolution - -> wp_mail() dispatch -``` - -## Email Template - -The `Email_Template` class (`inc/email/email-template.php`) handles: - -- HTML email template rendering -- Dynamic content insertion via smart tags -- Multiple notification recipients -- Customizable subject lines and body content -- Reply-to address configuration - -### Per-Form Email Settings - -Email notifications are configured per-form via the `_srfm_email_notification` post meta key. Each form can have: - -- **To** -- Recipient email addresses (supports multiple, comma-separated) -- **Subject** -- Email subject line (supports smart tags) -- **Body** -- Email content (supports smart tags and HTML) -- **Reply-To** -- Reply-to email address -- **CC/BCC** -- Additional recipients - -## Smart Tags - -The `Smart_Tags` class (`inc/smart-tags.php`) provides template variables that are resolved at email send time: - -| Smart Tag | Description | -|-----------|-------------| -| `{admin_email}` | WordPress admin email | -| `{site_title}` | WordPress site title | -| `{site_url}` | WordPress site URL | -| `{form_title}` | Name of the submitted form | -| `{form_id}` | ID of the submitted form | -| `{entry_id}` | ID of the created entry | -| `{all_fields}` | All submitted field values formatted | -| `{field:field_name}` | Specific field value by field name | -| `{user_ip}` | Submitter's IP address | -| `{user_agent}` | Submitter's browser user agent | -| `{submission_date}` | Date of submission | - -Smart tags are processed by replacing the tag placeholders with actual values from the form submission data. - -## Email Summary - -The `Email_Summary` class (`inc/global-settings/email-summary.php`) provides scheduled digest reports: - -- Configurable frequency (daily, weekly, monthly) -- Summary of form submissions over the period -- Entry counts per form -- Sent to configured admin email addresses -- Uses WordPress cron (`Events_Scheduler`) for scheduling - -## Customization - -### Modifying Email Content - -Email content is filtered through smart tag processing, allowing dynamic content: - -```php -// In per-form settings, the email body supports: -// - Smart tags: {form_title}, {all_fields}, etc. -// - HTML formatting -// - Custom text -``` - -### Extending Smart Tags - -Smart tags can be extended by hooking into the tag resolution process to add custom replacements. - -## Related Pages - -- [Form Submission Flow](Form-Submission-Flow) -- When emails are triggered -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Email-related hooks -- [Architecture Overview](Architecture-Overview) -- Email_Template initialization diff --git a/docs/wiki/Environment-Configuration.md b/docs/wiki/Environment-Configuration.md deleted file mode 100644 index b4a89da2f..000000000 --- a/docs/wiki/Environment-Configuration.md +++ /dev/null @@ -1,197 +0,0 @@ -# Environment Configuration - -## Prerequisites - -| Requirement | Version | Notes | -|-------------|---------|-------| -| PHP | 7.4+ | WordPress minimum requirement | -| WordPress | 6.4+ | Gutenberg block editor required | -| Node.js | 24.16.0 | Managed via Volta (see `package.json`) | -| Composer | 2.x | PHP dependency management | -| npm | 11.x+ | Comes with Node 24 | - -## Plugin Constants - -Defined in `sureforms.php`: - -| Constant | Value | Purpose | -|----------|-------|---------| -| `SRFM_FILE` | `__FILE__` | Absolute path to main plugin file | -| `SRFM_BASENAME` | `sureforms/sureforms.php` | Plugin basename for hooks | -| `SRFM_DIR` | Plugin directory path | File system path operations | -| `SRFM_URL` | Plugin URL | Asset URL generation | -| `SRFM_VER` | `2.5.1` | Version for cache busting | -| `SRFM_SLUG` | `srfm` | Short identifier slug | -| `SRFM_FORMS_POST_TYPE` | `sureforms_form` | Custom post type slug | -| `SRFM_ENTRIES` | `sureforms_entries` | Entries identifier | -| `SRFM_PAYMENTS` | `sureforms_payments` | Payments identifier | -| `SRFM_WEBSITE` | `https://sureforms.com/` | Official website | -| `SRFM_AI_MIDDLEWARE` | `https://credits.startertemplates.com/sureforms/` | AI credits API | -| `SRFM_MIDDLEWARE_BASE_URL` | `https://api.sureforms.com/` | API middleware | -| `SRFM_BILLING_PORTAL` | `https://billing.sureforms.com/` | Billing portal | -| `SRFM_PRO_RECOMMENDED_VER` | `2.5.1` | Minimum recommended Pro version | -| `SRFM_SURETRIGGERS_INTEGRATION_BASE_URL` | `https://app.ottokit.com/` | OttoKit integration | - -## Development Environment Setup - -### Option 1: wp-env (Docker) - -```bash -# Start the WordPress environment -npm run play:up - -# Stop the environment -npm run play:stop - -# Clean environment data -npm run env:clean -``` - -Configuration is in `.wp-env.json`. Requires Docker to be installed. - -### Option 2: Local by Flywheel / MAMP / XAMPP - -1. Set up a local WordPress installation -2. Clone the repo into `wp-content/plugins/sureforms` -3. Run `composer install` for PHP dependencies -4. Run `npm install` for JS dependencies -5. Run `npm start` for development with hot reload - -## Build Tools - -### wp-scripts (JavaScript) - -Primary build tool for JS/React compilation via Webpack: - -```bash -npm start # Dev server with hot reload -npm run build:script # Production JS build -``` - -Entry points are defined in `webpack.config.js` and output to `assets/build/`. - -### Grunt (SASS / Minification) - -```bash -npm run build:sass # Compile SASS to CSS -npm run build # Full build (JS + SASS + minify) -``` - -The `Gruntfile.js` handles: -- SASS compilation (`sass/` -> `assets/css/`) -- CSS minification -- JS minification -- PostCSS with Autoprefixer - -### Tailwind CSS - -Used for admin dashboard styles. Configuration in `tailwind.config.js`: -- Content scanning paths: `src/admin/**/*.{js,jsx}` -- Plugin: `@tailwindcss/forms` -- Custom theme extensions for Force UI integration - -### PostCSS - -Configuration in `postcss.config.js`: -- Autoprefixer for browser compatibility -- cssnano for production minification - -## NPM Scripts Reference - -| Script | Command | Description | -|--------|---------|-------------| -| `start` | `wp-scripts start` | Dev server with hot reload | -| `build` | Full pipeline | JS + SASS + minify | -| `build:script` | `wp-scripts build` | Build JS only | -| `build:sass` | Grunt sass tasks | Build SASS only | -| `lint-js` | `wp-scripts lint-js` | ESLint check | -| `lint-js:fix` | `wp-scripts lint-js --fix` | Auto-fix JS lint | -| `lint-css` | `wp-scripts lint-style` | Stylelint check | -| `lint-css:fix` | `wp-scripts lint-style --fix` | Auto-fix CSS lint | -| `pretty` | `prettier --check` | Prettier check | -| `pretty:fix` | `prettier --write` | Prettier format | -| `test:unit` | `wp-scripts test-unit-js` | Jest unit tests | -| `play:up` | `wp-env start` | Start Docker env | -| `play:stop` | `wp-env stop` | Stop Docker env | -| `play:run` | `playwright test` | Run E2E tests | -| `play:run:interactive` | `playwright test --headed` | E2E tests (headed) | -| `env:clean` | `wp-env clean` | Clean Docker data | -| `makepot` | `wp i18n make-pot` | Generate .pot file | - -## Composer Scripts - -| Script | Description | -|--------|-------------| -| `vendor/bin/phpunit` | Run PHPUnit tests | -| `vendor/bin/phpcs` | PHP CodeSniffer (WPCS) | -| `vendor/bin/phpcbf` | Auto-fix PHPCS issues | -| `vendor/bin/phpstan` | PHPStan static analysis | - -## Linting Configuration - -### PHP (PHPCS) - -Configured via `phpcs.xml`: -- Ruleset: WordPress Coding Standards (WPCS) -- PHPCompatibility for PHP 7.4+ -- Text domain: `sureforms` -- Prefixes: `srfm`, `SRFM_` - -### JavaScript (ESLint) - -Uses `@wordpress/scripts` ESLint configuration which extends WordPress defaults. - -### CSS (Stylelint) - -WordPress Stylelint configuration via `@wordpress/scripts`. - -### Prettier - -Code formatting for JS/CSS files. - -## Volta Configuration - -Node version is pinned in `package.json`: - -```json -{ - "volta": { - "node": "24.16.0" - } -} -``` - -Install Volta to automatically use the correct Node version when entering the project directory. - -## Key Dependencies - -### PHP (Composer) - -| Package | Purpose | -|---------|---------| -| `yoast/phpunit-polyfills` | PHPUnit compatibility | -| `squizlabs/php_codesniffer` | Code style checking | -| `wp-coding-standards/wpcs` | WordPress standards | -| `phpcompatibility/php-compatibility` | PHP version checks | -| `phpstan/phpstan` | Static type analysis | -| `nunomaduro/phpinsights` | Code quality insights | - -### JavaScript (npm) - -| Package | Purpose | -|---------|---------| -| `@bsf/force-ui` | BSF internal UI component library | -| `@wordpress/scripts` | Build tooling | -| `@wordpress/api-fetch` | REST API client | -| `@tanstack/react-query` | Data fetching/caching | -| `react-router-dom` | Client-side routing | -| `tailwindcss` | Utility-first CSS | -| `@playwright/test` | E2E testing | -| `@wordpress/env` | Docker test environment | - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin structure and bootstrap -- [Testing Guide](Testing-Guide) -- Test setup and execution -- [Contributing Guide](Contributing-Guide) -- Coding standards and workflow -- [Frontend Assets](Frontend-Assets) -- Asset pipeline details diff --git a/docs/wiki/Form-Fields-Architecture.md b/docs/wiki/Form-Fields-Architecture.md deleted file mode 100644 index 285f4793c..000000000 --- a/docs/wiki/Form-Fields-Architecture.md +++ /dev/null @@ -1,137 +0,0 @@ -# Form Fields Architecture - -SureForms uses a layered architecture for form fields: Gutenberg blocks define the editor experience, field markup classes generate the frontend HTML, and the validation system ensures data integrity on submission. - -## Field Type Hierarchy - -``` -Fields\Base (inc/fields/base.php) - |-- Input_Markup (text, hidden) - |-- Email_Markup (email with validation) - |-- Textarea_Markup (multi-line text) - |-- Number_Markup (numeric with min/max) - |-- Phone_Markup (with country selector) - |-- URL_Markup (URL with validation) - |-- Dropdown_Markup (select options) - |-- Checkbox_Markup (single/multiple) - |-- Multichoice_Markup (radio/checkbox groups) - |-- Address_Markup (composite address field) - |-- GDPR_Markup (consent checkbox) - |-- Payment_Markup (Stripe payment field) - |-- Inlinebutton_Markup (inline buttons) -``` - -## Base Field Class - -`inc/fields/base.php` provides the foundation: - -- Common HTML wrapper generation -- Label rendering with required indicator -- Help text / description output -- Error message container -- CSS class assembly with `srfm-` prefix -- Conditional logic attribute output -- Accessibility attributes (aria-label, aria-required) - -## Markup Generation Pattern - -Each field markup class follows a consistent pattern: - -1. Receive block attributes from the Gutenberg block -2. Process and sanitize attribute values -3. Generate field-specific HTML using WordPress escaping functions -4. Wrap in the standard field container with label and error placeholder -5. Return the complete HTML string - -## Available Field Types - -### Input (input-markup.php) -Standard text input supporting `text`, `hidden`, and other HTML5 input types. Supports placeholder, default value, min/max length. - -### Email (email-markup.php) -Email input with built-in email format validation. Supports confirmation email field option. - -### Textarea (textarea-markup.php) -Multi-line text area with configurable rows and max length. - -### Number (number-markup.php) -Numeric input with min/max value constraints, step size, and format validation. - -### Phone (phone-markup.php) -Phone number input integrated with `intl-tel-input` library for country code selection. Uses `countries.json` for country data. - -### URL (url-markup.php) -URL input with format validation. - -### Dropdown (dropdown-markup.php) -Select dropdown with configurable options. Supports default selection and placeholder text. - -### Checkbox (checkbox-markup.php) -Single or multiple checkbox inputs with label positioning options. - -### Multi Choice (multichoice-markup.php) -Radio button or checkbox groups for multiple choice questions. Supports image choices and custom layouts. - -### Address (address-markup.php) -Composite field with subfields: street address, city, state/province, zip/postal code, and country. Each subfield is independently configurable. - -### GDPR (gdpr-markup.php) -GDPR consent checkbox with customizable compliance text. Integrates with form-level compliance settings. - -### Payment (payment-markup.php) -Payment collection field for Stripe integration. Renders the payment form elements and connects to the payment processing flow. - -## Validation System - -### Server-Side Validation (field-validation.php) - -`inc/field-validation.php` handles validation during form submission: - -- **Required field check** -- Ensures required fields have values -- **Type validation** -- Validates data types (email format, URL format, number ranges) -- **Unique value check** -- Optional unique validation against existing entries -- **Custom validation** -- Extensible via filters - -Common error messages are defined in `Helper::get_common_err_msg()`: -- `required`: "This field is required." -- `unique`: "Value needs to be unique." - -### Client-Side Validation - -Frontend JavaScript provides instant validation feedback before submission, matching server-side rules. - -## Field Rendering Pipeline - -``` -1. Block Editor (Gutenberg) - -> Block attributes saved in post_content - -2. Frontend Request - -> Generate_Form_Markup::get_form_markup() - -> Parses post_content blocks - -> For each block, calls its render callback - -3. Block Render Callback - -> Block class (inc/blocks/{type}/block.php) - -> Instantiates field markup class - -> Passes block attributes - -4. Field Markup Generation - -> Field markup class (inc/fields/{type}-markup.php) - -> Base class wraps with container, label, error placeholder - -> Field-specific HTML generated - -> Returns escaped HTML string - -5. Form Assembly - -> All field HTML concatenated inside
wrapper - -> Submit button appended - -> Nonce field added - -> Final form HTML returned -``` - -## Related Pages - -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block registration and editor components -- [Form Submission Flow](Form-Submission-Flow) -- How field data is validated and processed -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Field-related filters -- [Payment Integration](Payment-Integration) -- Payment field details diff --git a/docs/wiki/Form-Submission-Flow.md b/docs/wiki/Form-Submission-Flow.md deleted file mode 100644 index 81a9e3806..000000000 --- a/docs/wiki/Form-Submission-Flow.md +++ /dev/null @@ -1,191 +0,0 @@ -# Form Submission Flow - -This page documents the end-to-end form submission process from frontend to entry storage. - -## Flow Diagram - -``` -+------------------+ +-------------------+ +------------------+ -| User fills form |---->| JavaScript fetch() |---->| REST API | -| in browser | | POST /submit-form | | Form_Submit | -+------------------+ +-------------------+ +--------+---------+ - | - +--------------------------------+ - | - +----------v-----------+ - | Permission Check | - | - X-WP-Submit-Nonce | - | - Form data exists | - +----------+-----------+ - | - +----------v-----------+ - | Form Restrictions | - | - Max entries check | - | - Date scheduling | - | - Login required | - +----------+-----------+ - | - +----------v-----------+ - | Field Validation | - | - Required fields | - | - Type validation | - | - Unique values | - +----------+-----------+ - | - +----------v-----------+ - | Security Checks | - | - CAPTCHA verify | - | - Honeypot check | - +----------+-----------+ - | - +----------v-----------+ - | Data Processing | - | - Sanitize fields | - | - Smart tag resolve | - | - Collect metadata | - +----------+-----------+ - | - +----------------+----------------+ - | | -+--------v--------+ +--------v--------+ -| Email Notify | | Entry Storage | -| Email_Template | | wp_srfm_entries | -+-----------------+ +--------+--------+ - | - +--------v--------+ - | Payment Process | - | (if applicable) | - | wp_srfm_payments| - +--------+--------+ - | - +--------v--------+ - | Confirmation | - | Response | - +-----------------+ -``` - -## Step-by-Step Process - -### 1. Frontend Submission - -The form is submitted via JavaScript `fetch()` to `POST /wp-json/sureforms/v1/submit-form` with: - -- Form field data as POST body -- `X-WP-Submit-Nonce` header for security (does not require logged-in user) -- Form ID to identify which form was submitted - -Nonces can be refreshed via `GET /refresh-nonces` (public, no auth required). - -### 2. Permission Check - -`Form_Submit::submit_form_permissions_check()`: - -- Verifies the `X-WP-Submit-Nonce` header -- Validates that form data and form ID are present -- Returns `WP_Error` on failure - -### 3. Form Restrictions - -`Form_Restriction` class checks: - -- **Maximum entries** -- Has the form reached its submission limit? -- **Date scheduling** -- Is the form within its active date range? -- **Login required** -- Does the form require a logged-in user? -- **Entry caps** -- Per-user submission limits - -### 4. Field Validation - -`Field_Validation` (`inc/field-validation.php`): - -- **Required fields** -- Ensures all required fields have non-empty values -- **Type validation** -- Email format, URL format, number ranges, phone format -- **Unique values** -- Checks existing entries for uniqueness if configured -- Returns validation errors that prevent submission - -### 5. Security Verification - -- **CAPTCHA** -- Validates reCAPTCHA v2/v3, hCaptcha, or Cloudflare Turnstile token -- **Honeypot** -- Checks the hidden honeypot field is empty (bots fill it) - -### 6. Data Processing - -- `apply_filters('srfm_form_submit_data', $data, $form_id)` -- allows modification -- Sanitizes each field value -- Resolves smart tags for email templates -- Collects submission metadata (IP, browser via Browser class, device info) -- `do_action('srfm_before_submission', $data, $form_id)` - -### 7. Email Notifications - -`Email_Template` sends configured email notifications: - -- Processes smart tags in subject and body -- Sends to configured recipients -- Handles CC/BCC -- See [Email Notifications](Email-Notifications) for details - -### 8. Entry Storage - -Checks GDPR compliance (`do_not_store_entries` setting). If storage is allowed: - -```php -Entries::add([ - 'form_id' => $form_id, - 'user_id' => get_current_user_id(), - 'form_data' => $sanitized_data, // JSON-encoded - 'submission_info' => $metadata, // JSON-encoded - 'logs' => $activity_logs, // JSON-encoded - 'status' => 'unread', - 'type' => $form_type, // 'standard', 'quiz', etc. -]); -``` - -### 9. Payment Processing - -If the form includes a payment block: - -- Stripe payment is processed -- Payment record created in `wp_srfm_payments` -- Payment ID linked to the entry -- See [Payment Integration](Payment-Integration) for details - -### 10. Post-Submission - -- `do_action('srfm_form_submit', $entry_id, $form_id, $form_data)` -- `do_action('srfm_after_submission_process', $entry_id, $form_id)` -- Returns confirmation response: - - **Message** -- Display a success message - - **Redirect** -- Redirect to a specified URL - -## Form Confirmation Types - -Configured via `_srfm_form_confirmation` post meta: - -| Type | Behavior | -|------|----------| -| Message | Display a success message on the same page | -| Redirect | Redirect to a specified URL after submission | - -Confirmation parameters are filterable via `srfm_form_confirmation_params`. - -## Error Handling - -Errors at any stage return a JSON error response to the frontend: - -```json -{ - "success": false, - "data": "Validation error message" -} -``` - -The frontend JavaScript displays errors inline next to the relevant fields. - -## Related Pages - -- [REST API Reference](REST-API-Reference) -- Submit endpoint details -- [Database Schema](Database-Schema) -- Entry and payment table structure -- [Email Notifications](Email-Notifications) -- Email dispatch details -- [Payment Integration](Payment-Integration) -- Payment processing -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- Submission hooks -- [Form Fields Architecture](Form-Fields-Architecture) -- Field validation diff --git a/docs/wiki/Frontend-Assets.md b/docs/wiki/Frontend-Assets.md deleted file mode 100644 index 99b4cca91..000000000 --- a/docs/wiki/Frontend-Assets.md +++ /dev/null @@ -1,122 +0,0 @@ -# Frontend Assets - -SureForms manages assets across three contexts: frontend (form display), admin (dashboard), and block editor. - -## Asset Loading Strategy - -### Frontend (Form Display) - -`inc/frontend-assets.php` (`Frontend_Assets` class) handles loading CSS/JS on pages containing SureForms forms: - -- Detects form blocks in page content -- Enqueues only necessary field-specific CSS/JS -- Loads Stripe JS SDK only when payment fields are present -- Enqueues `intl-tel-input` only when phone fields exist - -### Admin Dashboard - -Admin assets are enqueued only on SureForms admin pages: - -- React app bundle from `assets/build/` -- Tailwind CSS base styles from `src/admin/tw-base.scss` -- Localized data via `wp_localize_script()` - -### Block Editor - -`inc/gutenberg-hooks.php` (`Gutenberg_Hooks` class) loads editor assets: - -- Block editor JS/CSS from `assets/build/` -- Editor-specific stylesheets -- Block preview styles - -## Build Pipeline - -### wp-scripts (Webpack) - -Primary JS build tool. Configuration extends `@wordpress/scripts` defaults: - -```bash -npm start # Development with hot reload -npm run build:script # Production build -``` - -**Entry Points:** -- `src/blocks/blocks.js` -- Gutenberg block registration -- `src/admin/editor-scripts.js` -- Admin dashboard React app -- Additional entries per block type - -**Output:** `assets/build/` directory - -**Webpack Features:** -- Code splitting per entry point -- WordPress dependency extraction (`@wordpress/*` packages) -- Source maps in development -- Asset hashing for cache busting - -### Grunt (SASS / Minification) - -```bash -npm run build:sass # Compile SASS only -npm run build # Full build (JS + SASS + minify) -``` - -The `Gruntfile.js` tasks include: - -| Task | Description | -|------|-------------| -| `sass` | Compile SASS files from `sass/` to `assets/css/` | -| `postcss` | Run PostCSS with Autoprefixer | -| `cssmin` | Minify CSS to `assets/css/minified/` | -| `uglify` | Minify JS to `assets/js/minified/` | -| `copy` | Copy assets to distribution directories | - -### SASS Directory Structure - -``` -sass/ -|-- frontend block styles -|-- form field styles -|-- layout and grid styles -``` - -Compiled output: `assets/css/` (unminified) and `assets/css/minified/` - -### Tailwind CSS - -Used exclusively for admin dashboard pages: - -**Configuration (`tailwind.config.js`):** -- Content paths: `src/admin/**/*.{js,jsx}` -- Plugins: `@tailwindcss/forms` -- Custom theme extensions for Force UI compatibility -- `tailwind-merge` used for dynamic class composition - -**Base styles:** `src/admin/tw-base.scss` - -## CSS Naming Conventions - -| Context | Prefix | Example | -|---------|--------|---------| -| Frontend form styles | `srfm-` | `srfm-input-field`, `srfm-form-container` | -| Admin dashboard | Tailwind utilities | `flex items-center gap-2` | -| Block editor | `srfm-editor-` | `srfm-editor-preview` | - -## Asset Versioning - -All enqueued assets use `SRFM_VER` (currently `2.5.1`) for cache busting: - -```php -wp_enqueue_style( - 'srfm-frontend', - SRFM_URL . 'assets/css/minified/frontend.min.css', - [], - SRFM_VER -); -``` - -## Related Pages - -- [Admin Dashboard](Admin-Dashboard) -- React admin asset details -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block editor assets -- [Environment Configuration](Environment-Configuration) -- Build tool setup -- [Contributing Guide](Contributing-Guide) -- CSS/JS conventions diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md deleted file mode 100644 index a0f2ea908..000000000 --- a/docs/wiki/Getting-Started.md +++ /dev/null @@ -1,149 +0,0 @@ -# Getting Started - -This guide walks you through setting up a SureForms development environment from scratch. - -## Prerequisites - -| Tool | Version | Notes | -|------|---------|-------| -| PHP | 7.4+ | Required for plugin runtime | -| WordPress | 6.4+ | Required for block editor features | -| Node.js | 24.16.0 | Managed via Volta (auto-switches) | -| Composer | 2.x | PHP dependency manager | -| Git | 2.x | Version control | -| Docker | Latest | Optional, for wp-env test environment | - -### Installing Volta (Recommended) - -[Volta](https://volta.sh/) ensures you use the correct Node.js version automatically: - -```bash -curl https://get.volta.sh | bash -``` - -The `package.json` pins Node 24.16.0 via Volta config -- it activates automatically when you enter the project directory. - -## Initial Setup - -### 1. Clone the Repository - -```bash -git clone https://github.com/brainstormforce/sureforms.git -cd sureforms -``` - -### 2. Install Dependencies - -```bash -composer install # PHP dependencies -npm install # Node dependencies -``` - -### 3. Build the Plugin - -```bash -npm run build # Full production build (JS + SASS + minify) -``` - -This runs three steps: -1. `wp-scripts build` -- Bundles JS/React from `src/` to `build/` -2. SASS compilation -- Compiles `sass/` to `assets/css/` -3. `grunt minify` -- Minifies CSS and JS to `assets/css/minified/` and `assets/js/minified/` - -### 4. Start Development Server - -```bash -npm start # wp-scripts dev server with hot reload -``` - -This watches `src/` for changes and rebuilds JS automatically. For SASS changes, run `npm run build:sass` separately. - -## WordPress Environment - -### Option A: Local WordPress Installation - -1. Set up a local WordPress site (e.g., using Local, MAMP, or Docker) -2. Symlink or copy the plugin into `wp-content/plugins/sureforms/` -3. Activate the plugin from the WordPress admin - -### Option B: wp-env (Docker-Based) - -The project includes a `.wp-env.json` configuration for a Docker-based WordPress environment: - -```bash -npm run play:up # Start WordPress + MySQL containers -``` - -This provides: -- WordPress at `http://localhost:8888` (admin: `admin` / `password`) -- Test WordPress at `http://localhost:8889` -- Plugin auto-mounted and activated -- Consistent environment across developers - -Additional commands: -```bash -npm run play:stop # Stop containers -npm run env:clean # Reset all data -``` - -## Verifying Your Setup - -### Run Linters - -```bash -vendor/bin/phpcs # PHP CodeSniffer -npm run lint-js # ESLint -npm run lint-css # Stylelint -npm run pretty # Prettier -``` - -### Run Tests - -```bash -vendor/bin/phpunit # PHP unit tests -npm run test:unit # JavaScript unit tests -``` - -### Build Check - -```bash -npm run build # Should complete without errors -``` - -## Project Structure Overview - -``` -sureforms/ -├── sureforms.php # Plugin entry point -├── plugin-loader.php # Autoloader and bootstrap -├── inc/ # PHP classes (core logic) -├── src/ # JS/React source (compiled by wp-scripts) -│ ├── admin/ # Admin dashboard React app -│ ├── blocks/ # Gutenberg block components -│ ├── components/ # Shared React components -│ └── store/ # WordPress data store -├── sass/ # SASS source files -├── assets/ # Compiled assets (CSS, JS, images) -├── templates/ # PHP templates (BladeOne) -├── tests/ # Test files -├── modules/ # Feature modules -└── admin/ # Admin PHP + assets -``` - -See [Architecture Overview](Architecture-Overview) for a detailed breakdown. - -## Key Concepts - -- **Custom Post Type**: Forms are stored as `sureforms_form` posts with block content -- **Custom Database Tables**: Entries in `wp_srfm_entries`, payments in `wp_srfm_payments` -- **REST API**: All data operations use the `sureforms/v1` REST namespace -- **Block Editor**: Form fields are Gutenberg blocks registered via `block.json` -- **Admin SPA**: The dashboard is a React single-page application using Force UI components - -## Next Steps - -- [Architecture Overview](Architecture-Overview) -- Understand the codebase -- [Contributing Guide](Contributing-Guide) -- Development workflow and standards -- [Gutenberg Blocks](Gutenberg-Blocks) -- How form blocks work -- [REST API Reference](REST-API-Reference) -- Available API endpoints -- [Testing Guide](Testing-Guide) -- Running and writing tests diff --git a/docs/wiki/Gutenberg-Blocks.md b/docs/wiki/Gutenberg-Blocks.md deleted file mode 100644 index ed38cd9b3..000000000 --- a/docs/wiki/Gutenberg-Blocks.md +++ /dev/null @@ -1,129 +0,0 @@ -# Gutenberg Blocks - -SureForms provides 14+ Gutenberg block types for building forms in the WordPress block editor. - -## Block Types - -### Field Blocks - -| Block | Slug | Description | -|-------|------|-------------| -| Input | `srfm/input` | Text input (text, hidden, etc.) | -| Email | `srfm/email` | Email address input with validation | -| Textarea | `srfm/textarea` | Multi-line text area | -| Number | `srfm/number` | Numeric input with min/max | -| Phone | `srfm/phone` | Phone number with country selector (intl-tel-input) | -| URL | `srfm/url` | URL input with validation | -| Dropdown | `srfm/dropdown` | Select dropdown with options | -| Checkbox | `srfm/checkbox` | Single/multiple checkboxes | -| Multi Choice | `srfm/multichoice` | Radio/checkbox group | -| Address | `srfm/address` | Address field with subfields (street, city, state, zip, country) | -| GDPR | `srfm/gdpr` | GDPR consent checkbox | -| Payment | `srfm/payment` | Payment collection (Stripe integration) | -| Inline Button | `srfm/inlinebutton` | Inline form button | - -### Container Block - -| Block | Slug | Description | -|-------|------|-------------| -| SForm | `srfm/sform` | Main form wrapper block (required container for all field blocks) | - -## Block Registration - -### Server-Side (PHP) - -Blocks are registered in `inc/blocks/register.php` via the `Blocks\Register` class, initialized on the `init` hook: - -```php -// inc/blocks/register.php -register_block_type_from_metadata( - SRFM_DIR . 'inc/blocks/{block-type}/', - [ - 'render_callback' => [ $block_instance, 'render' ], - ] -); -``` - -Each block type has: -- `block.json` -- Block metadata, attributes, and category -- `block.php` -- Server-side rendering class extending `Blocks\Base` - -### Client-Side (JavaScript) - -Block registration happens in `src/blocks/blocks.js`: - -```javascript -// src/blocks/blocks.js -import { registerBlockType } from '@wordpress/blocks'; -// Block imports and registrations... -``` - -The `src/blocks/register-block.js` utility handles individual block registration. - -## Block Architecture - -### Base Class: `Blocks\Base` - -All block PHP classes extend `SRFM\Inc\Blocks\Base`, which provides: - -- `render()` -- Main render callback invoked by WordPress -- Access to block attributes from `block.json` -- Common rendering logic shared across field types -- Integration with field markup classes - -### Block to Field Mapping - -Each block delegates HTML generation to its corresponding field markup class in `inc/fields/`: - -``` -Block (inc/blocks/input/block.php) - -> Field Markup (inc/fields/input-markup.php) - -> Base Field (inc/fields/base.php) - -> HTML output -``` - -### block.json Structure - -Each block directory contains a `block.json` file defining: - -```json -{ - "apiVersion": 3, - "name": "srfm/input", - "title": "Input", - "category": "sureforms", - "attributes": { - "label": { "type": "string", "default": "Input" }, - "required": { "type": "boolean", "default": false }, - "placeholder": { "type": "string", "default": "" } - }, - "supports": { "html": false } -} -``` - -## Adding a New Block Type - -1. **Create block directory:** `inc/blocks/{new-type}/` -2. **Add `block.json`** with attributes, name, and category -3. **Create `block.php`** extending `Blocks\Base` with a `render()` method -4. **Create field markup:** `inc/fields/{new-type}-markup.php` extending `Fields\Base` -5. **Register in `Blocks\Register`** -- add to the blocks array -6. **Create editor component:** `src/blocks/{new-type}/` with `edit.js` and `index.js` -7. **Add to `src/blocks/blocks.js`** -- import and register the new block - -## The sform Container Block - -The `srfm/sform` block is the required wrapper for all form fields. It: - -- Renders the `` HTML element -- Handles form-level settings (styling, confirmation, CAPTCHA) -- Manages submission nonce generation -- Controls form layout and submit button -- Integrates with frontend JavaScript for submission handling - -## Related Pages - -- [Form Fields Architecture](Form-Fields-Architecture) -- Field markup and validation -- [Block Editor Controls](Block-Editor-Controls) -- Custom inspector controls -- [Architecture Overview](Architecture-Overview) -- Block registration in bootstrap -- [Frontend Assets](Frontend-Assets) -- Block editor asset loading diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md deleted file mode 100644 index 365d8af63..000000000 --- a/docs/wiki/Home.md +++ /dev/null @@ -1,58 +0,0 @@ -# SureForms Wiki - -Welcome to the SureForms developer wiki. This documentation covers the architecture, APIs, development workflows, and internals of the SureForms WordPress form builder plugin. - -**Version:** 2.5.1 | **WordPress:** 6.4+ | **PHP:** 7.4+ | **Node:** 24.16.0 - -## Quick Links - -- [Getting Started](Getting-Started) -- Set up your development environment -- [Architecture Overview](Architecture-Overview) -- Understand the codebase structure -- [Contributing Guide](Contributing-Guide) -- How to contribute - -## Architecture & Core - -| Page | Description | -|------|-------------| -| [Architecture Overview](Architecture-Overview) | Plugin bootstrap, namespaces, directory layout, design patterns | -| [Database Schema](Database-Schema) | Custom tables (`wp_srfm_entries`, `wp_srfm_payments`), post meta keys | -| [Environment Configuration](Environment-Configuration) | Constants, build tools, linting, development setup | -| [Form Submission Flow](Form-Submission-Flow) | End-to-end submission pipeline from frontend to storage | - -## WordPress / Backend - -| Page | Description | -|------|-------------| -| [Gutenberg Blocks](Gutenberg-Blocks) | Block types, registration, `block.json` structure | -| [Form Fields Architecture](Form-Fields-Architecture) | Field type hierarchy, validation, rendering pipeline | -| [WordPress Hooks Reference](WordPress-Hooks-Reference) | Custom actions, filters, and execution order | -| [Payment Integration](Payment-Integration) | Stripe setup, payment flow, webhooks, refunds | -| [Email Notifications](Email-Notifications) | Email templates, smart tags, notification settings | -| [AI Form Builder](AI-Form-Builder) | AI-powered form generation process | -| [Data Export & Import](Data-Export-Import) | Form/entry export, import, duplication | - -## Frontend / React - -| Page | Description | -|------|-------------| -| [Admin Dashboard](Admin-Dashboard) | React SPA architecture, routing, data fetching | -| [State Management](State-Management) | WordPress data store, actions, selectors | -| [Frontend Assets](Frontend-Assets) | Asset loading, build pipeline, SASS, Tailwind | -| [Block Editor Controls](Block-Editor-Controls) | Custom inspector controls, CSS generation, icons | - -## API - -| Page | Description | -|------|-------------| -| [REST API Reference](REST-API-Reference) | All endpoints with methods, parameters, authentication | - -## Development Workflow - -| Page | Description | -|------|-------------| -| [Getting Started](Getting-Started) | Prerequisites, installation, first build | -| [Contributing Guide](Contributing-Guide) | Git workflow, coding standards, PR process | -| [Testing Guide](Testing-Guide) | PHPUnit, Playwright, Jest setup and execution | -| [Deployment Guide](Deployment-Guide) | CI/CD pipelines, release process, version bumping | -| [Troubleshooting & FAQ](Troubleshooting-FAQ) | Common issues and debugging tips | -| [Changelog](Changelog) | Recent version history | diff --git a/docs/wiki/Payment-Integration.md b/docs/wiki/Payment-Integration.md deleted file mode 100644 index 28d985e6e..000000000 --- a/docs/wiki/Payment-Integration.md +++ /dev/null @@ -1,127 +0,0 @@ -# Payment Integration - -SureForms supports Stripe payment processing with one-time payments and recurring subscriptions. - -## Architecture Overview - -``` -inc/payments/ -|-- payments.php # Main payments orchestrator -|-- front-end.php # Frontend payment handling -|-- payment-helper.php # Shared payment utilities -|-- admin/ -| |-- admin-handler.php # Admin payment management UI -|-- stripe/ - |-- payments-settings.php # Stripe settings + REST routes - |-- stripe-helper.php # Stripe API utilities - |-- stripe-webhook.php # Webhook event handler - |-- admin-stripe-handler.php # Admin Stripe config -``` - -## Stripe Setup - -### Configuration - -Stripe settings are managed via the admin settings page and stored as WordPress options: - -1. Navigate to SureForms > Settings > Payments -2. Connect your Stripe account via OAuth flow -3. Configure test/live mode -4. Set default currency - -The `Payments_Settings` class registers REST routes for settings management and handles the Stripe OAuth callback via `admin_init`. - -### OAuth Flow - -1. Admin clicks "Connect Stripe" in settings -2. `intercept_stripe_callback()` on `admin_init` handles the OAuth redirect -3. Stripe credentials are securely stored in WordPress options - -## Payment Flow - -``` -1. FORM DISPLAY - - Payment block renders Stripe Elements - - Frontend_Assets enqueues Stripe JS SDK - - Payment form elements injected into the form - -2. FORM SUBMISSION - - User fills form + enters payment details - - Frontend JavaScript creates Stripe PaymentIntent - - Form data + payment token submitted to /submit-form - -3. SERVER PROCESSING - - Form_Submit validates form data - - Payment processing triggered - - Stripe API called to confirm payment - - Payment record created in wp_srfm_payments table - - Entry created in wp_srfm_entries table - - Payment ID linked to entry - -4. WEBHOOK HANDLING - - Stripe sends webhook events to /wp-json endpoint - - Stripe_Webhook validates signature - - Payment status updated in database - - Subscription events handled (renewal, cancellation, etc.) -``` - -## Payment Database Schema - -Payments are stored in the `wp_srfm_payments` custom table. See [Database Schema](Database-Schema) for full column definitions. - -### Payment Types - -| Type | Description | -|------|-------------| -| `payment` | One-time payment | -| `subscription` | Recurring subscription record | -| `renewal` | Subscription renewal payment | - -### Payment Statuses - -`pending`, `succeeded`, `failed`, `canceled`, `requires_action`, `requires_payment_method`, `processing`, `refunded`, `partially_refunded` - -### Subscription Statuses - -`active`, `canceled`, `past_due`, `unpaid`, `trialing`, `incomplete`, `incomplete_expired`, `paused` - -## Webhook Handling - -The `Stripe_Webhook` class (`inc/payments/stripe/stripe-webhook.php`) processes Stripe webhook events: - -- Validates webhook signature using Stripe signing secret -- Handles payment status updates (succeeded, failed, etc.) -- Processes subscription lifecycle events (renewal, cancellation) -- Creates renewal payment records for subscription billing cycles -- Updates payment and subscription statuses in the database - -## Refund Management - -The `Payments` table class provides refund tracking: - -- `add_refund_to_payment_data()` -- Record refund details in payment_data JSON -- `add_refund_amount()` -- Increment the refunded_amount column -- `get_refundable_amount()` -- Calculate remaining refundable balance -- `is_fully_refunded()` / `is_partially_refunded()` -- Status checks - -## Supported Currencies - -USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, SEK, NZD, MXN, SGD, HKD, NOK, TRY, RUB, INR, BRL, ZAR, KRW - -## Payment Modes - -| Mode | Description | -|------|-------------| -| `test` | Stripe test mode (test API keys) | -| `live` | Stripe live mode (production) | - -## Admin Payment Management - -The `Admin_Handler` class provides the admin interface for viewing and managing payments. Payment IDs in entry views are rendered as clickable links via the `srfm_entry_value` filter. - -## Related Pages - -- [Database Schema](Database-Schema) -- Payment table columns and relationships -- [Form Submission Flow](Form-Submission-Flow) -- Payment processing in submission pipeline -- [REST API Reference](REST-API-Reference) -- Payment-related endpoints -- [Gutenberg Blocks](Gutenberg-Blocks) -- Payment block type diff --git a/docs/wiki/REST-API-Reference.md b/docs/wiki/REST-API-Reference.md deleted file mode 100644 index d366470e5..000000000 --- a/docs/wiki/REST-API-Reference.md +++ /dev/null @@ -1,114 +0,0 @@ -# REST API Reference - -All endpoints are registered under the namespace `sureforms/v1` and accessible at `/wp-json/sureforms/v1/`. - -## Authentication - -### Admin Endpoints -All admin endpoints require the `manage_options` capability. Authentication is via WordPress REST API nonce (`X-WP-Nonce` header) or cookie-based auth. - -Permission check: `Helper::get_items_permissions_check()` verifies `current_user_can('manage_options')`. - -### Public Endpoints -The form submission endpoint uses a custom nonce (`X-WP-Submit-Nonce` header) that does not require a logged-in user. - -## Endpoints - -### AI Form Builder - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/generate-form` | Generate a form using AI | -| POST | `/map-fields` | Map AI response to SureForms Gutenberg blocks | -| GET | `/initiate-auth` | Get auth URL for AI billing portal | -| POST | `/handle-access-key` | Decrypt and save AI access key | - -### Dashboard - -| Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/entries-chart-data` | Get entries chart data for analytics | -| GET | `/form-data` | Get all forms data for dashboard | -| GET | `/plugin-status` | Check integration plugin install status | - -### Onboarding - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/onboarding/set-status` | Mark onboarding as complete | -| GET | `/onboarding/get-status` | Get current onboarding status | - -### Entries - -| Method | Endpoint | Description | Parameters | -|--------|----------|-------------|------------| -| GET | `/entries/list` | Paginated entries listing | `form_id`, `status`, `search`, `date_from`, `date_to`, `orderby`, `order`, `per_page`, `page` | -| POST | `/entries/read-status` | Bulk mark entries read/unread | `entry_ids` (required), `action` (required: read/unread) | -| POST | `/entries/trash` | Bulk trash/restore entries | `entry_ids` (required), `action` (required: trash/restore) | -| POST | `/entries/delete` | Permanently delete entries | `entry_ids` (required) | -| POST | `/entries/export` | Export entries to CSV/ZIP | `entry_ids`, `form_id`, `status`, `search`, `date_from`, `date_to` | -| GET | `/entry/{id}/details` | Single entry with form data | `id` (required) | -| GET | `/entry/{id}/logs` | Entry activity logs | `id` (required), `page`, `per_page` | - -### Forms - -| Method | Endpoint | Description | Parameters | -|--------|----------|-------------|------------| -| GET | `/forms` | Paginated forms listing | `page`, `per_page`, `search`, `status`, `orderby`, `order`, `date_from`, `date_to` | -| POST | `/forms/export` | Export forms as JSON | `form_ids` (required) | -| POST | `/forms/import` | Import forms from JSON | File upload | -| POST | `/forms/manage` | Manage form lifecycle | `form_ids` (required), `action` (trash/restore/delete) | -| POST | `/forms/duplicate` | Duplicate a form | `form_id` (required) | - -### Form Submission (Public) - -| Method | Endpoint | Auth | Description | -|--------|----------|------|-------------| -| POST | `/submit-form` | Custom nonce | Public form submission | -| GET | `/refresh-nonces` | None | Refresh frontend nonces | - -## Extending the API - -Endpoints are extensible via the `srfm_rest_api_endpoints` filter: - -```php -add_filter( 'srfm_rest_api_endpoints', function( $endpoints ) { - $endpoints['custom/endpoint'] = [ - 'methods' => 'GET', - 'callback' => [ $this, 'handle_custom' ], - 'permission_callback' => [ Helper::class, 'get_items_permissions_check' ], - ]; - return $endpoints; -} ); -``` - -## Error Responses - -Errors are returned via `wp_send_json_error()`: - -```json -{ - "success": false, - "data": "Error message string" -} -``` - -## Pagination - -List endpoints support pagination with consistent parameters: - -| Parameter | Default | Description | -|-----------|---------|-------------| -| `page` | 1 | Current page number | -| `per_page` | 20 | Items per page | -| `orderby` | `created_at` | Sort column | -| `order` | `DESC` | Sort direction (ASC/DESC) | - -Response includes total counts for pagination UI. - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- API registration flow -- [Form Submission Flow](Form-Submission-Flow) -- Submit endpoint details -- [Data Export Import](Data-Export-Import) -- Export/import endpoints -- [Payment Integration](Payment-Integration) -- Payment API endpoints diff --git a/docs/wiki/State-Management.md b/docs/wiki/State-Management.md deleted file mode 100644 index 6cb4b072a..000000000 --- a/docs/wiki/State-Management.md +++ /dev/null @@ -1,109 +0,0 @@ -# State Management - -SureForms uses the `@wordpress/data` package to create a Redux-like data store for managing application state in the block editor. - -## Store Structure - -Located in `src/store/`: - -``` -src/store/ -|-- store.js # Store registration -|-- reducer.js # State reducers -|-- actions.js # Action creators -|-- selectors.js # State selectors -|-- constants.js # Store constants -|-- setInitialState.js # Initial state configuration -``` - -## Store Registration - -The store is registered with `@wordpress/data` in `store.js`: - -```javascript -import { createReduxStore, register } from '@wordpress/data'; -import reducer from './reducer'; -import * as actions from './actions'; -import * as selectors from './selectors'; - -const store = createReduxStore('sureforms', { - reducer, - actions, - selectors, -}); - -register(store); -``` - -The store namespace is `sureforms`. - -## Initial State - -`setInitialState.js` configures the initial state from server-side localized data (`wp_localize_script`), providing: - -- Form settings and metadata -- Block defaults -- User preferences -- Plugin configuration - -## Actions - -Actions are defined in `actions.js` and dispatched to modify the store state. They follow the WordPress data store pattern: - -```javascript -// Dispatching an action -import { dispatch } from '@wordpress/data'; -dispatch('sureforms').updateFormSettings({ key: 'value' }); -``` - -## Selectors - -Selectors in `selectors.js` extract specific data from the store state: - -```javascript -// Using a selector -import { select } from '@wordpress/data'; -const settings = select('sureforms').getFormSettings(); -``` - -## Component Integration - -Components connect to the store using WordPress data hooks: - -```javascript -import { useSelect, useDispatch } from '@wordpress/data'; - -function MyComponent() { - const formData = useSelect((select) => { - return select('sureforms').getFormSettings(); - }, []); - - const { updateFormSettings } = useDispatch('sureforms'); - - return ( - - ); -} -``` - -## Constants - -`constants.js` defines store-wide constants used across actions, reducers, and selectors to ensure consistency. - -## Usage Context - -The WordPress data store is primarily used in: - -- **Block editor** -- Form-level settings and block state -- **Inspector controls** -- Sidebar settings panels -- **Block attributes** -- Synchronized state between blocks - -For the admin dashboard pages, `@tanstack/react-query` is used instead for server state management. See [Admin Dashboard](Admin-Dashboard) for details. - -## Related Pages - -- [Admin Dashboard](Admin-Dashboard) -- React admin app architecture -- [Block Editor Controls](Block-Editor-Controls) -- Inspector controls using the store -- [Gutenberg Blocks](Gutenberg-Blocks) -- Block registration and attributes diff --git a/docs/wiki/Testing-Guide.md b/docs/wiki/Testing-Guide.md deleted file mode 100644 index ea129c084..000000000 --- a/docs/wiki/Testing-Guide.md +++ /dev/null @@ -1,172 +0,0 @@ -# Testing Guide - -SureForms uses three testing frameworks: PHPUnit for PHP unit tests, Playwright for end-to-end tests, and Jest for JavaScript unit tests. - -## Testing Strategy - -| Level | Framework | Location | Purpose | -|-------|-----------|----------|---------| -| Unit (PHP) | PHPUnit 9.x | `tests/unit/` | PHP class and function testing | -| Unit (JS) | Jest | Via `wp-scripts` | JavaScript utility testing | -| E2E | Playwright 1.48+ | `tests/play/` | Full browser-based workflow testing | - -## PHPUnit (PHP Unit Tests) - -### Setup - -1. Install PHP dependencies: - ```bash - composer install - ``` - -2. WordPress test environment must be available (via wp-env or local setup) - -### Running Tests - -```bash -# Run all PHP unit tests -vendor/bin/phpunit - -# Run a specific test file -vendor/bin/phpunit tests/unit/test-specific.php - -# Run tests with verbose output -vendor/bin/phpunit --verbose -``` - -### Test File Conventions - -- Location: `tests/unit/` -- File prefix: `test-` (e.g., `test-helper.php`) -- Class naming: `Test_ClassName` extending `WP_UnitTestCase` -- Method naming: `test_` prefix (e.g., `test_form_submission()`) - -### CI Configuration - -PHPUnit runs in GitHub Actions (`.github/workflows/phpunit.yml`): -- PHP: 8.2 -- WordPress: trunk -- MySQL: 5.7 -- Triggered on pull requests - -## Playwright (E2E Tests) - -### Prerequisites - -- Node.js 24.16.0 (Volta) -- Docker (for wp-env) - -### Setup - -```bash -# Install dependencies -npm install - -# Start the WordPress environment -npm run play:up - -# Build the plugin -npm run build - -# Install Playwright browsers -npx playwright install -``` - -### Running Tests - -```bash -# Run all E2E tests (headless) -npm run play:run - -# Run tests in headed mode (visible browser) -npm run play:run:interactive - -# Run a specific test file -npx playwright test tests/play/specific-test.spec.js -``` - -### Test File Conventions - -- Location: `tests/play/` -- File suffix: `.spec.js` -- Uses Playwright Test runner with `expect` assertions -- Page Object Model pattern for reusable interactions - -### CI Configuration - -Playwright runs in GitHub Actions (`.github/workflows/playwright.yml`): -- Triggered when PR is labeled with `e2e` -- Node: 18.15 -- Builds plugin and starts wp-env -- Generates HTML test report (30-day artifact retention) - -## Jest (JavaScript Unit Tests) - -### Running Tests - -```bash -npm run test:unit -``` - -Uses `@wordpress/scripts` Jest configuration with WordPress-specific transforms and module resolution. - -## Test Environment (wp-env) - -The Docker-based test environment is configured in `.wp-env.json`: - -```bash -# Start environment -npm run play:up - -# Stop environment -npm run play:stop - -# Clean all data -npm run env:clean -``` - -wp-env provides: -- WordPress installation with the plugin active -- MySQL database -- Consistent environment across developers -- Used by both Playwright E2E tests and local development - -## Writing New Tests - -### PHP Unit Test - -```php -class Test_My_Feature extends WP_UnitTestCase { - public function test_feature_works() { - // Arrange - $form_id = $this->factory->post->create([ - 'post_type' => 'sureforms_form', - ]); - - // Act - $result = some_function($form_id); - - // Assert - $this->assertEquals('expected', $result); - } -} -``` - -### Playwright E2E Test - -```javascript -const { test, expect } = require('@playwright/test'); - -test('form submission works', async ({ page }) => { - await page.goto('/form-page/'); - await page.fill('[name="email"]', 'test@example.com'); - await page.click('button[type="submit"]'); - await expect(page.locator('.success-message')).toBeVisible(); -}); -``` - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup -- [Contributing Guide](Contributing-Guide) -- Test requirements for PRs -- [Deployment Guide](Deployment-Guide) -- CI/CD test pipeline diff --git a/docs/wiki/Troubleshooting-FAQ.md b/docs/wiki/Troubleshooting-FAQ.md deleted file mode 100644 index 7afa4619a..000000000 --- a/docs/wiki/Troubleshooting-FAQ.md +++ /dev/null @@ -1,232 +0,0 @@ -# Troubleshooting & FAQ - -Common issues, debugging tips, and frequently asked questions for SureForms development. - -## Common Development Issues - -### Build Failures - -**Problem:** `npm run build` fails with errors. - -**Solutions:** -1. Ensure Node.js 24.16.0 is active (Volta manages this automatically via `package.json`): - ```bash - node -v # Should output v24.16.0 - ``` -2. Clear and reinstall dependencies: - ```bash - rm -rf node_modules - npm install - ``` -3. If SASS compilation fails, check that `sass/` source files have valid syntax -4. If Grunt minification fails, ensure Grunt CLI is available: - ```bash - npx grunt minify - ``` - -### wp-env Issues - -**Problem:** `npm run play:up` fails to start the Docker environment. - -**Solutions:** -1. Ensure Docker is installed and running -2. Clean the environment: - ```bash - npm run env:clean - ``` -3. Restart the environment: - ```bash - npm run play:stop - npm run play:up - ``` -4. If port conflicts occur, check for other services on ports 8888/8889 - -### PHPCS Errors - -**Problem:** `vendor/bin/phpcs` reports violations. - -**Solutions:** -1. Auto-fix what's possible: - ```bash - vendor/bin/phpcbf - ``` -2. Common issues: - - Missing text domain: Add `'sureforms'` to all translatable strings - - Unescaped output: Wrap output in `esc_html()`, `esc_attr()`, etc. - - Missing nonce verification: Add nonce checks for form/AJAX handlers - - Short array syntax: Use `[]` instead of `array()` (enforced by config) - -### PHPUnit Test Failures - -**Problem:** `vendor/bin/phpunit` tests fail. - -**Solutions:** -1. Ensure WordPress test environment is available (wp-env or local setup) -2. Check database connectivity for the test suite -3. Run a specific test to isolate the issue: - ```bash - vendor/bin/phpunit tests/unit/test-specific.php - ``` - -### JavaScript Lint Errors - -**Problem:** `npm run lint-js` reports errors. - -**Solutions:** -1. Auto-fix most issues: - ```bash - npm run lint-js:fix - ``` -2. Format code with Prettier: - ```bash - npm run pretty:fix - ``` -3. Common issues: - - Import ordering: WordPress dependencies should use `@wordpress/*` - - Unused variables: Remove or prefix with underscore - - Missing dependencies in `useEffect`: Add to dependency array - -## Form Submission Issues - -### Nonce Verification Fails - -**Cause:** The `X-WP-Submit-Nonce` has expired (default: 24-hour lifetime). - -**Debug:** -- Nonces can be refreshed via `GET /wp-json/sureforms/v1/refresh-nonces` -- Check that the nonce is being sent in the request header -- Caching plugins may cache the nonce -- exclude form pages from page cache - -### CAPTCHA Validation Fails - -**Cause:** CAPTCHA keys are misconfigured or the token is expired. - -**Debug:** -1. Verify CAPTCHA keys in SureForms global settings -2. Check browser console for CAPTCHA loading errors -3. Ensure the CAPTCHA provider (reCAPTCHA, hCaptcha, Turnstile) is correctly configured -4. reCAPTCHA v3 can trigger multiple times on pages with multiple forms - -### File Upload Errors - -**Debug:** -1. Check PHP `upload_max_filesize` and `post_max_size` in `php.ini` -2. Verify allowed file types in form field settings -3. Check file size against the configured maximum -4. Ensure the `wp-content/uploads/` directory is writable - -## Database Issues - -### Custom Tables Missing - -**Cause:** The `wp_srfm_entries` or `wp_srfm_payments` table was not created. - -**Debug:** -1. Deactivate and reactivate the plugin to trigger table creation -2. Check the `srfm_db_version` option in `wp_options` -3. Verify database user has `CREATE TABLE` permissions -4. Check for errors in `debug.log` during activation - -### Entry Data Not Saving - -**Debug:** -1. Check the `do_not_store_entries` form setting (GDPR compliance) -2. Verify the form submission response for error messages -3. Check `debug.log` for database insert errors -4. Ensure the entries table schema is up to date - -## REST API Issues - -### 401 Unauthorized on Admin Endpoints - -**Cause:** User is not authenticated or lacks required capabilities. - -**Debug:** -1. Admin endpoints require `manage_options` capability -2. Ensure the user is logged in with a valid session -3. Check that the REST API nonce is being sent (via `X-WP-Nonce` header) -4. WordPress cookie authentication must be active - -### 403 on Form Submission - -**Cause:** The submit nonce is invalid or form restrictions are blocking submission. - -**Debug:** -1. Check the `X-WP-Submit-Nonce` header value -2. Review form restriction settings (max entries, date scheduling, login required) -3. Check the `srfm_form_submit_permissions_check` filter - -## Performance Issues - -### Slow Admin Dashboard - -**Causes & Solutions:** -1. **Large number of entries:** Entries are paginated -- default page size is managed by REST API -2. **Unoptimized queries:** The entries table has indexes on `form_id`, `user_id`, and a composite index on `(form_id, created_at, status)` -3. **Asset loading:** Admin assets are conditionally loaded only on SureForms admin pages - -### Slow Form Rendering - -**Causes & Solutions:** -1. **Too many blocks:** Complex forms with many fields may benefit from multi-step layout -2. **External scripts:** CAPTCHA and payment scripts load conditionally based on form settings -3. **CSS generation:** Each block generates dynamic CSS -- review `generateCSS.js` for optimization - -## Debugging Tips - -### Enable WordPress Debug Mode - -Add to `wp-config.php`: -```php -define( 'WP_DEBUG', true ); -define( 'WP_DEBUG_LOG', true ); -define( 'WP_DEBUG_DISPLAY', false ); -define( 'SCRIPT_DEBUG', true ); -``` - -### Check REST API Responses - -Use the browser developer tools Network tab to inspect REST API calls: -- Filter by `sureforms/v1` to see plugin API requests -- Check response status codes and error messages - -### Inspect Form Data Store - -In the browser console on a form editor page: -```javascript -wp.data.select('starter-starter/starter-starter-forms-data').getFormSettings(); -``` - -## FAQ - -### What WordPress version is required? - -WordPress 6.4 or higher is required. - -### What PHP version is required? - -PHP 7.4 or higher. The `phpcs.xml` enforces PHP 7.4+ compatibility. - -### Can I modify bundled libraries? - -Files in `inc/lib/` are third-party libraries and should not be modified directly. If changes are needed, they should be managed upstream. - -### Where do I put new PHP classes? - -New PHP classes go in the `inc/` directory following the existing namespace structure (`SRFM\Inc\*`). The autoloader in `plugin-loader.php` handles class loading. - -### How do I add a new admin page? - -Admin pages are rendered by the React SPA in `src/admin/`. Add new routes using `react-router-dom` and register the corresponding menu items in PHP. See [Admin Dashboard](Admin-Dashboard) for details. - -### How do I add a new form field type? - -See [Form Fields Architecture](Form-Fields-Architecture) for the field type hierarchy and the process for adding new field types. - -## Related Pages - -- [Environment Configuration](Environment-Configuration) -- Development setup -- [Testing Guide](Testing-Guide) -- Running tests -- [Architecture Overview](Architecture-Overview) -- Codebase structure -- [REST API Reference](REST-API-Reference) -- API endpoints -- [Database Schema](Database-Schema) -- Table structure diff --git a/docs/wiki/Using-WPML-With-SureForms.md b/docs/wiki/Using-WPML-With-SureForms.md deleted file mode 100644 index 540b74aef..000000000 --- a/docs/wiki/Using-WPML-With-SureForms.md +++ /dev/null @@ -1,243 +0,0 @@ -# Using WPML With SureForms - -SureForms ships with native compatibility for **WPML**, the leading WordPress multilingual plugin. With WPML active, you can offer your forms in any number of languages without duplicating the form itself — one form serves every language, every submission is tagged with the visitor's language, and translators work from a single place. - -This guide walks you through everything you need to know as a site owner, content editor, or translator. - ---- - -## What you can do with WPML + SureForms - -Once both plugins are active and configured, SureForms automatically: - -- Translates **field labels, placeholders, help text, error messages** and **dropdown / multi-choice options** based on the visitor's language. -- Translates the **submit button** label. -- Translates the **success message** shown after a submission. -- Translates the **confirmation redirect content** (when "show same page" confirmation is used). -- Translates the **form-restriction notice** (e.g., "This form is closed"). -- Records the **submission language** alongside every entry, so you can see at a glance which language a lead came from. - -You keep one form. You collect entries from every language into the same list. Reports, integrations, and exports stay unified. - ---- - -## Before you start - -You will need: - -| Item | Notes | -|---|---| -| **WordPress 6.4 or higher** | SureForms minimum requirement. | -| **WPML Multilingual CMS** (4.5+) | Paid plugin from [wpml.org](https://wpml.org). | -| **WPML String Translation** add-on | Required to translate form labels, options, and messages. Bundled with most WPML licences. | -| **SureForms 2.5.1 or higher** | Native WPML support shipped in this release. | - -> **Polylang and other multilingual plugins:** SureForms is architected with a generic provider interface so other plugins can be supported in the future. WPML is the only multilingual plugin currently supported out of the box. - ---- - -## Step 1 — Install and configure WPML - -1. Install and activate the following plugins from your WordPress dashboard: - - **WPML Multilingual CMS** (`sitepress-multilingual-cms`) - - **WPML String Translation** - -2. Open **WPML → Languages** and choose at least two languages (for example, English as the default, plus German and French). - -3. Open **WPML → Settings → Translation Options** and confirm that **Posts** are set to *"Translatable — use translation"*. - -That's the only WPML setup that is required. SureForms takes care of the rest automatically. - ---- - -## Step 2 — Create your form (just once) - -Create a SureForms form exactly the way you always have: - -1. Go to **SureForms → Add New Form**. -2. Pick a template or start blank. -3. Add fields (text, email, dropdown, multi-choice, etc.) and configure their labels, placeholders, help text, options, and error messages in your default language. -4. Configure the submit button text, success message, and any other form settings. -5. Save the form. - -> **Important:** Do **not** try to duplicate the form for each language. SureForms intentionally registers the form post type as *non-translatable* in WPML. There is one canonical form post; WPML translates the strings on it. This keeps entries unified and avoids the maintenance nightmare of keeping multiple form copies in sync. - -When you save the form, SureForms automatically registers every translatable string with WPML under the `sureforms` text domain. - ---- - -## Step 3 — Translate your form strings - -1. Go to **WPML → String Translation**. -2. In the **"Select strings within domain"** dropdown at the top, choose `sureforms`. -3. You will see entries for every translatable piece of the form, grouped by form ID for easy scanning. For example, a form with ID `42` will show strings such as: - - - `form_42_submit_button` - - `form_42_confirmation_0_message` - - `form_42_notification_0_subject` - - `form_42_restriction_message` - - `form_42_block_{block-id}_label` - - `form_42_block_{block-id}_placeholder` - - `form_42_block_{block-id}_helpText` - - `form_42_block_{block-id}_errorMsg` - - `form_42_block_{block-id}_option_0_label` *(for dropdown / multi-choice options)* - -4. Click **"translations"** next to any string, enter the translated text for each language, and save. - -You can also use WPML's built-in **Translation Editor** workflow or send strings to a translation service from this screen — the standard WPML String Translation UI applies. - -> **Tip:** The per-form naming scheme (`form_{id}_...`) keeps every form's strings neatly grouped, so translators don't have to hunt across the entire site for a particular form's labels. - ---- - -## Step 4 — Display your form on translated pages - -SureForms forms are added to pages exactly the way they always have been — via the SureForms block or the `[sureforms id="..."]` shortcode. - -For the translated version of a form to render correctly, the **host page must also be translated in WPML**: - -1. Create the original page and embed your form on it. -2. In WPML, translate that page into each language. The form block / shortcode is carried over automatically. -3. Visit the translated URL (e.g., `/de/contact/`) — the form will render with all the labels, placeholders, options, and the submit button in German. - -If you instead use SureForms' **instant form** URL (`/form/{id}/`), append the language code to switch languages: - -``` -https://example.com/form/42/?lang=de -https://example.com/form/42/?lang=fr -``` - ---- - -## Step 5 — Verify everything is translating - -Open the translated form in the browser and confirm that the following are all in the target language: - -- ✅ Field labels -- ✅ Field placeholders -- ✅ Help text -- ✅ Inline error messages (e.g., "This field is required") -- ✅ Dropdown / multi-choice / checkbox option labels -- ✅ Submit button text -- ✅ Success / confirmation message after submission -- ✅ Form restriction notice (if the form is closed or capped) - -If anything appears in the original language: - -1. Check that the host page is translated in WPML. -2. Check that the specific string has a translation in **WPML → String Translation** (filter by domain `sureforms`). -3. Re-save the form once — this regenerates the string registry. - ---- - -## Step 6 — Collect entries and review them by language - -Submissions made on the German version of the page are stored against the **same form** as English submissions, with the submission language recorded on the entry row. - -1. Go to **SureForms → Entries**. -2. You will see a new **Language** column between *Date & Time* and *Actions*. -3. Each entry displays an uppercase language code: `EN`, `DE`, `FR`, etc. -4. Click the **Language** column header to sort entries by language. - -Older entries (and entries submitted on sites where WPML is not active) display `—`. - -> **Tip:** All your usual entry features — search, view details, delete, export — work as normal. Language is just an extra column for filtering and reporting. - ---- - -## Step 7 — Form restriction messages - -If you have configured form restrictions (a closing date or maximum entry count), the **restriction notice** shown to visitors is automatically translated: - -1. Open the form and enable restrictions (e.g., set a close date in the past, or set a max-entry limit). -2. Customise the restriction message in the form settings. -3. Translate `form_{id}_restriction_message` in **WPML → String Translation**. -4. Trigger the restriction (visit the form in a translated URL once it's closed) — visitors see the translated restriction notice. - ---- - -## What about email notifications? - -> **Note:** In the current release, email notifications (the message sent to your admin or to the visitor after a successful submission) are sent in the site's default language. Multilingual email notifications — where each email is composed in the visitor's submission language — are planned for a future SureForms release. - -The submission language is already recorded on the entry, so any custom integration that hooks into the SureForms submission flow can use it. - ---- - -## Graceful fallback when WPML is not active - -You can safely install and uninstall WPML at any time: - -- **WPML active**: forms translate based on the visitor's language. Entries are tagged. -- **WPML deactivated**: forms render in their default language as if WPML had never been installed. New entries record an empty language string. No errors, no broken pages. - -The plugin is designed so that the multilingual layer is completely silent when WPML is not active. - ---- - -## Troubleshooting - -**Translated strings don't appear in WPML String Translation** - -- Confirm that *WPML String Translation* is active (not just WPML core). -- Re-save your form once. The string registry runs on form save. -- Filter by domain `sureforms` in the WPML String Translation UI — strings from other plugins live in different domains. - -**Translated labels still render in the original language on `/de/...`** - -- Make sure the host page is translated in WPML and you are visiting the translated URL. -- Open WPML → String Translation, filter by `sureforms`, and confirm the specific strings actually have translations saved. -- Check that the page language switcher confirms you are on the German (or other target) version of the page. - -**The success message is in English even though I submitted in German** - -- Confirm `form_{id}_confirmation_0_message` has a German translation in WPML String Translation. -- Confirm the form is being submitted from a German page (`/de/...`), not from a query-string variant in some WPML modes. - -**Entries show `—` in the Language column** - -- Older entries submitted before WPML support shipped will not have a language stored. -- Entries created from the admin (e.g., test submissions, or imports) may not carry a language code. -- New front-end submissions on translated pages with WPML active will always carry a language code. - -**An entry's language code is wrong** - -- The visitor's language is determined by WPML based on the URL of the page they submitted from. If your WPML configuration uses query-string mode (`?lang=de`), make sure the parameter is on the page URL when the form is submitted. - ---- - -## Frequently asked questions - -**Do I need to duplicate the form for each language?** - -No. There is one canonical form post regardless of how many languages you support. WPML translates the strings on it. - -**Will entries from different languages mix in the same list?** - -Yes — by design. All entries from a given form, regardless of the visitor's language, belong to that one form. The Language column lets you filter and sort. - -**Can I export entries with the language?** - -The submission language is stored alongside each entry and is available to any integration that reads SureForms entries. A future SureForms release will include the language column in the standard CSV export header. - -**What languages are supported?** - -Any language WPML supports. SureForms records the standard WPML language code (`en`, `de`, `hi`, `zh-cn`, `pt-br`, etc.) — up to 20 characters, which covers the full WPML and BCP-47 locale-code range. - -**Does this work with Polylang or other multilingual plugins?** - -SureForms is built on a generic multilingual provider interface, so support for Polylang and other plugins is straightforward to add. WPML is the only multilingual plugin currently supported in the box. - ---- - -## Need help? - -If you run into anything not covered here, contact the SureForms support team at [sureforms.com/support](https://sureforms.com/support) with: - -1. Your SureForms version (Plugins → Installed Plugins → SureForms). -2. Your WPML version, including whether *WPML String Translation* is active. -3. Your form ID. -4. The specific string or behaviour that isn't translating. -5. A screenshot if possible. - -We're happy to help. diff --git a/docs/wiki/WordPress-Hooks-Reference.md b/docs/wiki/WordPress-Hooks-Reference.md deleted file mode 100644 index 1db16e372..000000000 --- a/docs/wiki/WordPress-Hooks-Reference.md +++ /dev/null @@ -1,152 +0,0 @@ -# WordPress Hooks Reference - -SureForms exposes custom actions and filters for extensibility, and hooks into WordPress core lifecycle events. - -## Custom Actions - -| Action | Parameters | Description | -|--------|-----------|-------------| -| `srfm_core_loaded` | -- | Fired after Plugin_Loader singleton is created | -| `srfm_before_submission` | `$form_data`, `$form_id` | Before form submission is processed | -| `srfm_form_submit` | `$entry_id`, `$form_id`, `$form_data` | After form submission is complete | -| `srfm_after_submission_process` | `$entry_id`, `$form_id` | After all post-submission processing | -| `srfm_before_delete_entry` | `$entry_id` | Before an entry is permanently deleted | - -## Custom Filters - -### REST API - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_rest_api_endpoints` | `$endpoints` | Add/modify REST API endpoints | - -### Form Data - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_form_submit_data` | `$form_data`, `$form_id` | Modify form data before processing | -| `srfm_entry_value` | `$value`, `$args` | Transform entry field values in responses | -| `srfm_normalize_csv_field_value` | `$value`, `$field` | Custom CSV export value formatting | -| `srfm_form_confirmation_params` | `$params`, `$form_id` | Modify confirmation settings | - -### Post Meta - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_register_post_meta` | `$meta_keys` | Register additional form post meta keys | - -### Blocks - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_is_special_block` | `$is_special`, `$block_name` | Declare a block as special (nested) | -| `srfm_handle_special_block` | `$result`, `$block` | Custom processing for special blocks | -| `srfm_extract_form_fields_field_name` | `$field_name`, `$block` | Modify generated field names | - -### Settings - -| Filter | Parameters | Description | -|--------|-----------|-------------| -| `srfm_global_settings_data` | `$settings` | Extend global settings data | -| `srfm_disable_nps_survey` | `$disable` | Disable NPS survey module | -| `srfm_enable_redirect_activation` | `$enable` | Control activation redirect behavior | - -## WordPress Core Hooks Used - -### plugins_loaded - -| Priority | Callback | Class | -|----------|----------|-------| -| default | `load_textdomain()` | Plugin_Loader | -| 99 | `load_plugin()` | Plugin_Loader | - -### init - -| Priority | Callback | Purpose | -|----------|----------|---------| -| 10 | `load_classes()` | Block registration, admin, payments | - -### rest_api_init - -| Callback | Class | Purpose | -|----------|-------|---------| -| `register_endpoints()` | Rest_Api | Register all admin REST endpoints | -| `register_routes()` | Form_Submit | Register public submission endpoints | -| `register_rest_routes()` | Payments_Settings | Register payment REST routes | - -### admin_init - -| Callback | Purpose | -|----------|---------| -| `activation_redirect()` | Redirect to onboarding after activation | -| `intercept_stripe_callback()` | Handle Stripe OAuth callback | - -### wp_enqueue_scripts - -| Callback | Class | Purpose | -|----------|-------|---------| -| Asset enqueue | Frontend_Assets | Load form CSS/JS on pages with forms | - -### enqueue_block_editor_assets - -| Callback | Class | Purpose | -|----------|-------|---------| -| Editor asset enqueue | Gutenberg_Hooks | Load block editor JS/CSS | - -## Hook Execution Order (Form Submission) - -``` -1. REST API receives POST /submit-form -2. Form_Submit::submit_form_permissions_check() - -> Nonce verification -3. Form_Submit::handle_form_submission() - -> apply_filters('srfm_form_submit_data', $data, $form_id) - -> Field validation - -> CAPTCHA verification - -> Honeypot check -4. do_action('srfm_before_submission', $data, $form_id) -5. Email notification sent -6. Entry stored in database -7. do_action('srfm_form_submit', $entry_id, $form_id, $data) -8. do_action('srfm_after_submission_process', $entry_id, $form_id) -9. Confirmation response returned -``` - -## Usage Examples - -### Adding a Custom REST Endpoint - -```php -add_filter( 'srfm_rest_api_endpoints', function( $endpoints ) { - $endpoints['my-custom/data'] = [ - 'methods' => 'GET', - 'callback' => 'my_custom_callback', - 'permission_callback' => [ \SRFM\Inc\Helper::class, 'get_items_permissions_check' ], - ]; - return $endpoints; -} ); -``` - -### Modifying Form Data Before Processing - -```php -add_filter( 'srfm_form_submit_data', function( $data, $form_id ) { - // Add custom processing - $data['custom_field'] = 'custom_value'; - return $data; -}, 10, 2 ); -``` - -### Running Code After Submission - -```php -add_action( 'srfm_form_submit', function( $entry_id, $form_id, $form_data ) { - // Send to external service, trigger automation, etc. -}, 10, 3 ); -``` - -## Related Pages - -- [Architecture Overview](Architecture-Overview) -- Plugin bootstrap and hook registration -- [REST API Reference](REST-API-Reference) -- Full endpoint documentation -- [Form Submission Flow](Form-Submission-Flow) -- Submission hook execution order diff --git a/docs/wiki/_Footer.md b/docs/wiki/_Footer.md deleted file mode 100644 index 0a54d2e24..000000000 --- a/docs/wiki/_Footer.md +++ /dev/null @@ -1,3 +0,0 @@ ---- - -**SureForms** by [Brainstorm Force](https://brainstormforce.com/) | [GitHub](https://github.com/brainstormforce/sureforms) | [Website](https://sureforms.com/) | [Support](https://support.brainstormforce.com/) diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md deleted file mode 100644 index 4fe3b09d1..000000000 --- a/docs/wiki/_Sidebar.md +++ /dev/null @@ -1,36 +0,0 @@ -**[Home](Home)** - -**Getting Started** -- [Getting Started](Getting-Started) -- [Environment Configuration](Environment-Configuration) - -**Architecture** -- [Architecture Overview](Architecture-Overview) -- [Database Schema](Database-Schema) -- [Form Submission Flow](Form-Submission-Flow) - -**WordPress / Backend** -- [Gutenberg Blocks](Gutenberg-Blocks) -- [Form Fields Architecture](Form-Fields-Architecture) -- [WordPress Hooks Reference](WordPress-Hooks-Reference) -- [Payment Integration](Payment-Integration) -- [Email Notifications](Email-Notifications) -- [AI Form Builder](AI-Form-Builder) -- [Data Export & Import](Data-Export-Import) -- [Using WPML With SureForms](Using-WPML-With-SureForms) - -**Frontend / React** -- [Admin Dashboard](Admin-Dashboard) -- [State Management](State-Management) -- [Frontend Assets](Frontend-Assets) -- [Block Editor Controls](Block-Editor-Controls) - -**API** -- [REST API Reference](REST-API-Reference) - -**Development** -- [Contributing Guide](Contributing-Guide) -- [Testing Guide](Testing-Guide) -- [Deployment Guide](Deployment-Guide) -- [Troubleshooting & FAQ](Troubleshooting-FAQ) -- [Changelog](Changelog) diff --git a/inc/abilities/CLAUDE.md b/inc/abilities/CLAUDE.md deleted file mode 100644 index 0a7af5e04..000000000 --- a/inc/abilities/CLAUDE.md +++ /dev/null @@ -1,66 +0,0 @@ -# Abilities — Development Guide - -## Adding a New Ability - -1. Create a class extending `Abstract_Ability` in the appropriate subdirectory: - - `forms/` — form CRUD operations - - `entries/` — entry/submission operations - - `embedding/` — shortcodes, block markup, rendering - - `settings/` — global settings read/write - - `analytics/` — form analytics and reporting - - `export/` — form and entry import/export -2. Register it in `Abilities_Registrar::register_abilities()` - -## Abstract_Ability Contract - -Every ability must implement: -- **Constructor** — set `$id`, `$label`, `$description`, `$capability` -- **`get_input_schema()`** — JSON Schema for input validation -- **`get_output_schema()`** — JSON Schema for output contract -- **`execute( $input )`** — business logic, return array or `WP_Error` -- **`get_annotations()`** — override if not default (`readonly`, `destructive`, `idempotent`) - -## Conventions - -- ID format: `sureforms/-` (e.g. `sureforms/list-forms`) -- Namespace: `SRFM\Inc\Abilities\\` -- Return `WP_Error` for failures, never throw exceptions -- Default capability: `edit_posts` — override per-ability where needed -- Destructive abilities must set `destructive: true` in annotations -- All classes need `@since x.x.x` tags -- Add PHPUnit tests in `tests/unit/inc/abilities/` for every new ability - -## Architecture - -``` -inc/abilities/ -├── abstract-ability.php # Base class — do not modify without updating all abilities -├── abilities-registrar.php # Singleton — registers category + all abilities -├── forms/ -│ ├── list-forms.php # readonly, idempotent -│ ├── create-form.php # uses Field_Mapping engine -│ ├── get-form.php # readonly, idempotent — parses Gutenberg blocks -│ ├── delete-form.php # destructive -│ ├── duplicate-form.php # delegates to \SRFM\Inc\Duplicate_Form -│ ├── update-form.php # write, idempotent — title/status/metadata -│ └── get-form-stats.php # readonly, idempotent — entry counts -├── entries/ -│ ├── list-entries.php # readonly, idempotent — paginated listing -│ ├── get-entry.php # readonly, idempotent — decrypted form_data -│ ├── update-entry-status.php # write, idempotent — read/unread/trash/restore -│ └── delete-entry.php # destructive — permanent delete -├── embedding/ -│ └── get-shortcode.php # readonly, idempotent -├── settings/ -│ ├── get-global-settings.php # readonly, idempotent — all setting categories -│ └── update-global-settings.php # write, idempotent — delegates to Global_Settings -└── analytics/ - └── get-form-analytics.php # readonly, idempotent — submission date queries -``` - -## Extensibility - -- **Filter:** `srfm_register_abilities` — third-party plugins can add abilities (must extend `Abstract_Ability`) -- **Hooks:** `srfm_before_ability_execute` / `srfm_after_ability_execute` — fire around every execution -- **Collision guard:** Registrar checks `wp_has_ability()` before registering to avoid duplicates with zipwp-mcp -- **WP < 6.9:** Registrar bails in constructor — zero overhead when Abilities API is unavailable diff --git a/internal-docs/README.md b/internal-docs/README.md deleted file mode 100644 index 9a933cae0..000000000 --- a/internal-docs/README.md +++ /dev/null @@ -1,160 +0,0 @@ -# SureForms Internal Documentation - -**Version:** 2.5.0 -**Last Updated:** February 2026 -**Target Audience:** Developers, AI Agents, Technical Team - ---- - -## Welcome - -This is the internal documentation for **SureForms Free** and **SureForms Pro** - WordPress form builder plugins used by 300,000+ websites. - -**SureForms Free:** AI-powered form builder with payments (Stripe), native WordPress blocks, 16+ fields -**SureForms Pro:** Advanced features - conversational forms, multi-step, conditional logic, PayPal, 24+ integrations - ---- - -## Quick Start - -### For New Developers (1 Hour) - -1. **Install locally:** - ```bash - cd /path/to/wordpress/wp-content/plugins - git clone https://github.com/brainstormforce/sureforms.git - git clone https://github.com/brainstormforce/sureforms-pro.git - cd sureforms && npm install && npm run build - cd ../sureforms-pro && npm install && npm run build - ``` - -2. **Activate plugins:** - - SureForms Free (required) - - SureForms Pro (optional, requires Free) - -3. **Create first form:** - - Dashboard → SureForms → Add New - - Try AI form builder or blank form - - Add fields from block inserter - - Publish and test submission - -4. **Read these docs:** - - [Architecture](architecture.md) - System design - - [Codebase Map](codebase-map.md) - Where is what - - [APIs](apis.md) - REST, AJAX, hooks - -### For AI Agents - -Start with [ai-agent-guide.md](ai-agent-guide.md) for codebase conventions, patterns, and agent-specific guidance. - ---- - -## Key Files & Directories - -| Path | Purpose | -|------|---------| -| `sureforms/inc/` | Core PHP logic | -| `sureforms/inc/blocks/` | Gutenberg block PHP | -| `sureforms/src/` | React components (admin UI) | -| `sureforms/inc/payments/` | Stripe payment processing | -| `sureforms/inc/ai-form-builder/` | AI form generation | -| `sureforms-pro/inc/business/` | Pro features (PayPal, registration, PDF) | -| `sureforms-pro/inc/pro/native-integrations/` | 24+ service integrations | - ---- - -## Build System - -**SureForms Free:** -```bash -npm run build # Full build (webpack + sass + grunt) -npm run start # Dev mode (watch) -npm run lint-js:fix # Fix JS linting -npm run makepot # Generate translation files -``` - -**SureForms Pro:** -```bash -npm run build # Full build -npm run package # Create distributable zip -``` - -Both use: -- **@wordpress/scripts** (webpack 5, Babel, ESLint) -- **Grunt** (minification, compression) -- **Sass** for CSS compilation - ---- - -## Database Tables - -Custom tables (via `inc/database/`): - -| Table | Purpose | -|-------|---------| -| `wp_sureforms_entries` | Form submissions | -| `wp_sureforms_payments` | Payment transactions (Stripe/PayPal) | -| `wp_sureforms_integrations` | Native integration credentials (encrypted) | -| `wp_sureforms_save_resume` | Draft form state (Pro) | - ---- - -## Testing - -**Unit Tests:** -```bash -composer test # PHPUnit -npm run test:unit # Jest (JS) -``` - -**E2E Tests:** -```bash -npm run play:up # Start wp-env -npm run play:run # Run Playwright tests -``` - -**Code Quality:** -```bash -composer lint # PHP_CodeSniffer -composer phpstan # Static analysis -npm run lint-js # ESLint -``` - ---- - -## Release Process - -**Free (WordPress.org):** -1. Update version in `sureforms.php` and `package.json` -2. Run `npm run build && grunt release` -3. Files excluded via `.distignore` -4. SVN commit to WordPress.org repo - -**Pro (Custom Distribution):** -1. Update version in `sureforms-pro.php` -2. Run `npm run build && npm run package` -3. Generates `sureforms-pro.zip` -4. Deploy to licensing server - ---- - -## Support & Resources - -- **GitHub (Free):** https://github.com/brainstormforce/sureforms -- **GitHub (Pro):** https://github.com/brainstormforce/sureforms-pro -- **Documentation:** https://sureforms.com/docs/ -- **Bug Bounty:** https://brainstormforce.com/bug-bounty-program/ -- **Support:** support@brainstormforce.com - ---- - -## Next Steps - -- **Product Vision:** [product-vision.md](product-vision.md) - Understand goals and personas -- **Architecture:** [architecture.md](architecture.md) - System design and data flow -- **Onboarding:** [onboarding.md](onboarding.md) - 1-hour, 1-day, 1-week learning paths - ---- - -**Maintained by:** SureForms Engineering Team -**Contact:** engineering@brainstormforce.com diff --git a/internal-docs/ai-agent-guide.md b/internal-docs/ai-agent-guide.md deleted file mode 100644 index 867f3c7ea..000000000 --- a/internal-docs/ai-agent-guide.md +++ /dev/null @@ -1,459 +0,0 @@ -# AI Agent Guide - -**Version:** 2.5.0 -**For:** AI Coding Agents & LLMs - ---- - -## Overview - -This guide helps AI agents understand SureForms codebase conventions, make safe changes, and avoid common pitfalls. - ---- - -## Core Principles - -1. **Security First:** Sanitize inputs, escape outputs, use prepared statements -2. **WordPress Standards:** Follow WP coding standards, use WP functions -3. **Backward Compatibility:** Don't break existing forms/entries -4. **Performance:** Avoid N+1 queries, cache when possible -5. **Testing:** Write tests, run existing tests before committing - ---- - -## File Modification Guidelines - -### ✅ Safe to Modify - -**Add features (low risk):** -- New blocks: `inc/blocks/new-block/` -- New integrations (Pro): `inc/pro/native-integrations/integrations/new-service/` -- New validation rules: Add to `inc/field-validation.php` -- New hooks: Add filters/actions (don't remove existing) - -**Extend existing:** -- Add sanitization functions to `inc/helper.php` -- Add validation methods to `inc/field-validation.php` -- Add utility functions to helper classes - -### ⚠️ Modify with Caution - -**Database schema changes:** -- Requires migration script -- Must handle existing data -- Version increments in `inc/database/tables/*.php` - -**Payment processing:** -- `inc/payments/stripe/stripe-webhook.php` -- `inc/business/payments/pay-pal/webhook-listener.php` -- **Risk:** Financial impact if broken - -**Form submission pipeline:** -- `inc/form-submit.php` -- **Risk:** Could break all forms - -### 🚫 High Risk - Avoid Unless Necessary - -**Core WordPress integration:** -- `plugin-loader.php` -- `sureforms.php` / `sureforms-pro.php` - -**Database base class:** -- `inc/database/base.php` (inherited by all tables) - -**Critical security:** -- Nonce verification logic -- Capability checks -- Encryption (Pro): `inc/pro/native-integrations/encryption.php` - ---- - -## Common Patterns - -### Adding a New Field Type - -1. **Create block:** - ``` - inc/blocks/my-field/block.php - src/blocks/my-field/ - ``` - -2. **Register block:** - ```php - // inc/blocks/my-field/block.php - namespace SRFM\Inc\Blocks\My_Field; - - class Block extends \SRFM\Inc\Blocks\Base { - public static function register() { - register_block_type( - 'sureforms/my-field', - [ - 'render_callback' => [__CLASS__, 'render'], - 'attributes' => [/* schema */], - ] - ); - } - - public static function render($attributes) { - // Return HTML - } - } - ``` - -3. **Add to block registry:** - ```php - // inc/blocks/register.php - My_Field\Block::register(); - ``` - -4. **Add sanitization:** - ```php - // inc/helper.php - public static function sanitize_by_field_type($type, $value) { - switch ($type) { - case 'my-field': - return sanitize_text_field($value); - } - } - ``` - -### Adding a REST Endpoint - -```php -// inc/rest-api.php or new file -public function register_routes() { - register_rest_route( - 'sureforms/v1', - '/my-endpoint', - [ - 'methods' => 'POST', - 'callback' => [$this, 'handle_request'], - 'permission_callback' => [$this, 'check_permissions'], - ] - ); -} - -public function check_permissions() { - // CRITICAL: Always verify permissions - return current_user_can('manage_options'); -} - -public function handle_request($request) { - // CRITICAL: Always verify nonce - $nonce = $request->get_header('X-WP-Nonce'); - if (!wp_verify_nonce($nonce, 'wp_rest')) { - return new \WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]); - } - - // Sanitize inputs - $data = $request->get_params(); - $clean_data = Helper::sanitize_array_recursively($data); - - // Process... - - return ['success' => true]; -} -``` - -### Adding an Integration (Pro) - -1. **Create integration folder:** - ``` - inc/pro/native-integrations/integrations/myservice/ - ├── config.json - └── actions/ - └── send-data.php - ``` - -2. **config.json:** - ```json - { - "name": "MyService", - "slug": "myservice", - "auth_method": "api_key", - "api_endpoint": "https://api.myservice.com/v1/", - "actions": [ - { - "name": "Send Data", - "slug": "send-data", - "endpoint": "contacts" - } - ] - } - ``` - -3. **actions/send-data.php:** - ```php - namespace SRFM_PRO\Inc\Pro\Native_Integrations\Integrations\Myservice\Actions; - - class Send_Data { - public function execute($integration_data, $form_data) { - $api_key = $integration_data['api_key']; - $response = wp_remote_post( - 'https://api.myservice.com/v1/contacts', - [ - 'headers' => ['Authorization' => 'Bearer ' . $api_key], - 'body' => json_encode($form_data), - ] - ); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => $response->get_error_message()]; - } - - return ['success' => true, 'response' => wp_remote_retrieve_body($response)]; - } - } - ``` - ---- - -## Security Patterns - -### Input Sanitization - -```php -// Text -$name = sanitize_text_field( wp_unslash( $_POST['name'] ?? '' ) ); - -// Email -$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); - -// Number -$id = absint( $_POST['id'] ?? 0 ); - -// Array (recursive) -$data = Helper::sanitize_array_recursively( $_POST['data'] ?? [] ); - -// Textarea -$message = Helper::sanitize_textarea( wp_unslash( $_POST['message'] ?? '' ) ); -``` - -### Output Escaping - -```php -// HTML content -echo esc_html( $user_name ); - -// Attributes -echo '
'; - -// URLs -echo ''; - -// Rich text (allows safe HTML) -echo wp_kses_post( $content ); -``` - -### Database Queries - -```php -global $wpdb; - -// ✅ Good - Prepared statement -$results = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE form_id = %d AND status = %s", - $form_id, - $status - ) -); - -// ❌ Bad - SQL injection risk -$results = $wpdb->query("DELETE FROM {$wpdb->prefix}sureforms_entries WHERE id = $_POST[id]"); -``` - ---- - -## Testing Your Changes - -### Before Committing - -```bash -# 1. Lint PHP -composer lint - -# 2. Run PHP tests -composer test - -# 3. Lint JavaScript -npm run lint-js - -# 4. Run JS tests -npm run test:unit - -# 5. Build assets -npm run build - -# 6. Test in browser -# - Create form -# - Submit form -# - Check entries -# - Verify no console errors -``` - -### E2E Testing - -```bash -# Start environment -npm run play:up - -# Run tests -npm run play:run - -# Interactive mode (with browser) -npm run play:run:interactive -``` - ---- - -## Database Queries - Common Patterns - -### Get Entries - -```php -use SRFM\Inc\Database\Tables\Entries; - -// Get all for form -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', -]); - -// With pagination -$entries = Entries::get_all([ - 'where' => [['key' => 'form_id', 'value' => 123, 'compare' => '=']], - 'limit' => 20, - 'offset' => 40 // Page 3 (20 per page) -]); - -// Count entries -$count = Entries::get_count([ - 'where' => [['key' => 'status', 'value' => 'published', 'compare' => '=']] -]); -``` - -### Get Payments - -```php -use SRFM\Inc\Database\Tables\Payments; - -// Get completed payments -$payments = Payments::get_all([ - 'where' => [ - ['key' => 'status', 'value' => 'completed', 'compare' => '='] - ] -]); - -// Get payment by transaction ID -$payment = Payments::get_by_transaction_id('ch_xxxxx', 'stripe'); -``` - ---- - -## Hooks - When to Use - -### Modify Data Before Save - -```php -// Filter entry data before save -add_filter('sureforms_submit_form_data', function($data, $form_id) { - // Add custom field - $data['custom_timestamp'] = current_time('mysql'); - return $data; -}, 10, 2); -``` - -### Trigger External Action - -```php -// Action after entry save -add_action('sureforms_after_entry_save', function($entry_id, $form_id, $data) { - // Send to external CRM - send_to_crm($data); -}, 10, 3); -``` - -### Modify Email - -```php -// Filter email template -add_filter('sureforms_email_template', function($html, $entry_id) { - // Add custom header - return '
...
' . $html; -}, 10, 2); -``` - ---- - -## Debugging Tips - -### Enable Debug Mode - -```php -// wp-config.php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -``` - -### Check Debug Log - -```bash -tail -f wp-content/debug.log -``` - -### JavaScript Console - -```javascript -// Check for errors -// Open browser DevTools → Console - -// SureForms global -console.log(window.sureforms); -``` - -### Database Queries - -```php -// Install Query Monitor plugin -// Shows all queries, slow queries, errors -``` - ---- - -## Common Pitfalls to Avoid - -1. **Don't bypass sanitization:** "It's internal, it's safe" → Still sanitize -2. **Don't skip nonce checks:** Every AJAX/REST endpoint needs verification -3. **Don't use `$_POST` directly:** Always sanitize first -4. **Don't concatenate SQL:** Use `$wpdb->prepare()` -5. **Don't echo user data raw:** Use `esc_html()`, `esc_attr()`, etc. -6. **Don't modify database schema without migration:** Breaks existing sites -7. **Don't remove existing hooks:** Other plugins may depend on them -8. **Don't hardcode `wp_` prefix:** Use `$wpdb->prefix` - ---- - -## Getting Help - -**Documentation:** -- This folder: All internal docs -- WordPress Codex: https://codex.wordpress.org/ -- React Docs: https://react.dev/ - -**Code Search:** -- Use Glob/Grep tools to find patterns -- Example: `grep -r "sanitize_text_field" inc/` → See how sanitization is used - -**Ask Before:** -- Breaking changes -- Database schema changes -- Removing existing functionality - ---- - -**Next:** [Troubleshooting](troubleshooting.md) - Common problems and solutions diff --git a/internal-docs/apis.md b/internal-docs/apis.md deleted file mode 100644 index 0a7a16ec7..000000000 --- a/internal-docs/apis.md +++ /dev/null @@ -1,418 +0,0 @@ -# APIs - -Complete API reference for SureForms Free & Pro. - ---- - -## REST API Endpoints - -### Free (`/sureforms/v1/`) - -**Form Management:** -```http -POST /sureforms/v1/generate-form -GET /sureforms/v1/forms -GET /sureforms/v1/forms/{id} -DELETE /sureforms/v1/forms/{id} -``` - -**Form Submission:** -```http -POST /sureforms/v1/submit-form -``` -**Body:** `{ form_id, nonce, field_data... }` -**Response:** `{ success, message, entry_id }` - -**Entries:** -```http -GET /sureforms/v1/entries/list -GET /sureforms/v1/entries/{id} -DELETE /sureforms/v1/entries/{id} -POST /sureforms/v1/entries/export -``` - -**Payments:** -```http -GET /sureforms/v1/payments/list -GET /sureforms/v1/payments/{id} -``` - -**Stripe Webhooks:** -```http -POST /sureforms/webhook_test # Stripe webhooks -POST /sureforms/webhook_live -``` - -**Settings:** -```http -GET /sureforms/v1/settings -POST /sureforms/v1/settings/update -``` - -### Pro (`/sureforms-pro/v1/`) - -**OAuth:** -```http -POST /sureforms-pro/v1/oauth/authorize -POST /sureforms-pro/v1/oauth/callback -``` - -**PayPal Webhooks:** -```http -POST /sureforms-pro/paypal-test-webhook -POST /sureforms-pro/paypal-live-webhook -``` - -**User Registration:** -```http -POST /sureforms-pro/v1/login -POST /sureforms-pro/v1/lost-password -POST /sureforms-pro/v1/reset-password -``` - ---- - -## AJAX Handlers - -### Free (30 handlers) - -**Admin:** -```php -'srfm_admin_action' # Generic admin handler -'srfm_get_forms' # Fetch forms list -'srfm_delete_form' # Delete form -'srfm_duplicate_form' # Duplicate form -'srfm_export_entries' # Export CSV -``` - -**Payments (Public):** -```php -'wp_ajax_srfm_create_payment_intent' # Stripe payment -'wp_ajax_nopriv_srfm_create_payment_intent' -'wp_ajax_srfm_create_subscription_intent' # Stripe subscription -'wp_ajax_nopriv_srfm_create_subscription_intent' -``` - -**Admin Stripe:** -```php -'srfm_stripe_cancel_subscription' # Cancel subscription -'srfm_stripe_pause_subscription' # Pause subscription -``` - -**Admin Payments:** -```php -'srfm_create_payment_refund' # Create refund -``` - -**Form Validation (Public):** -```php -'wp_ajax_srfm_field_unique_validation' # Check unique fields -'wp_ajax_nopriv_srfm_field_unique_validation' -``` - -### Pro (22 handlers) - -**PayPal (Public):** -```php -'wp_ajax_srfm_pro_create_paypal_order' -'wp_ajax_nopriv_srfm_pro_create_paypal_order' -'wp_ajax_srfm_pro_create_paypal_subscription' -'wp_ajax_nopriv_srfm_pro_create_paypal_subscription' -``` - -**PayPal Admin:** -```php -'srfm_pro_paypal_cancel_subscription' -'srfm_pro_paypal_pause_subscription' -``` - -**PDF:** -```php -'srfm_pro_generate_pdf' # Generate PDF from entry -'srfm_pro_download_pdf' # Download PDF -``` - -**Licensing:** -```php -'srfm_pro_activate_license' -'srfm_pro_deactivate_license' -``` - -**Entries:** -```php -'sureforms_pro_entry_delete_file' # Delete uploaded file -``` - ---- - -## WordPress Hooks - -### Actions (Form Submission) - -```php -// Before entry save -do_action('sureforms_before_entry_save', $form_id, $entry_data); - -// After entry save -do_action('sureforms_after_entry_save', $entry_id, $form_id, $entry_data); - -// After payment completed -do_action('sureforms_payment_completed', $payment_id, $entry_id, $gateway); - -// After integration success -do_action('sureforms_integration_success', $integration_type, $response); - -// Before email send -do_action('sureforms_before_email_send', $to, $subject, $message); - -// After form submission (success) -do_action('sureforms_form_submitted', $entry_id, $form_id); -``` - -### Filters (Data Modification) - -```php -// Modify form data before save -$entry_data = apply_filters('sureforms_submit_form_data', $entry_data, $form_id); - -// Modify payment amount -$amount = apply_filters('sureforms_payment_amount', $amount, $form_id, $entry_data); - -// Modify email template -$html = apply_filters('sureforms_email_template', $html, $entry_id, $form_id); - -// Modify email subject -$subject = apply_filters('sureforms_email_subject', $subject, $form_id); - -// Modify email recipients -$to = apply_filters('sureforms_email_recipients', $to, $entry_id, $form_id); - -// Modify confirmation message -$message = apply_filters('sureforms_confirmation_message', $message, $entry_id); - -// Modify field validation -$is_valid = apply_filters('sureforms_validate_field', $is_valid, $field, $value); - -// Modify sanitized value -$value = apply_filters("sureforms_sanitize_{$field_type}", $value, $field); -``` - -### Integration Hooks (Pro) - -```php -// Before integration request -do_action('sureforms_pro_before_integration', $integration, $data); - -// After integration request -do_action('sureforms_pro_after_integration', $integration, $response); - -// Integration error -do_action('sureforms_pro_integration_error', $integration, $error); -``` - ---- - -## JavaScript APIs - -### Global Objects - -```javascript -// Form settings -window.sureforms = { - ajaxUrl: '/wp-admin/admin-ajax.php', - nonce: 'xxx', - formId: 123, - settings: {...} -}; - -// Block editor -window.sureformsBlocks = { - registerBlock: (name, settings) => {...}, - getBlock: (name) => {...} -}; -``` - -### Custom Events - -```javascript -// Form submitted -document.addEventListener('sureforms-form-submitted', (e) => { - console.log('Entry ID:', e.detail.entryId); -}); - -// Payment completed -document.addEventListener('sureforms-payment-completed', (e) => { - console.log('Payment ID:', e.detail.paymentId); -}); - -// Validation error -document.addEventListener('sureforms-validation-error', (e) => { - console.log('Field:', e.detail.field, 'Error:', e.detail.message); -}); -``` - ---- - -## Database APIs - -### Entry Operations - -```php -use SRFM\Inc\Database\Tables\Entries; - -// Get all entries for a form -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', - 'limit' => 20, - 'offset' => 0 -]); - -// Get single entry -$entry = Entries::get_by_id(456); - -// Insert entry -$entry_id = Entries::insert([ - 'form_id' => 123, - 'entry_data' => json_encode($data), - 'status' => 'published' -]); - -// Update entry -Entries::update(456, ['status' => 'trash']); - -// Delete entry -Entries::delete(456); -``` - -### Payment Operations - -```php -use SRFM\Inc\Database\Tables\Payments; - -// Get payments -$payments = Payments::get_all_main_payments([ - 'where' => [ - ['key' => 'status', 'value' => 'completed', 'compare' => '='] - ] -]); - -// Create payment -$payment_id = Payments::insert([ - 'entry_id' => 456, - 'transaction_id' => 'ch_xxx', - 'gateway' => 'stripe', - 'status' => 'pending', - 'total_amount' => 99.99, - 'currency' => 'USD' -]); - -// Update payment status -Payments::update($payment_id, ['status' => 'completed']); -``` - ---- - -## Helper APIs - -### Sanitization - -```php -use SRFM\Inc\Helper; - -// Sanitize by field type -$clean = Helper::sanitize_by_field_type('email', $value); -$clean = Helper::sanitize_by_field_type('textarea', $value); -$clean = Helper::sanitize_by_field_type('number', $value); - -// Email-specific -$email = Helper::sanitize_email_header($raw_email); - -// Recursive sanitization -$clean_array = Helper::sanitize_array_recursively($array); -``` - -### Validation - -```php -use SRFM\Inc\Field_Validation; - -// Validate field -$result = Field_Validation::validate_field($field_config, $value); -// Returns: ['valid' => true/false, 'message' => 'Error message'] - -// Validate entire form -$result = Field_Validation::validate_form_data($form_id, $data); -``` - -### Payment Helpers - -```php -use SRFM\Inc\Payments\Payment_Helper; - -// Validate amount against form config -$result = Payment_Helper::validate_payment_amount($amount, $currency, $form_id, $block_id); - -// Get currency -$currency = Payment_Helper::get_currency(); -``` - ---- - -## Encryption API (Pro) - -```php -use SRFM_PRO\Inc\Pro\Native_Integrations\Encryption; - -$enc = new Encryption(); - -// Encrypt sensitive data -$encrypted = $enc->encrypt('oauth_token_12345'); - -// Decrypt -$decrypted = $enc->decrypt($encrypted); - -// Storage: wp_sureforms_integrations.data column -``` - -**Algorithm:** AES-256-CTR -**Key:** `SRFM_ENCRYPTION_KEY` constant or `LOGGED_IN_KEY` - ---- - -## Integration Workflow API (Pro) - -```php -use SRFM_PRO\Inc\Pro\Native_Integrations\Services\Workflow_Processor; - -// Execute integration workflow -$processor = new Workflow_Processor(); -$result = $processor->execute_workflow($integration_config, $entry_data); - -// Returns: ['success' => true/false, 'response' => API response] -``` - ---- - -## Rate Limiting - -**Note:** No built-in rate limiting. Implementations should add: - -```php -// Example pattern (not in core) -$key = 'form_submit_' . $user_ip; -$attempts = get_transient($key) ?: 0; - -if ($attempts >= 5) { - wp_send_json_error('Too many submissions', 429); -} - -set_transient($key, $attempts + 1, 15 * MINUTE_IN_SECONDS); -``` - ---- - -**Next:** [Coding Standards](coding-standards.md) - PHP/JS conventions diff --git a/internal-docs/architecture.md b/internal-docs/architecture.md deleted file mode 100644 index 9e59e0b81..000000000 --- a/internal-docs/architecture.md +++ /dev/null @@ -1,329 +0,0 @@ -# Architecture - -**Version:** 2.5.0 - ---- - -## System Overview - -SureForms is a WordPress form builder with a **Free + Pro** plugin architecture: - -``` -WordPress Site -├── SureForms Free (Required) -│ ├── Core form functionality -│ ├── Stripe payments -│ ├── AI form builder -│ └── Entry management -└── SureForms Pro (Optional Extension) - ├── Advanced features (conditional logic, multi-step) - ├── PayPal payments - ├── User registration/login - └── 24+ native integrations -``` - ---- - -## High-Level Architecture - -``` -┌─────────────────────────────────────────────────────────┐ -│ WordPress Admin │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ React Admin UI (src/) │ │ -│ │ - Form Builder (Gutenberg Blocks) │ │ -│ │ - Entry Dashboard │ │ -│ │ - Settings Panels │ │ -│ └──────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ REST API Layer │ -│ - /sureforms/v1/* (Free) │ -│ - /sureforms-pro/v1/* (Pro) │ -│ - AJAX handlers (wp_ajax_*) │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Business Logic (inc/) │ -│ ┌──────────┬──────────┬──────────┬──────────────────┐ │ -│ │ Forms │ Payments │ AI │ Integrations │ │ -│ │ Submit │ Stripe │ Builder │ (Pro: 24+) │ │ -│ │ Validate │ PayPal │ Field │ Mailchimp, etc │ │ -│ │ Store │ Webhooks │ Mapping │ │ │ -│ └──────────┴──────────┴──────────┴──────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Data Layer │ -│ ┌──────────────┬────────────────┬──────────────────┐ │ -│ │ WP Options │ Custom Tables │ Encrypted Data │ │ -│ │ Form configs │ Entries │ OAuth tokens │ │ -│ │ Settings │ Payments │ API keys │ │ -│ └──────────────┴────────────────┴──────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - ↕ -┌─────────────────────────────────────────────────────────┐ -│ Frontend Rendering │ -│ - Gutenberg Blocks (PHP + JS) │ -│ - Form submission (AJAX) │ -│ - reCAPTCHA/Turnstile │ -│ - Real-time validation │ -└─────────────────────────────────────────────────────────┘ -``` - ---- - -## Component Breakdown - -### 1. Block System (Gutenberg) - -**Free Blocks (16):** -- Input types: Text, Email, Number, Phone, URL, Textarea, Dropdown -- Special: Address, Checkbox, Multiple Choice, GDPR, Payment -- Layout: Separator, Heading, Image, Icon, Custom Button - -**Pro Blocks (8):** -- Input: Date Picker, Time Picker, Rating, Slider, Hidden, Password -- Advanced: File Upload, HTML, Page Break (multi-step) -- User Registration: Login, Register, Lost Password, Reset Password - -**Location:** `inc/blocks/*/block.php` and `src/blocks/*/` - -### 2. Form Submission Pipeline - -``` -User Submits Form - ↓ -1. Nonce Verification (inc/form-submit.php:91-95) - ↓ -2. Field Validation (inc/field-validation.php) - - Type validation (email, URL, number) - - Required fields - - Min/max constraints - - Custom regex - ↓ -3. Sanitization (inc/helper.php:258-274) - - Per-field type sanitization - - XSS prevention (wp_kses_post) - - SQL injection prevention - ↓ -4. CAPTCHA Validation (inc/form-submit.php:317-386) - - reCAPTCHA v2/v3 - - Cloudflare Turnstile - - hCaptcha - ↓ -5. Business Logic (Pro: Conditional Logic) - ↓ -6. Payment Processing (if enabled) - - Stripe: Create PaymentIntent - - PayPal: Create Order - ↓ -7. Entry Storage (inc/database/tables/entries.php) - ↓ -8. Integrations (Pro: Send to external services) - ↓ -9. Email Notifications (inc/email/email-template.php) - ↓ -10. Confirmation/Redirect -``` - -### 3. Payment Processing - -**Stripe (Free):** -- Location: `inc/payments/stripe/` -- Flow: - 1. Frontend creates PaymentIntent (AJAX) - 2. User completes payment on Stripe - 3. Webhook updates payment status - 4. Entry marked as paid - -**PayPal (Pro):** -- Location: `inc/business/payments/pay-pal/` -- Flow: - 1. Frontend creates Order via API - 2. User redirected to PayPal - 3. Webhook confirms payment - 4. Entry status updated - -**Database:** `wp_sureforms_payments` table - -### 4. AI Form Builder - -**Location:** `inc/ai-form-builder/` - -**Flow:** -``` -User Prompt ("Create a contact form") - ↓ -AI Middleware (SRFM_AI_MIDDLEWARE) - ↓ -Field Mapping (field-mapping.php) - ↓ -Gutenberg Block Generation - ↓ -Form Preview -``` - -**API:** `https://credits.startertemplates.com/sureforms/` - -### 5. Native Integrations (Pro) - -**Location:** `inc/pro/native-integrations/` - -**Supported Services (24+):** -- Email: Mailchimp, Brevo, Constant Contact -- CRM: HubSpot, Salesforce, Zoho -- Messaging: Telegram, Slack, Discord -- Marketing: FluentCRM, MailerPress -- Booking: LatePoint -- Automation: Zapier, OttoKit - -**Architecture:** -``` -Generic Provider Base Class - ↓ -Integration-Specific Classes -(inc/pro/native-integrations/integrations/*/actions/) - ↓ -OAuth Handler (oauth-handler.php) - ↓ -Encrypted Storage (encryption.php - AES-256-CTR) - ↓ -Workflow Processor (services/workflow-processor.php) -``` - -### 6. Database Schema - -**Custom Tables:** - -```sql --- Entries -wp_sureforms_entries -- id (INT, PK) -- form_id (INT) -- entry_data (LONGTEXT, JSON) -- created_at (DATETIME) -- updated_at (DATETIME) -- status (VARCHAR - 'published', 'trash') -- user_agent (TEXT) -- ip_address (VARCHAR) - --- Payments -wp_sureforms_payments -- id (INT, PK) -- entry_id (INT, FK) -- transaction_id (VARCHAR) -- gateway (ENUM 'stripe', 'paypal') -- status (ENUM 'pending', 'completed', 'failed', 'refunded') -- total_amount (DECIMAL) -- refunded_amount (DECIMAL) -- currency (VARCHAR) - --- Integrations (Pro) -wp_sureforms_integrations -- id (INT, PK) -- type (VARCHAR - 'mailchimp', 'hubspot', etc.) -- data (LONGTEXT, encrypted JSON) -- status (ENUM 'active', 'inactive') - --- Save & Resume (Pro) -wp_sureforms_save_resume -- id (INT, PK) -- form_id (INT) -- token (VARCHAR, unique) -- form_data (LONGTEXT, JSON) -- expires_at (DATETIME) -``` - ---- - -## Security Architecture - -**Input Validation:** -- Nonce verification on all AJAX/REST requests -- Sanitization via `sanitize_*` functions -- Type casting (absint, floatval) - -**Output Escaping:** -- `esc_html()`, `esc_attr()`, `esc_url()` throughout -- `wp_kses_post()` for rich text - -**SQL Security:** -- `$wpdb->prepare()` for all queries -- No direct SQL concatenation - -**Payment Security:** -- Webhook signature verification (Stripe, PayPal) -- Server-side amount validation -- PCI-DSS compliant (payments handled by Stripe/PayPal) - -**Encryption (Pro):** -- AES-256-CTR for OAuth tokens -- Key derivation: `SRFM_ENCRYPTION_KEY` constant or `LOGGED_IN_KEY` -- Storage: `wp_sureforms_integrations.data` (encrypted JSON) - ---- - -## Performance Optimizations - -1. **Lazy Loading:** React components loaded on-demand -2. **Asset Minification:** Grunt uglifies JS/CSS -3. **Database Indexing:** Indexes on `form_id`, `entry_id`, `status` -4. **Caching:** Transients for integration API calls -5. **Query Optimization:** `SELECT` only needed columns - ---- - -## Extensibility - -**Hooks (Filters):** -- `sureforms_submit_form_data` - Modify data before save -- `sureforms_email_template` - Customize email HTML -- `sureforms_payment_amount` - Modify payment amount - -**Hooks (Actions):** -- `sureforms_after_entry_save` - Trigger after entry created -- `sureforms_payment_completed` - After successful payment -- `sureforms_integration_success` - After integration sends data - -**Developer APIs:** -- REST: `/sureforms/v1/*` -- PHP: `SRFM\Inc\Helper` class -- JavaScript: `window.sureforms.*` globals - ---- - -## Technology Stack - -| Layer | Technologies | -|-------|-------------| -| Frontend (Admin) | React 18, @wordpress/components, TanStack Query | -| Frontend (Public) | Vanilla JS, DOMPurify, intl-tel-input | -| Backend | PHP 7.4+, WordPress 6.4+ | -| Build | webpack 5, Babel, Sass, Grunt | -| Testing | PHPUnit, Jest, Playwright | -| Database | MySQL 5.7+, MariaDB 10.3+ | - ---- - -## Deployment Architecture - -``` -Developer Machine - ↓ -Git Commit → GitHub Actions CI - ↓ -Automated Tests (PHPUnit, Playwright) - ↓ -Build Assets (npm run build) - ↓ -Free: WordPress.org SVN -Pro: Licensing Server (ZIP) - ↓ -End User WordPress Sites (300,000+) -``` - ---- - -**Next:** [Codebase Map](codebase-map.md) for detailed folder structure diff --git a/internal-docs/codebase-map.md b/internal-docs/codebase-map.md deleted file mode 100644 index 9d85000c2..000000000 --- a/internal-docs/codebase-map.md +++ /dev/null @@ -1,303 +0,0 @@ -# Codebase Map - -**Version:** 2.5.0 - -Complete folder-by-folder guide to both plugins. - ---- - -## SureForms Free (`sureforms/`) - -### Root Files - -| File | Purpose | -|------|---------| -| `sureforms.php` | Main plugin file, defines constants, loads `plugin-loader.php` | -| `plugin-loader.php` | Bootstraps plugin, registers hooks, loads dependencies | -| `composer.json` | PHP dependencies (nps-survey, bsf-analytics, astra-notices) | -| `package.json` | JS dependencies, build scripts | -| `Gruntfile.js` | Grunt tasks (minify, compress, release) | - -### `admin/` - WordPress Admin UI - -| File | Purpose | -|------|---------| -| `admin.php` | Admin page registration, AJAX handlers for admin actions | -| `analytics.php` | Usage analytics, event tracking | -| `notice-manager.php` | Admin notices, dismissible alerts | - -### `inc/` - Core PHP Logic - -#### `inc/blocks/` - Gutenberg Block Definitions (PHP) - -Each block has a `block.php` with: -- Block registration (`register_block_type`) -- Server-side rendering callback -- Attribute schema -- Enqueue scripts/styles - -**Blocks (16):** -- `address/`, `checkbox/`, `dropdown/`, `email/`, `gdpr/`, `inlinebutton/` -- `input/`, `multichoice/`, `number/`, `payment/`, `phone/`, `sform/`, `textarea/`, `url/` -- `base.php` - Base block class - -#### `inc/fields/` - Field Markup Generation - -Mirrors `blocks/` - generates HTML markup for each field type. - -**Pattern:** -```php -*-markup.php files: -- render_field() method -- Outputs escaped HTML -- Handles field attributes, validation states -``` - -#### `inc/payments/` - Payment Processing - -**Core Files:** -| File | Purpose | -|------|---------| -| `front-end.php` | Public AJAX handlers (`srfm_create_payment_intent`, `srfm_create_subscription_intent`) | -| `payment-helper.php` | Utility functions, amount validation, currency formatting | - -**`payments/stripe/`:** -| File | Purpose | -|------|---------| -| `stripe-webhook.php` | Webhook handler (charge, refund, subscription events) | -| `stripe-helper.php` | Stripe API wrapper, PaymentIntent creation | -| `admin-stripe-handler.php` | Admin AJAX for subscription management | -| `payments-settings.php` | Stripe connection settings, API keys | - -**`payments/admin/`:** -| File | Purpose | -|------|---------| -| `admin-handler.php` | Admin AJAX for refunds, payment management | - -#### `inc/ai-form-builder/` - AI Form Generation - -| File | Purpose | -|------|---------| -| `ai-form-builder.php` | Main AI form builder class, API integration | -| `ai-auth.php` | Middleware authentication | -| `ai-helper.php` | Helper functions, prompt processing | -| `field-mapping.php` | Maps AI response to Gutenberg blocks | - -**API:** `SRFM_AI_MIDDLEWARE` - `https://credits.startertemplates.com/sureforms/` - -#### `inc/database/` - Custom Database Tables - -| File | Purpose | -|------|---------| -| `base.php` | Abstract base class for all table classes (CRUD operations) | -| `register.php` | Table registration, schema creation | -| `tables/entries.php` | `wp_sureforms_entries` table (1,300+ lines) | -| `tables/payments.php` | `wp_sureforms_payments` table (1,200+ lines) | - -**Key Methods:** -- `get_all()`, `get_by_id()`, `insert()`, `update()`, `delete()` -- `get_all_main_payments()` - Payment queries with filtering - -#### Other `inc/` Files - -| File | Purpose | Lines | -|------|---------|-------| -| `form-submit.php` | Form submission handler (AJAX + REST) | 1,280 | -| `helper.php` | Utility functions (sanitization, escaping, validation) | 1,500+ | -| `field-validation.php` | Server-side field validation | 800+ | -| `rest-api.php` | REST API registration (`/sureforms/v1/*`) | 800 | -| `entries.php` | Entry management (list, view, export) | 1,000+ | -| `email/email-template.php` | Email notification templates | 400 | -| `frontend-assets.php` | Enqueue public scripts/styles | 300 | - -### `src/` - React Admin UI - -**Structure:** -``` -src/ -├── admin/ # Main admin app -│ ├── components/ # Reusable React components -│ ├── pages/ # Page-level components -│ └── index.js # Entry point -├── blocks/ # Block editor components -│ ├── address/ # Block-specific React -│ ├── email/ -│ └── ... -└── common/ # Shared utilities - ├── api.js # API client (wp.apiFetch) - └── utils.js # Helper functions -``` - -**Build:** webpack via `@wordpress/scripts` - ---- - -## SureForms Pro (`sureforms-pro/`) - -### Root Files - -| File | Purpose | -|------|---------| -| `sureforms-pro.php` | Main plugin file, requires Free plugin | -| `plugin-loader.php` | Bootstraps Pro features | - -### `inc/business/` - Pro Business Features - -#### `inc/business/payments/pay-pal/` - PayPal Integration - -| File | Purpose | Lines | -|------|---------|-------| -| `webhook-listener.php` | PayPal webhook handler (orders, refunds, subscriptions) | 1,173 | -| `frontend.php` | Public AJAX for PayPal orders, subscriptions | 1,100 | -| `api-payments.php` | PayPal API wrapper (create order, capture, refund) | 178 | -| `helper.php` | PayPal utilities, amount formatting | 520 | -| `settings.php` | PayPal connection settings | AJAX | - -#### `inc/business/user-registration/` - Login & Registration - -| File | Purpose | Lines | -|------|---------|-------| -| `init.php` | REST API registration, login/password endpoints | 600+ | -| `processor.php` | Registration processing, `wp_insert_user()` | 1,000+ | -| `login/block.php` | Login block definition | | -| `register/block.php` | Registration block | | -| `lost-password/block.php` | Lost password block | | -| `reset-password/block.php` | Reset password block | | - -**⚠️ Security Note:** Registration processor creates WordPress users from form submissions. - -#### `inc/business/pdf/` - PDF Generation - -| File | Purpose | -|------|---------| -| `pdf.php` | PDF generation API, AJAX handlers | -| `document.php` | PDF document builder (uses TCPDF or similar) | -| `utils.php` | PDF utilities | - -#### `inc/business/custom-app/` - Custom Application Builder - -| File | Purpose | -|------|---------| -| `load.php` | Custom post type forms, standalone applications | -| `utils.php` | App utilities | - -#### `inc/business/custom-post-type/` - Custom Post Type Integration - -Allows forms to create/update custom post types. - -### `inc/pro/native-integrations/` - 24+ Service Integrations - -**Core:** -| File | Purpose | Lines | -|------|---------|-------| -| `native-integrations.php` | Main integration manager | | -| `encryption.php` | AES-256-CTR encryption for OAuth tokens | 200 | -| `oauth-handler.php` | OAuth authorization flow | 400+ | -| `generic-provider.php` | Base provider class | | -| `integration-provider.php` | Provider interface | | - -**`inc/pro/native-integrations/integrations/`:** - -Each integration has its own folder: -``` -mailchimp/ -├── config.json # API endpoints, auth method -└── actions/ - ├── add-contact.php # Action: Add subscriber - └── remove-contact.php - -hubspot/ -├── config.json -└── actions/ - └── create-contact.php - -...24+ integrations -``` - -**Supported:** -- Email: Mailchimp, Brevo, Constant Contact -- CRM: HubSpot, Salesforce, Zoho -- Messaging: Telegram, Slack, Discord -- WP: FluentCRM, MailerPress, MailPoet -- Booking: LatePoint -- Automation: Zapier, OttoKit - -**Encryption:** OAuth tokens encrypted via `encryption.php` before storage in `wp_sureforms_integrations`. - -### `inc/pro/database/tables/` - Pro Database Tables - -| File | Purpose | -|------|---------| -| `integrations.php` | `wp_sureforms_integrations` table | -| `save-resume.php` | `wp_sureforms_save_resume` table (draft forms) | - -### `inc/extensions/` - Pro Extensions - -| File | Purpose | -|------|---------| -| `conditional-logic.php` | Show/hide fields based on conditions | -| `conditional-emails.php` | Conditional email notifications | -| `conditional-confirmations.php` | Conditional redirects | -| `field-validation.php` | Extended validation (file uploads, MIME types) | -| `entries-management.php` | Enhanced entry features (edit, delete files) | -| `page-break.php` | Multi-step form logic | - -### `inc/blocks/` - Pro Blocks (8) - -- `date-picker/`, `time-picker/`, `rating/`, `slider/` -- `upload/`, `hidden/`, `html/`, `page-break/` - ---- - -## Key Patterns & Conventions - -### Naming Conventions - -**Functions:** -- Public: `srfm_function_name()` -- Internal: `_srfm_internal_function()` - -**Classes:** -- Namespaced: `SRFM\Inc\ClassName` -- Pro: `SRFM_PRO\Inc\ClassName` - -**Hooks:** -- Actions: `sureforms_action_name` -- Filters: `sureforms_filter_name` - -**Database:** -- Tables: `wp_sureforms_table_name` -- Options: `sureforms_option_name` - -### File Organization - -``` -feature/ -├── block.php # Block registration -├── feature-markup.php # HTML generation -├── api-handler.php # AJAX/REST handlers -└── helper.php # Utilities -``` - -### REST API Convention - -**Free:** `/sureforms/v1/endpoint` -**Pro:** `/sureforms-pro/v1/endpoint` - ---- - -## Where to Find Things - -**Need to:** -- **Add a new field type?** → `inc/blocks/` + `inc/fields/` + `src/blocks/` -- **Modify form submission?** → `inc/form-submit.php` -- **Change email template?** → `inc/email/email-template.php` -- **Add payment gateway?** → `inc/payments/` (copy Stripe/PayPal pattern) -- **Add integration?** → `inc/pro/native-integrations/integrations/new-service/` -- **Debug entries?** → `inc/database/tables/entries.php` -- **Fix validation?** → `inc/field-validation.php` -- **Customize admin UI?** → `src/admin/` - ---- - -**Next:** [APIs](apis.md) - REST endpoints, AJAX handlers, hooks diff --git a/internal-docs/coding-standards.md b/internal-docs/coding-standards.md deleted file mode 100644 index 0b746674c..000000000 --- a/internal-docs/coding-standards.md +++ /dev/null @@ -1,312 +0,0 @@ -# Coding Standards - -**Version:** 2.5.0 - ---- - -## PHP Standards - -**WordPress Coding Standards:** https://developer.wordpress.org/coding-standards/wordpress-coding-standards/php/ - -### Code Style - -- **PSR-12 inspired** but follows WordPress conventions -- **Tabs for indentation** (not spaces) -- **Yoda conditions:** `if ( 'value' === $variable )` -- **Braces:** Required for all control structures - -```php -// ✅ Good -if ( $condition ) { - do_something(); -} - -// ❌ Bad -if ($condition) do_something(); -``` - -### Naming Conventions - -**Functions:** -```php -srfm_public_function() // Public functions -_srfm_private_function() // Internal (leading underscore) -``` - -**Classes:** -```php -namespace SRFM\Inc; -class Form_Submit { } // Underscores in class names - -namespace SRFM_PRO\Inc\Business; -class PayPal_Helper { } -``` - -**Variables:** -```php -$snake_case_variable = 'value'; // Lowercase with underscores -``` - -**Constants:** -```php -define( 'SRFM_CONSTANT', 'value' ); -``` - -### Security - -**Always:** -- Sanitize input: `sanitize_text_field()`, `absint()`, `sanitize_email()` -- Validate: Check types, ranges, allowed values -- Escape output: `esc_html()`, `esc_attr()`, `esc_url()` -- Use nonces: `wp_verify_nonce()` for AJAX/forms -- Use `$wpdb->prepare()` for SQL queries -- Check capabilities: `current_user_can()` - -**Example:** -```php -// Input -$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) ); - -// Database -$results = $wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE form_id = %d", - $form_id - ) -); - -// Output -echo '
' . esc_html( $user_name ) . '
'; -``` - -### Documentation - -**PHPDoc blocks:** -```php -/** - * Short description. - * - * Longer description if needed. - * - * @since 2.5.0 - * - * @param int $form_id Form ID. - * @param array $data Entry data. - * @return int|false Entry ID on success, false on failure. - */ -function srfm_create_entry( $form_id, $data ) { - // Implementation -} -``` - ---- - -## JavaScript Standards - -**WordPress JavaScript Coding Standards:** https://developer.wordpress.org/coding-standards/wordpress-coding-standards/javascript/ - -### Code Style (ESLint) - -**Config:** `@wordpress/eslint-plugin` - -```javascript -// ✅ Good - ES6+, const/let, arrow functions -const handleSubmit = (event) => { - event.preventDefault(); - const formData = new FormData(event.target); - // ... -}; - -// ❌ Bad - var, function expressions -var handleSubmit = function(event) { - event.preventDefault(); -}; -``` - -### React Conventions - -**Functional components with hooks:** -```jsx -import { useState, useEffect } from '@wordpress/element'; - -function FormBuilder({ formId }) { - const [fields, setFields] = useState([]); - - useEffect(() => { - fetchFields(formId).then(setFields); - }, [formId]); - - return ( -
- {fields.map(field => )} -
- ); -} -``` - -**Naming:** -- Components: `PascalCase` -- Hooks: `useCamelCase` -- Functions: `camelCase` -- Constants: `UPPER_SNAKE_CASE` - ---- - -## CSS/SCSS Standards - -**BEM-inspired naming:** -```scss -.srfm-form { - &__field { - // Field styles - } - - &__field--error { - // Error state - } -} -``` - -**Prefix:** All classes start with `srfm-` to avoid conflicts. - ---- - -## Database Standards - -### Table Names - -```php -$wpdb->prefix . 'sureforms_entries' -$wpdb->prefix . 'sureforms_payments' -``` - -**Always use `$wpdb->prefix`**, never hardcode `wp_`. - -### Queries - -**Always use prepared statements:** -```php -// ✅ Good -$wpdb->get_results( - $wpdb->prepare( - "SELECT * FROM {$wpdb->prefix}sureforms_entries WHERE id = %d", - $entry_id - ) -); - -// ❌ Bad -$wpdb->query( "DELETE FROM wp_sureforms_entries WHERE id = $entry_id" ); -``` - ---- - -## Git Commit Messages - -**Format:** -``` -type(scope): brief description - -Longer explanation if needed. - -Co-Authored-By: Name -``` - -**Types:** -- `feat`: New feature -- `fix`: Bug fix -- `docs`: Documentation -- `style`: Formatting, no code change -- `refactor`: Code restructuring -- `test`: Adding tests -- `chore`: Build/tooling - -**Example:** -``` -feat(payments): add PayPal subscription support - -- Implement PayPal billing agreement API -- Add webhook handler for subscription events -- Update admin UI for PayPal subscriptions - -Co-Authored-By: Claude Sonnet 4.5 -``` - ---- - -## File Organization - -**One class per file:** -``` -inc/payments/stripe/stripe-helper.php → class Stripe_Helper -``` - -**Group related functionality:** -``` -inc/payments/ -├── payment-helper.php # Shared utilities -├── front-end.php # Public AJAX handlers -├── stripe/ -│ ├── stripe-helper.php -│ ├── stripe-webhook.php -│ └── admin-stripe-handler.php -└── admin/ - └── admin-handler.php -``` - ---- - -## Testing Standards - -**PHPUnit:** -```php -class Test_Form_Submit extends WP_UnitTestCase { - public function test_form_submission_saves_entry() { - $entry_id = srfm_submit_form( $data ); - $this->assertIsInt( $entry_id ); - } -} -``` - -**Playwright:** -```javascript -test('form submission works', async ({ page }) => { - await page.goto('/test-form/'); - await page.fill('[name="email"]', 'test@example.com'); - await page.click('button[type="submit"]'); - await expect(page.locator('.success-message')).toBeVisible(); -}); -``` - ---- - -## Code Review Checklist - -Before submitting PR: - -- [ ] Code follows WordPress/project standards -- [ ] All inputs sanitized -- [ ] All outputs escaped -- [ ] SQL queries use `prepare()` -- [ ] Nonces verified for AJAX/forms -- [ ] Capabilities checked for admin actions -- [ ] PHPDoc blocks added -- [ ] No PHP/JS errors in console -- [ ] Tested in latest WordPress version -- [ ] Tests pass: `composer test && npm run test:unit` -- [ ] Linting passes: `composer lint && npm run lint-js` - ---- - -**Automated Checks:** - -```bash -# PHP linting -composer lint # PHP_CodeSniffer - -# JS linting -npm run lint-js # ESLint - -# Auto-fix -composer format # phpcbf -npm run lint-js:fix # ESLint --fix -``` diff --git a/internal-docs/faq.md b/internal-docs/faq.md deleted file mode 100644 index 8e174c45f..000000000 --- a/internal-docs/faq.md +++ /dev/null @@ -1,1003 +0,0 @@ -# Frequently Asked Questions - -**Version:** 2.5.0 - ---- - -## General Questions - -### What is SureForms? - -SureForms is an AI-powered WordPress form builder that uses Gutenberg blocks. - -**Key features:** -- AI form generation (describe in plain language, get complete form) -- Built-in payments (Stripe in Free, PayPal in Pro) -- Mobile-first design -- Native WordPress (no proprietary builder) -- Custom database tables (not post meta) - -**Target users:** Website owners, designers, developers who need forms without complexity. - ---- - -### What's the difference between Free and Pro? - -**Free (sureforms):** -- Unlimited forms & submissions -- All 15+ field types -- Stripe payments (one-time & subscriptions) -- Email notifications -- Spam protection (reCAPTCHA, Honeypot) -- Form analytics -- CSV export - -**Pro (sureforms-pro):** -- **All Free features, plus:** -- PayPal payments -- 24+ native integrations (Mailchimp, HubSpot, Salesforce, etc.) -- Conditional logic -- Multi-step & conversational forms -- User registration & login -- PDF generation -- File upload fields -- Priority support (< 24hr response) - -**Pricing:** -- Free: $0 (WordPress.org) -- Pro: $99-$299/year (3 sites to unlimited) - ---- - -### Can I use SureForms on multiple sites? - -**Free:** Yes, unlimited sites. - -**Pro:** Depends on license: -- Essential ($99/year): 3 sites -- Plus ($199/year): 20 sites -- Agency ($299/year): Unlimited sites - -**License activation:** -- Enter license key in SureForms → Settings → License -- Deactivate from one site to move to another -- All sites must be owned by you (not for client sites unless Agency plan) - ---- - -### Is SureForms GDPR compliant? - -**Yes.** SureForms provides tools for GDPR compliance: - -**Features:** -- GDPR checkbox field (explicit consent) -- Data export (users can request their data) -- Data deletion (users can request deletion) -- No third-party data sharing (unless you enable integrations) -- Privacy policy link support - -**What you must do:** -1. Add GDPR checkbox to forms collecting personal data -2. Link to your privacy policy -3. Honor data export/deletion requests -4. Configure data retention policies - -**Data storage:** -- Submissions stored in your WordPress database -- No data sent to SureForms servers (AI calls go through middleware but don't store data) -- Payment data stored encrypted (PCI-compliant) - ---- - -### Does SureForms work with my theme? - -**Yes.** SureForms is designed to work with any properly-coded WordPress theme. - -**Tested with:** -- Astra -- GeneratePress -- Kadence -- Neve -- Blocksy -- Twenty Twenty-Three/Four -- Page builder themes (Elementor, Divi, Beaver Builder) - -**If you experience styling conflicts:** -1. Check [troubleshooting guide](troubleshooting.md#form-breaks-after-theme-update) -2. Use SureForms → Settings → Custom CSS to override -3. Contact support with theme name - ---- - -### Can I use SureForms with page builders? - -**Yes.** SureForms works with: -- **Gutenberg** (native, best experience) -- **Elementor** (via shortcode or WordPress widget) -- **Divi** (via shortcode or code module) -- **Beaver Builder** (via WordPress widget) -- **Bricks** (via shortcode or WordPress element) - -**How to add:** - -**Elementor:** -1. Add "Shortcode" widget -2. Paste: `[sureforms id="123"]` -3. Replace 123 with your form ID - -**Divi:** -1. Add "Code" module -2. Paste shortcode -3. Save - -**Beaver Builder:** -1. Add "WordPress Widget" module -2. Select "SureForms" widget -3. Choose form - ---- - -## Technical Questions - -### What are the system requirements? - -**Minimum:** -- WordPress 6.4+ -- PHP 7.4+ -- MySQL 5.6+ or MariaDB 10.1+ -- HTTPS (for payments) - -**Recommended:** -- WordPress 6.6+ -- PHP 8.1+ -- MySQL 8.0+ or MariaDB 10.6+ -- 256MB PHP memory limit -- Object caching (Redis or Memcached) - -**Browser support:** -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - ---- - -### Does SureForms slow down my site? - -**No.** SureForms is optimized for performance: - -**Impact on page load:** -- Additional CSS: ~15KB (minified) -- Additional JS: ~25KB (minified, deferred) -- Total: ~40KB additional assets - -**Database queries:** -- Form render: 2-3 queries (cached) -- Form submission: 4-5 queries (optimized with indexes) -- No N+1 query issues - -**Performance features:** -- Lazy loading for non-critical assets -- Custom database tables (not post meta) -- Query optimization (indexed columns) -- Minified & concatenated assets -- CDN-friendly (versioned static files) - -**Benchmarks:** -- Lighthouse score: 95+ (with proper hosting) -- Time to Interactive: < 2 seconds on 3G -- Form submission latency: < 500ms - ---- - -### Can I export my data? - -**Yes.** Multiple export options: - -**Entries export:** -1. Go to SureForms → Entries -2. Select entries (or "Select All") -3. Click "Export to CSV" -4. Download file - -**Format:** CSV with all fields as columns - -**Form export:** -- Export form as JSON (Settings → Export) -- Import on another site (Settings → Import) - -**Database export:** -```bash -# Export all SureForms data -wp db export sureforms-backup.sql --tables=wp_sureforms_entries,wp_sureforms_payments,wp_sureforms_integrations -``` - ---- - -### How do I migrate forms to another site? - -**Method 1: Export/Import (Recommended)** - -**On old site:** -1. Go to SureForms → Forms -2. Hover over form → "Export" -3. Download JSON file - -**On new site:** -1. Install SureForms (same version) -2. Go to SureForms → Import -3. Upload JSON file -4. Map any dependencies (integrations, etc.) - -**Method 2: Database migration** - -```bash -# On old site -wp db export sureforms-data.sql --tables=wp_sureforms_entries,wp_sureforms_payments - -# Transfer file to new site - -# On new site -wp db import sureforms-data.sql - -# Update URLs in entries (if domain changed) -wp search-replace 'https://oldsite.com' 'https://newsite.com' wp_sureforms_entries -``` - ---- - -### Can I use SureForms offline / localhost? - -**Yes, with limitations:** - -**Works offline:** -- Form creation -- Form editing -- Local submissions (saved to database) - -**Requires internet:** -- AI form generation (external API) -- Payment processing (Stripe/PayPal API) -- Native integrations (Mailchimp, HubSpot, etc.) -- Google Fonts (if used) -- reCAPTCHA (if enabled) - -**Development setup:** -```bash -# Use Local by Flywheel or similar -# Or manual setup: -cd ~/Sites/my-wordpress-site -wp core download -wp core config --dbname=sureforms_dev --dbuser=root -wp core install --url=http://localhost:8080 --title="Dev Site" -wp plugin install sureforms --activate -``` - ---- - -## Payment Questions - -### Which payment gateways are supported? - -**Free plugin:** -- Stripe (one-time payments & subscriptions) - -**Pro plugin:** -- Stripe (one-time payments & subscriptions) -- PayPal (one-time payments & subscriptions) - -**Coming soon:** -- Razorpay (India) -- Mollie (Europe) - -**Not planned:** -- WooCommerce (use WooCommerce for full e-commerce) -- Square (low demand) -- Bitcoin/crypto (regulatory complexity) - ---- - -### How do I test payments before going live? - -**Stripe test mode:** - -1. Get test API keys: - - Go to https://dashboard.stripe.com/test/apikeys - - Copy "Publishable key" (pk_test_...) - - Copy "Secret key" (sk_test_...) - -2. In SureForms: - - Go to Settings → Payments → Stripe - - Enable "Test Mode" - - Paste test keys - - Save - -3. Test with card: - - Card number: `4242 4242 4242 4242` - - Expiry: Any future date (e.g., 12/28) - - CVC: Any 3 digits (e.g., 123) - -4. Verify in Stripe dashboard: - - Go to Payments → Test Mode - - See your test payment - -**PayPal sandbox (Pro):** - -1. Create sandbox account: - - Go to https://developer.paypal.com/ - - Sign in with PayPal account - - Create sandbox business & buyer accounts - -2. Get sandbox credentials: - - Apps & Credentials → Sandbox - - Copy Client ID & Secret - -3. In SureForms: - - Settings → Payments → PayPal - - Enable "Sandbox Mode" - - Paste credentials - - Save - -4. Test with sandbox buyer account - ---- - -### How are payment fees handled? - -**SureForms fees:** $0 (we charge nothing) - -**Gateway fees:** - -**Stripe:** -- Standard: 2.9% + $0.30 per transaction -- International cards: +1.5% -- Currency conversion: +1% -- (Varies by country, see Stripe pricing) - -**PayPal:** -- Standard: 2.9% + $0.30 per transaction -- International: +1.5% -- (Varies by country, see PayPal pricing) - -**Who pays fees:** -- Fees deducted from amount received -- Example: $100 charge → You receive ~$97 - -**Passing fees to customer:** -- Not built-in (would require payment gateway approval) -- Workaround: Add fee as separate amount field - ---- - -### Can I accept subscriptions? - -**Yes** (both Stripe and PayPal). - -**Setup:** - -1. Create form with Payment block -2. Payment block settings: - - Payment Type: ● Subscription - - Interval: Monthly/Yearly/Weekly - - Amount: [price] - -3. Customer pays once, charged automatically recurring - -**Management:** - -**Stripe:** -- View subscriptions: Stripe Dashboard → Subscriptions -- Cancel: Click subscription → "Cancel subscription" -- Refund: Not automatic, manual refund in Stripe - -**PayPal (Pro):** -- View subscriptions: PayPal Dashboard → Subscriptions -- Cancel: Click subscription → "Cancel" - -**Customer cancellation:** -- Customers can cancel in Stripe/PayPal customer portal -- You can provide link in confirmation email - ---- - -## AI & Form Generation Questions - -### How does AI form generation work? - -**Technical flow:** - -``` -User enters prompt ("job application form") - ↓ -SureForms sends to AI middleware - ↓ -Middleware calls GPT-4 API - ↓ -GPT-4 returns structured JSON (field types, labels, validations) - ↓ -Middleware sends back to SureForms - ↓ -SureForms creates Gutenberg blocks from JSON - ↓ -Form appears in editor, ready to customize -``` - -**What data is sent:** -- Your prompt ("create a contact form") -- Form context (if modifying existing form) -- No user data, no submissions, no PII - -**What data is stored:** -- None. AI responses are not logged. -- Form configuration stored in WordPress database only - -**Privacy:** -- AI calls routed through SureForms middleware (not direct to OpenAI) -- No data retention -- No training on your data - ---- - -### Can I use AI without internet? - -**No.** AI requires external API call to GPT-4. - -**Offline alternatives:** -- Create forms manually (no AI) -- Use templates (no AI needed) -- Import pre-made forms - ---- - -### What languages does AI support? - -**Prompts:** Any language GPT-4 supports (100+ languages) - -**Field labels:** Generated in same language as prompt - -**Example:** -- Prompt (Spanish): "formulario de contacto" -- Result: Fields with Spanish labels ("Nombre", "Correo electrónico", etc.) - -**Limitations:** -- Some languages better than others (English, Spanish, French, German excellent) -- Less common languages may have lower quality - ---- - -## Integration Questions (Pro) - -### What integrations are available? - -**24+ native integrations:** - -**Email Marketing:** -- Mailchimp -- Brevo (Sendinblue) -- ActiveCampaign -- ConvertKit -- MailerLite - -**CRM:** -- HubSpot -- Salesforce -- Zoho CRM -- Pipedrive - -**Communication:** -- Slack -- Telegram -- Discord (coming soon) - -**Productivity:** -- Google Sheets -- Airtable (coming soon) -- Notion (coming soon) - -**WordPress Plugins:** -- FluentCRM (native) -- OttoKit (SureTriggers) - -**Webhooks:** -- Custom webhooks (POST to any URL) - -**Not included:** -- Zapier (use webhooks instead) -- Make/Integromat (use webhooks) - ---- - -### How do I connect Mailchimp? - -**Setup (OAuth):** - -1. Go to SureForms → Settings → Integrations -2. Click "Mailchimp" → "Connect" -3. Redirected to Mailchimp → "Allow access" -4. Redirected back, connection confirmed - -**Use in form:** - -1. Edit form -2. Settings → Actions → "Add Action" -3. Select "Mailchimp" -4. Choose audience (list) -5. Map fields: - - Email field → Mailchimp EMAIL - - Name field → Mailchimp FNAME -6. Save - -**On form submission:** -- Contact added to Mailchimp list automatically -- Tags applied (if configured) -- Double opt-in email sent (if enabled in Mailchimp) - ---- - -### Can I send form data to custom webhook? - -**Yes** (Pro feature). - -**Setup:** - -1. Form settings → Actions → "Add Action" -2. Select "Webhook" -3. Configure: - - URL: `https://yourapi.com/endpoint` - - Method: POST (or GET, PUT) - - Headers: (optional, e.g., `Authorization: Bearer token`) -4. Save - -**Payload format:** - -```json -{ - "form_id": 123, - "entry_id": 456, - "fields": { - "email": "user@example.com", - "name": "John Doe", - "message": "Hello!" - }, - "meta": { - "submitted_at": "2026-02-12 10:30:00", - "user_ip": "192.168.1.1", - "user_agent": "Mozilla/5.0..." - } -} -``` - -**Retry logic:** -- Failed webhooks retried 3 times -- Exponential backoff (1s, 5s, 25s) -- After 3 failures, logged as error - ---- - -## Security & Privacy Questions - -### Is SureForms secure? - -**Yes.** Security is a core priority. - -**Security measures:** - -**Input sanitization:** -- All user input sanitized before storage -- WordPress functions: `sanitize_text_field()`, `sanitize_email()`, etc. -- Custom sanitization for complex fields - -**Output escaping:** -- All output escaped before rendering -- `esc_html()`, `esc_attr()`, `esc_url()` used throughout -- Prevents XSS attacks - -**SQL injection prevention:** -- All database queries use `$wpdb->prepare()` -- No direct SQL concatenation -- Custom database class with built-in protection - -**CSRF protection:** -- Nonce verification on all AJAX/REST endpoints -- `wp_verify_nonce()` checked before state-changing operations - -**Authorization:** -- Capability checks: `current_user_can()` -- Admin actions require `manage_options` capability -- User-specific data access controlled - -**Payment security:** -- PCI DSS compliant (no card storage) -- Stripe/PayPal handle sensitive data -- API credentials encrypted at rest (AES-256) - -**File uploads (Pro):** -- MIME type validation (server-side) -- File size limits enforced -- Path traversal prevention -- Disallowed file types: `.php`, `.js`, `.exe`, etc. - ---- - -### Where is my data stored? - -**Form submissions:** -- Your WordPress database -- Table: `wp_sureforms_entries` -- No data sent to SureForms servers - -**Payment data:** -- Stripe: Stored in Stripe (not in WordPress) -- PayPal: Stored in PayPal (not in WordPress) -- WordPress stores: Transaction ID, amount, status only - -**Integration credentials (Pro):** -- WordPress database -- Table: `wp_sureforms_integrations` -- Encrypted with AES-256 -- Encryption key in `wp-config.php` (recommended) - -**AI prompts:** -- Sent to AI middleware → OpenAI GPT-4 -- Not stored or logged -- Not used for training - ---- - -### Can users delete their data? - -**Yes** (GDPR requirement). - -**Manual deletion:** - -1. User requests deletion via email -2. Admin finds submissions by email -3. Delete from SureForms → Entries - -**Automatic (Pro):** -- Data retention policy: Auto-delete entries after X days -- Settings → Privacy → Data Retention -- Configure per form or globally - -**What gets deleted:** -- Entry data (all fields) -- Entry metadata (IP, user agent) -- File uploads (if any) - -**What's preserved:** -- Form configuration (for re-use) -- Analytics (anonymized counts only) - ---- - -## Troubleshooting Questions - -### My forms aren't showing on the frontend. Why? - -**Common causes:** - -**1. Form not published:** -- Check form status in SureForms → Forms -- Must be "Published" not "Draft" - -**2. Form not embedded:** -- Add SureForms block to page -- Select your form from dropdown -- Or use shortcode: `[sureforms id="123"]` - -**3. Theme conflict:** -- Try default theme: `wp theme activate twentytwentythree` -- If works → Theme issue, contact theme developer - -**4. JavaScript disabled:** -- Check browser console for errors -- Disable other plugins to find conflict - -**Debug:** -```bash -# Enable debugging -wp config set WP_DEBUG true --raw -wp config set WP_DEBUG_LOG true --raw - -# Check debug log -tail -f wp-content/debug.log -``` - -See [troubleshooting guide](troubleshooting.md) for more. - ---- - -### Form submissions aren't saving. What's wrong? - -**Check:** - -**1. Database tables exist:** -```bash -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - -Should show `wp_sureforms_entries` table. - -**2. Database permissions:** -```bash -wp db query "SHOW GRANTS;" -``` - -User must have INSERT privilege. - -**3. Nonce verification:** -- If using caching, exclude AJAX endpoints -- WP Super Cache: Exclude `/wp-admin/admin-ajax.php` - -**4. Debug log:** -```bash -tail -f wp-content/debug.log -# Submit form, watch for errors -``` - -See [troubleshooting: submissions not saving](troubleshooting.md#form-submissions-not-saving) - ---- - -### Email notifications aren't sending. Help! - -**Diagnose:** - -```bash -# Test WordPress mail function -wp eval "wp_mail('your@email.com', 'Test', 'Testing');" -``` - -If no email received → Server mail() disabled. - -**Fix:** - -**Install SMTP plugin:** -```bash -wp plugin install wp-mail-smtp --activate -``` - -**Configure with:** -- Gmail -- SendGrid -- Mailgun -- Amazon SES - -**Check spam folder:** -- Default WordPress sender: `wordpress@yourdomain.com` -- Often flagged as spam -- SMTP fixes this - -See [troubleshooting: emails not sending](troubleshooting.md#email-notifications-not-sending) - ---- - -## Customization Questions - -### Can I customize form styling? - -**Yes.** Multiple options: - -**Option 1: Settings panel (no code):** -1. Edit form -2. Settings sidebar → Design -3. Customize: - - Colors (background, text, buttons) - - Typography (fonts, sizes) - - Spacing (padding, margins) - - Border radius - -**Option 2: Custom CSS:** -1. SureForms → Settings → Custom CSS -2. Add your styles: -```css -.srfm-form { - background: #f9f9f9; - padding: 30px; - border-radius: 8px; -} - -.srfm-form input { - border: 2px solid #333; -} -``` - -**Option 3: Theme stylesheet:** - -Add to `style.css`: -```css -.srfm-form { /* Your styles */ } -``` - -**Option 4: Page builder:** - -Use page builder's CSS editor (if using Elementor/Divi). - ---- - -### Can I add custom fields? - -**Yes** (requires development). - -**Process:** - -1. Create block folder: `inc/blocks/my-field/` -2. Register block: `block.php` -3. Create React component: `src/blocks/my-field/edit.js` -4. Add sanitization: `inc/helper.php` -5. Add validation: `inc/field-validation.php` - -**Example:** See [onboarding guide](onboarding.md#step-2-create-block-structure) - -**Pre-built custom fields (Pro):** -- Star rating -- Signature -- File upload -- Date/time picker - ---- - -### Can I change the submit button text? - -**Yes.** - -**Per form:** -1. Edit form -2. Click submit button block -3. Settings sidebar → Button Text -4. Change text -5. Update form - -**Globally:** - -Add to `functions.php`: -```php -add_filter('sureforms_submit_button_text', function($text) { - return __('Send Message', 'sureforms'); -}); -``` - ---- - -## Business & Licensing Questions - -### Can I use SureForms on client sites? - -**Free:** Yes, unlimited sites. - -**Pro:** -- **Agency license required** ($299/year, unlimited sites) -- Essential ($99/year, 3 sites) - only for your own sites -- Plus ($199/year, 20 sites) - only for your own sites - -**License terms:** -- You must own the sites (not client-owned sites with Essential/Plus) -- Agency license allows client sites -- Each site needs separate activation - ---- - -### Can I white-label SureForms? - -**Not officially.** - -**What you can do:** -- Remove branding from admin (CSS) -- Custom email footer (replace "Powered by SureForms") -- Custom confirmation messages - -**What you can't do:** -- Remove copyright notices from code -- Rebrand and resell as your own product -- Remove attribution from free plugin - -**Enterprise white-label:** -- Contact sales for custom licensing -- Available for high-volume agencies - ---- - -### What's your refund policy? - -**14-day money-back guarantee** (Pro licenses) - -**Refund eligibility:** -- Purchased within last 14 days -- Tried to use but encountered issues -- Support unable to resolve - -**Not eligible:** -- After 14 days -- Changed mind (not technical issue) -- Purchased wrong license (can upgrade instead) - -**Process:** -1. Contact support: support@sureforms.com -2. Describe issue (we'll try to help first) -3. If unresolved, refund processed within 3-5 business days - ---- - -### How long do I get updates? - -**Free:** Lifetime updates. - -**Pro:** -- Updates: For duration of license (1 year) -- Renewals: Discounted renewal price after year 1 -- If license expires: Plugin continues working, no new updates - -**Version compatibility:** -- Major updates: Included -- Security updates: Prioritized -- Feature updates: Based on roadmap - ---- - -## Developer Questions - -### Is the code open source? - -**Free plugin:** Yes. -- License: GPL v2 or later -- GitHub: https://github.com/brainstormforce/sureforms - -**Pro plugin:** No. -- Proprietary license -- Source code visible but not redistributable - ---- - -### Can I contribute to SureForms? - -**Yes!** We welcome contributions. - -**How to contribute:** - -1. Fork repo: https://github.com/brainstormforce/sureforms -2. Create branch: `git checkout -b feat/my-feature` -3. Make changes -4. Run tests: `composer test && npm run test:unit` -5. Submit PR - -**Contribution guidelines:** -- Follow [coding standards](coding-standards.md) -- Add tests for new features -- Update docs if needed -- Sign off commits: `Co-Authored-By: Your Name ` - ---- - -### Where can I find the API documentation? - -**API docs:** [apis.md](apis.md) - -**Key resources:** -- REST endpoints: [apis.md#rest-api](apis.md#rest-api) -- Hooks & filters: [apis.md#hooks--filters](apis.md#hooks--filters) -- JavaScript APIs: [apis.md#javascript-apis](apis.md#javascript-apis) -- Database APIs: [apis.md#database-apis](apis.md#database-apis) - -**Code examples:** [ai-agent-guide.md](ai-agent-guide.md) - ---- - -## Need More Help? - -**Documentation:** -- [README.md](README.md) - Quick start -- [Troubleshooting](troubleshooting.md) - Common problems -- [Onboarding](onboarding.md) - Developer guide -- [Glossary](glossary.md) - Technical terms - -**Support:** -- **Free users:** WordPress.org forum, GitHub issues -- **Pro users:** https://support.brainstormforce.com/ (< 24hr response) - -**Community:** -- Facebook: https://www.facebook.com/groups/surecart -- GitHub Discussions: https://github.com/brainstormforce/sureforms/discussions - ---- - -**Next:** [Glossary](glossary.md) diff --git a/internal-docs/glossary.md b/internal-docs/glossary.md deleted file mode 100644 index 440e34b70..000000000 --- a/internal-docs/glossary.md +++ /dev/null @@ -1,723 +0,0 @@ -# Glossary - -**Version:** 2.5.0 - ---- - -## A - -### AJAX (Asynchronous JavaScript and XML) -Technique for updating parts of a page without full reload. Used in SureForms for form submissions, payment processing, and file uploads. - -**Example:** -```javascript -// AJAX request to submit form -wp.ajax.post('srfm_submit_form', { data: formData }); -``` - -### API (Application Programming Interface) -Set of functions and methods for interacting with software. SureForms provides REST API, database API, and JavaScript API. - -**Types in SureForms:** -- **REST API:** HTTP endpoints for external access -- **Database API:** Methods to query entries/payments -- **JavaScript API:** Frontend form manipulation - -### Attribute (Gutenberg) -Data stored with a block. In SureForms, attributes define field configuration (label, required, placeholder, etc.). - -**Example:** -```json -{ - "label": "Email", - "required": true, - "placeholder": "name@example.com" -} -``` - ---- - -## B - -### Block (Gutenberg) -Reusable component in WordPress editor. SureForms forms are composed of blocks (Email block, Textarea block, Submit button block, etc.). - -**Types:** -- **Static blocks:** Rendered client-side (React) -- **Dynamic blocks:** Rendered server-side (PHP) - -### Block Editor -WordPress's editing interface (formerly called "Gutenberg"). Native to WordPress 5.0+. - -**Why SureForms uses it:** -- Users already familiar -- No proprietary builder to learn -- Future-proof (WordPress core) - ---- - -## C - -### Capability -WordPress permission level. SureForms checks capabilities before allowing admin actions. - -**Common capabilities:** -- `manage_options` - Admin settings access -- `edit_posts` - Create forms (default) -- `read` - View forms (logged-in users) - -**Example:** -```php -if (current_user_can('manage_options')) { - // Allow admin action -} -``` - -### Conditional Logic (Pro) -Show or hide form fields based on user answers. Example: Show "Dietary restrictions" only if user selects "Yes" to "Will you attend?" - -**Types:** -- **Show when:** Display field if condition matches -- **Hide when:** Hide field if condition matches -- **AND/OR logic:** Multiple conditions combined - -### Conversational Form (Pro) -Form that displays one question at a time, like a chat conversation. Increases engagement and completion rates. - -**Benefits:** -- Less overwhelming than long forms -- Higher completion rate (3x vs single-page) -- Better mobile experience - -### CSRF (Cross-Site Request Forgery) -Attack where malicious site tricks user into submitting request to your site. SureForms prevents with nonce verification. - -**Example attack (prevented):** -```html - - - - - - -``` - -### CSV (Comma-Separated Values) -File format for spreadsheet data. SureForms can export form entries as CSV. - -**Example:** -``` -Name,Email,Message -John Doe,john@example.com,Hello! -Jane Smith,jane@example.com,Question about... -``` - ---- - -## D - -### Database Table -MySQL table storing structured data. SureForms uses 4 custom tables: -- `wp_sureforms_entries` - Form submissions -- `wp_sureforms_payments` - Payment records -- `wp_sureforms_integrations` - OAuth credentials -- `wp_sureforms_save_resume` - Draft submissions (Pro) - -**Why custom tables?** -- Faster queries (indexed columns) -- More efficient than post meta -- Easier to export - -### Dynamic Block -Gutenberg block rendered server-side (PHP). Most SureForms field blocks are dynamic for security (server-side validation). - -**Example:** -```php -register_block_type('sureforms/email', [ - 'render_callback' => 'render_email_field' -]); -``` - ---- - -## E - -### Encryption -Converting data to unreadable format. SureForms encrypts OAuth credentials and API keys. - -**Algorithm:** AES-256-CTR - -**Example:** -```php -$encrypted = Encryption::encrypt($api_key); -// Stored in database: "base64encodedIV.encryptedData" -``` - -### Entry -Single form submission. Stored in `wp_sureforms_entries` table. - -**Contains:** -- Form ID -- User ID (if logged in) -- Field values (JSON) -- Metadata (IP, user agent, timestamp) -- Status (published, spam, trash) - -### Escaping (Output Escaping) -Converting special characters to prevent XSS. SureForms escapes all user data before display. - -**Functions:** -- `esc_html()` - For HTML content -- `esc_attr()` - For HTML attributes -- `esc_url()` - For URLs -- `esc_js()` - For JavaScript strings - -**Example:** -```php -echo '
' . esc_html($user_input) . '
'; -``` - ---- - -## F - -### Field Type -Type of input field (email, text, number, etc.). SureForms has 15+ field types. - -**Common types:** -- Text, Email, URL, Textarea -- Number, Phone -- Multiple Choice, Checkbox, Dropdown -- Address, Upload (Pro) - -### Filter (WordPress Hook) -Function that modifies data. SureForms provides 30+ filters for customization. - -**Example:** -```php -add_filter('sureforms_email_subject', function($subject, $form_id) { - return "[Form $form_id] $subject"; -}, 10, 2); -``` - ---- - -## G - -### GDPR (General Data Protection Regulation) -European privacy law. SureForms provides GDPR-compliance features (consent checkbox, data export/deletion). - -**Requirements:** -- Explicit consent for data collection -- Right to access data (export) -- Right to deletion -- Privacy policy link - -### Gutenberg -WordPress's block editor (officially called "Block Editor"). Named after Johannes Gutenberg, inventor of printing press. - -**Why "Gutenberg"?** -- Revolutionized content creation (like printing press) -- Blocks are reusable components - ---- - -## H - -### Honeypot -Anti-spam technique using hidden field. Bots fill all fields (including hidden), revealing themselves. - -**How it works:** -```html - - -``` - -**SureForms logic:** -```php -if (!empty($_POST['srfm-honeypot'])) { - // Spam detected -} -``` - -### Hook (WordPress) -Point where developers can inject custom code. Two types: actions and filters. - -**SureForms hooks:** -- 50+ action hooks (e.g., `sureforms_after_entry_save`) -- 30+ filter hooks (e.g., `sureforms_email_template`) - ---- - -## I - -### Integration (Native Integration) -Connection to third-party service. SureForms Pro has 24+ native integrations (Mailchimp, HubSpot, etc.). - -**Types:** -- **OAuth:** User authorizes connection (Mailchimp, Google) -- **API Key:** User provides key from service -- **Webhook:** SureForms sends POST request to URL - -### Instant Form -SureForms feature allowing forms to be published with unique URL (no embedding needed). - -**Example URL:** -``` -https://yoursite.com/?srfm_form=abc123 -``` - -**Use cases:** -- Email signatures -- Social media links -- QR codes - ---- - -## J - -### JSON (JavaScript Object Notation) -Data format for structured information. SureForms stores entry data and block attributes as JSON. - -**Example entry data:** -```json -{ - "email": "john@example.com", - "name": "John Doe", - "message": "Hello!" -} -``` - ---- - -## L - -### Localization (l10n) -Adapting software for specific language/region. SureForms supports translation. - -**Files:** -- `languages/sureforms-{locale}.po` - Translation strings -- `languages/sureforms-{locale}.mo` - Compiled translations - -**Functions:** -```php -__('Email', 'sureforms'); // Translate string -_e('Submit', 'sureforms'); // Translate and echo -``` - ---- - -## M - -### Middleware -Intermediate server between client and API. SureForms uses middleware for AI form generation (client → middleware → OpenAI). - -**Why middleware?** -- Hides API keys from client -- Rate limiting -- Request logging -- Caching - -### Multi-Step Form (Pro) -Form split into multiple pages/steps. Improves completion rate by reducing cognitive load. - -**Example:** -``` -Step 1: Personal Information (name, email) -Step 2: Address Details -Step 3: Payment Information -``` - ---- - -## N - -### Namespace -PHP feature to organize code and prevent name conflicts. SureForms uses namespaces. - -**Example:** -```php -namespace SRFM\Inc\Database\Tables; - -class Entries { - // ... -} -``` - -### Native Integration -Integration built into SureForms (not requiring Zapier or external connector). - -**Examples:** -- Mailchimp (OAuth) -- HubSpot (OAuth) -- Slack (Webhook URL) - -### Nonce (Number Used Once) -Security token to verify request came from your site. SureForms verifies nonces on all AJAX/REST requests. - -**Example:** -```php -// Generate nonce -$nonce = wp_create_nonce('srfm_submit_form'); - -// Verify nonce -if (!wp_verify_nonce($_POST['nonce'], 'srfm_submit_form')) { - die('Invalid nonce'); -} -``` - ---- - -## O - -### OAuth (Open Authorization) -Protocol for secure authorization. Used by SureForms integrations (Mailchimp, HubSpot, etc.). - -**Flow:** -1. User clicks "Connect to Mailchimp" -2. Redirected to Mailchimp login -3. User approves access -4. Mailchimp redirects back with token -5. SureForms stores encrypted token - ---- - -## P - -### Payment Gateway -Service that processes credit card payments. SureForms supports Stripe and PayPal. - -**Comparison:** -- **Stripe:** 2.9% + $0.30, better for US/Europe -- **PayPal:** 2.9% + $0.30, more global recognition - -### Payment Intent (Stripe) -Stripe object representing payment attempt. Created before customer enters card details. - -**States:** -- `requires_payment_method` - Awaiting card -- `requires_confirmation` - Card entered, needs confirm -- `succeeded` - Payment successful -- `canceled` - Payment canceled - -### PCI DSS (Payment Card Industry Data Security Standard) -Security standard for handling credit card data. SureForms is PCI-compliant (never stores card data). - -**Compliance:** -- Card data sent directly to Stripe/PayPal -- SureForms only stores transaction ID -- No PAN (Primary Account Number) stored - -### Placeholder -Example text shown in empty input field. Disappears when user types. - -**Example:** -```html - -``` - -**Accessibility note:** Never use placeholder as label (screen readers may miss it). - -### Post Meta -WordPress method for storing additional data with posts. SureForms **doesn't** use post meta (uses custom tables instead). - -**Why not post meta?** -- Slow queries (no indexes) -- Hard to search -- Not optimized for large datasets - -### Post Type (Custom Post Type) -WordPress content type. SureForms forms are stored as custom post type `sureforms_form`. - -**Advantages:** -- Uses WordPress admin UI -- Revision history -- Trash/restore functionality - ---- - -## R - -### reCAPTCHA -Google anti-spam service. SureForms supports reCAPTCHA v2 and v3. - -**Versions:** -- **v2:** "I'm not a robot" checkbox -- **v3:** Invisible, scores user behavior (0-1) - -**Setup:** -1. Get API keys from Google reCAPTCHA admin -2. Add to SureForms → Settings → Security -3. Enable on forms - -### REST API -HTTP API for accessing SureForms data. Used by frontend, mobile apps, integrations. - -**Base URL:** `/wp-json/sureforms/v1/` - -**Example endpoints:** -- `POST /submit-form` - Submit form -- `GET /forms/{id}` - Get form data -- `POST /generate-form` - AI form generation - -### RTL (Right-to-Left) -Text direction for languages like Arabic, Hebrew. SureForms supports RTL. - -**CSS:** -```css -.srfm-field { - margin-inline-start: 10px; /* Not margin-left */ -} -``` - ---- - -## S - -### Sanitization (Input Sanitization) -Cleaning user input to prevent attacks. SureForms sanitizes all input before storage. - -**Functions:** -- `sanitize_text_field()` - Remove HTML/PHP -- `sanitize_email()` - Validate email format -- `sanitize_url()` - Validate URL format -- `absint()` - Force integer - -**Example:** -```php -$email = sanitize_email($_POST['email']); -$id = absint($_POST['id']); -``` - -### Schema (Database Schema) -Structure of database table (columns, types, indexes). - -**Example (wp_sureforms_entries):** -```sql -CREATE TABLE wp_sureforms_entries ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - form_id BIGINT UNSIGNED NOT NULL, - user_id BIGINT UNSIGNED, - entry_data LONGTEXT NOT NULL, - status VARCHAR(20) DEFAULT 'published', - created_at DATETIME NOT NULL, - INDEX idx_form_id (form_id), - INDEX idx_created_at (created_at) -); -``` - -### Shortcode -WordPress feature for embedding content. SureForms forms can be embedded via shortcode. - -**Syntax:** -``` -[sureforms id="123"] -``` - -**Parameters:** -- `id` - Form ID (required) -- `title` - Show title (true/false) -- `description` - Show description (true/false) - -### SQL Injection -Attack injecting malicious SQL into queries. SureForms prevents with `$wpdb->prepare()`. - -**Vulnerable code (DON'T DO THIS):** -```php -$id = $_GET['id']; -$wpdb->query("DELETE FROM table WHERE id = $id"); -// Attack: ?id=1 OR 1=1 (deletes all records!) -``` - -**Safe code:** -```php -$id = absint($_GET['id']); -$wpdb->query($wpdb->prepare("DELETE FROM table WHERE id = %d", $id)); -``` - -### Subscription -Recurring payment. SureForms supports subscriptions via Stripe and PayPal. - -**Intervals:** -- Daily -- Weekly -- Monthly -- Yearly - ---- - -## T - -### Template (Form Template) -Pre-built form configuration. SureForms provides 100+ templates (contact, registration, survey, etc.). - -**Structure:** -```json -{ - "name": "Contact Form", - "fields": [ - { "type": "text", "label": "Name" }, - { "type": "email", "label": "Email" }, - { "type": "textarea", "label": "Message" } - ] -} -``` - -### Transient -WordPress temporary cache. SureForms uses transients for AI rate limiting and API responses. - -**Example:** -```php -// Store for 1 hour -set_transient('srfm_ai_rate_limit_' . $user_id, true, HOUR_IN_SECONDS); - -// Check -if (get_transient('srfm_ai_rate_limit_' . $user_id)) { - // Rate limited -} -``` - ---- - -## U - -### User Agent -String identifying browser/device. SureForms logs user agent for spam detection. - -**Example:** -``` -Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 -``` - ---- - -## V - -### Validation (Form Validation) -Checking if user input meets requirements. SureForms validates both client-side (JavaScript) and server-side (PHP). - -**Types:** -- **Required:** Field must not be empty -- **Format:** Email/URL must be valid -- **Range:** Number within min/max -- **Length:** String length constraints - -**Example:** -```php -if (empty($email) || !is_email($email)) { - $errors[] = 'Please enter a valid email'; -} -``` - ---- - -## W - -### Webhook -HTTP callback sent when event occurs. SureForms can send webhooks on form submission (Pro). - -**Flow:** -``` -User submits form - ↓ -SureForms processes submission - ↓ -Sends POST to webhook URL - ↓ -External service receives data -``` - -**Example payload:** -```json -{ - "event": "form_submit", - "form_id": 123, - "entry_id": 456, - "fields": { ... } -} -``` - -### WordPress Coding Standards (WPCS) -PHP coding style guide for WordPress. SureForms follows WPCS. - -**Key rules:** -- Tabs for indentation (not spaces) -- Yoda conditions: `if ( 'value' === $variable )` -- Braces required for all control structures -- Space after control keywords: `if (` not `if(` - -**Check compliance:** -```bash -composer lint -``` - ---- - -## X - -### XSS (Cross-Site Scripting) -Attack injecting malicious JavaScript into pages. SureForms prevents with output escaping. - -**Vulnerable code (DON'T DO THIS):** -```php -echo '
' . $_POST['name'] . '
'; -// Attack: -``` - -**Safe code:** -```php -echo '
' . esc_html($_POST['name']) . '
'; -// Output: <script>steal(document.cookie)</script> -``` - ---- - -## Z - -### Zapier -Third-party automation service. SureForms doesn't have native Zapier integration but supports webhooks (which Zapier can consume). - -**Alternative:** Use SureForms webhooks to trigger Zapier zaps. - ---- - -## Acronyms Quick Reference - -| Acronym | Full Term | Meaning | -|---------|-----------|---------| -| AJAX | Asynchronous JavaScript and XML | Update page without reload | -| API | Application Programming Interface | Methods to interact with software | -| CSRF | Cross-Site Request Forgery | Attack tricking users into unwanted actions | -| CSV | Comma-Separated Values | Spreadsheet file format | -| GDPR | General Data Protection Regulation | EU privacy law | -| JSON | JavaScript Object Notation | Data format | -| OAuth | Open Authorization | Secure authorization protocol | -| PCI DSS | Payment Card Industry Data Security Standard | Credit card security | -| REST | Representational State Transfer | Web API architecture | -| RTL | Right-to-Left | Text direction (Arabic, Hebrew) | -| SQL | Structured Query Language | Database query language | -| URL | Uniform Resource Locator | Web address | -| WCAG | Web Content Accessibility Guidelines | Accessibility standards | -| WPCS | WordPress Coding Standards | PHP coding style | -| XSS | Cross-Site Scripting | JavaScript injection attack | - ---- - -## Common Abbreviations - -| Abbr | Full Term | -|------|-----------| -| Pro | SureForms Pro (premium plugin) | -| CRM | Customer Relationship Management | -| UI | User Interface | -| UX | User Experience | -| CPT | Custom Post Type | -| DB | Database | -| WP | WordPress | -| PHP | PHP: Hypertext Preprocessor | -| JS | JavaScript | -| CSS | Cascading Style Sheets | - ---- - -**Next:** [Maintenance](maintenance.md) diff --git a/internal-docs/maintenance.md b/internal-docs/maintenance.md deleted file mode 100644 index 71c2f2604..000000000 --- a/internal-docs/maintenance.md +++ /dev/null @@ -1,842 +0,0 @@ -# Documentation Maintenance Guide - -**Version:** 2.5.0 - ---- - -## Purpose - -This guide explains how to keep SureForms internal documentation accurate and useful. - -**Goal:** Documentation should always reflect current reality. - -**Principle:** Update docs when code changes, not as afterthought. - ---- - -## When to Update Documentation - -### Always Update - -**1. New Feature Added** -- Update: `architecture.md`, `apis.md`, `product-vision.md` -- Add: Code examples to `ai-agent-guide.md` -- Update: Onboarding guide if workflow changes - -**2. API Changes** -- Update: `apis.md` (new endpoints, hooks, parameters) -- Update: Code examples in all docs referencing changed API -- Mark deprecated APIs in `apis.md` - -**3. Database Schema Changes** -- Update: `architecture.md` (database section) -- Update: `codebase-map.md` (new files) -- Update: Migration guide if schema change requires migration - -**4. Security Fix** -- Update: `ai-agent-guide.md` (add to common pitfalls) -- Update: `troubleshooting.md` if fix addresses known issue -- Add example of vulnerable pattern to avoid - -**5. Breaking Changes** -- Update: `README.md` (migration guide) -- Update: `onboarding.md` (new workflows) -- Update: `faq.md` (address upgrade questions) - ---- - -### Update if Needed - -**6. Bug Fixes** -- Update: `troubleshooting.md` if common issue -- Add: To FAQ if frequently asked - -**7. Performance Improvements** -- Update: `architecture.md` if architecture changed -- Update: Benchmarks in `README.md` - -**8. UI/UX Changes** -- Update: `ui-and-copy.md` (new patterns, copy) -- Update: `onboarding.md` (user journeys) - ---- - -### Don't Update - -**9. Code Refactoring (No Behavior Change)** -- No doc update unless internal architecture significantly changed - -**10. Minor Copy Changes** -- No doc update unless it changes UX patterns - -**11. Version Bumps** -- Update version number at top of each doc -- That's it (unless other changes) - ---- - -## How to Update Documentation - -### Step 1: Identify Affected Docs - -**Use this decision tree:** - -``` -Did you change... - -┌─ Database schema? -│ └─> Update: architecture.md, codebase-map.md -│ -┌─ REST API? -│ └─> Update: apis.md, ai-agent-guide.md (examples) -│ -┌─ AJAX handlers? -│ └─> Update: apis.md -│ -┌─ Hooks/filters? -│ └─> Update: apis.md, ai-agent-guide.md (patterns) -│ -┌─ User-facing UI? -│ └─> Update: ui-and-copy.md, onboarding.md -│ -┌─ Security patterns? -│ └─> Update: ai-agent-guide.md, coding-standards.md -│ -┌─ Build process? -│ └─> Update: README.md, onboarding.md -│ -└─ New file/folder? - └─> Update: codebase-map.md -``` - ---- - -### Step 2: Read Existing Doc - -**Before editing:** -1. Read entire document -2. Identify outdated sections -3. Check for consistency with your changes - -**Don't:** -- Blindly add new section without reading context -- Create duplicate information -- Use different terminology than existing docs - ---- - -### Step 3: Make Minimal Changes - -**Best practices:** - -**Do:** -- Update only what changed -- Keep existing structure -- Match existing tone and style -- Add code examples if helpful - -**Don't:** -- Rewrite entire document (unless necessary) -- Change unrelated sections -- Reorganize without discussion -- Remove information (mark deprecated instead) - ---- - -### Step 4: Verify Examples Still Work - -**All code examples must be tested:** - -**PHP examples:** -```bash -# Create test file -cat > /tmp/test-example.php << 'EOF' - [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'limit' => 20 -]); -``` - -**❌ Bad:** -```php -// Vague, generic -$data = SomeClass::get_stuff($args); -``` - ---- - -### Cross-Referencing - -**Internal links:** -```markdown -See [Architecture Guide](architecture.md) for details. -See [Database Schema](architecture.md#database-schema) for table structure. -``` - -**External links:** -```markdown -Read [WordPress Coding Standards](https://developer.wordpress.org/coding-standards/). -``` - -**Code references:** -```markdown -See `inc/form-submit.php:88-119` for nonce verification. -``` - ---- - -### Code Block Guidelines - -**Always specify language:** - -````markdown -```php -// PHP code here -``` - -```javascript -// JavaScript here -``` - -```bash -# Shell commands here -``` - -```sql --- SQL queries here -``` -```` - -**Include comments:** -```php -// Good: Explain what code does -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'status', 'value' => 'published', 'compare' => '='] - ] -]); -``` - -**Show output if helpful:** -```bash -wp plugin list --status=active -# Output: -# sureforms active -# sureforms-pro active -``` - ---- - -## Deprecation Process - -**When removing features:** - -### Step 1: Mark as Deprecated - -**In code:** -```php -/** - * Old function. - * - * @deprecated 2.5.0 Use new_function() instead. - */ -function old_function() { - _deprecated_function(__FUNCTION__, '2.5.0', 'new_function'); - return new_function(); -} -``` - -**In docs:** -```markdown -## ~~Old Feature~~ (Deprecated) - -**Deprecated in:** 2.5.0 -**Removed in:** 3.0.0 -**Replacement:** [New Feature](#new-feature) - -This feature is deprecated and will be removed in version 3.0.0. -Use [New Feature](#new-feature) instead. - -~~Old documentation here...~~ -``` - ---- - -### Step 2: Update Migration Guide - -**In README.md:** - -```markdown -## Upgrading from 2.4.x to 2.5.0 - -### Breaking Changes - -**Old Feature Deprecated:** -- **What changed:** `old_function()` is now deprecated -- **Action required:** Replace with `new_function()` -- **Code example:** - ```php - // Old (deprecated) - old_function($data); - - // New (recommended) - new_function($data); - ``` -``` - ---- - -### Step 3: Remove After Major Version - -**On next major version (3.0.0):** -1. Remove deprecated code -2. Remove ~~strikethrough~~ docs -3. Update changelog - ---- - -## Changelog Management - -**Keep CHANGELOG.md updated:** - -### Format - -```markdown -# Changelog - -## [2.5.1] - 2026-02-15 - -### Added -- New REST endpoint: `/sureforms/v1/forms/{id}/duplicate` -- Support for custom date formats in email notifications - -### Changed -- Improved performance of entry queries (20% faster) -- Updated Stripe API to v2024-01-01 - -### Deprecated -- `old_function()` - Use `new_function()` instead - -### Fixed -- Bug where conditional logic didn't work with checkboxes -- Memory leak in AI form generation - -### Security -- Fixed XSS vulnerability in admin settings (CVE-2026-0001) - -## [2.5.0] - 2026-02-01 - -... -``` - -**Categories:** -- **Added:** New features -- **Changed:** Changes to existing features -- **Deprecated:** Features marked for removal -- **Removed:** Features removed -- **Fixed:** Bug fixes -- **Security:** Security fixes - ---- - -## Documentation Review Checklist - -**Before committing doc changes:** - -### Content -- [ ] Information is accurate (tested code examples) -- [ ] No outdated information (double-checked) -- [ ] Cross-references updated (links work) -- [ ] Version number updated -- [ ] Terminology consistent with other docs - -### Style -- [ ] Clear and concise (no fluff) -- [ ] Code examples formatted correctly -- [ ] Spelling and grammar correct -- [ ] Tone matches existing docs - -### Technical -- [ ] All code examples tested -- [ ] All commands verified (on dev environment) -- [ ] All links work (no 404s) -- [ ] File paths correct (checked in codebase) - -### Accessibility -- [ ] Headings in logical order (H1 → H2 → H3) -- [ ] Code blocks have language specified -- [ ] Alt text for images (if any) -- [ ] Tables have headers - ---- - -## Automation & Tools - -### Linting Documentation - -**Check Markdown syntax:** - -```bash -# Install markdownlint -npm install -g markdownlint-cli - -# Lint all docs -cd internal-docs/ -markdownlint *.md -``` - -**Common issues caught:** -- Inconsistent heading levels -- Trailing whitespace -- Missing blank lines around code blocks - ---- - -### Link Checking - -**Verify all links work:** - -```bash -# Install markdown-link-check -npm install -g markdown-link-check - -# Check links -markdown-link-check internal-docs/*.md -``` - -**Fix broken links:** -- Update to correct URL -- Remove if resource no longer exists -- Use Internet Archive if critical resource - ---- - -### Spell Checking - -**Use spell checker:** - -```bash -# Install aspell -brew install aspell # macOS -apt install aspell # Linux - -# Check spelling -aspell check internal-docs/README.md -``` - -**Custom dictionary:** - -Create `.aspell.en.pws`: -``` -personal_ws-1.1 en 50 -SureForms -WordPress -Gutenberg -Mailchimp -PayPal -``` - ---- - -## Documentation Templates - -### New Feature Documentation Template - -**When adding new feature:** - -```markdown -## [Feature Name] - -**Since:** [version] -**Type:** [Free/Pro] - -### Overview - -[2-3 sentence description of what feature does and why it exists] - -### Use Cases - -- [Use case 1] -- [Use case 2] -- [Use case 3] - -### How to Use - -**Step 1: [Action]** - -[Description] - -```php -// Code example -``` - -**Step 2: [Action]** - -[Description] - -### Configuration - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `option_name` | string | 'default' | What this does | - -### API Reference - -**REST Endpoint:** -``` -POST /sureforms/v1/feature -``` - -**Parameters:** -- `param1` (string, required) - Description -- `param2` (integer, optional) - Description - -**Response:** -```json -{ - "success": true, - "data": { ... } -} -``` - -### Examples - -**Example 1: [Use case]** -```php -// Code here -``` - -**Example 2: [Use case]** -```php -// Code here -``` - -### Troubleshooting - -**Issue:** [Common problem] -**Solution:** [How to fix] - -### Related - -- [Related feature 1](link) -- [Related feature 2](link) -``` - ---- - -### Bug Fix Documentation Template - -**When fixing bug:** - -**In troubleshooting.md:** - -```markdown -### [Issue Description] - -**Symptom:** [What user sees] - -**Diagnosis:** - -[How to identify the issue] - -```bash -# Commands to diagnose -``` - -**Fixes:** - -**Option 1: [Fix description]** -```php -// Code or command -``` - -**Option 2: [Alternative fix]** -```php -// Alternative code -``` - -**Prevents:** [What this prevents] - -**See also:** [Related issues] -``` - ---- - -## Review & Approval Process - -### Before Merging Docs - -**Self-review:** -1. Read your changes out loud -2. Test all code examples -3. Run linters -4. Check cross-references - -**Peer review:** -1. Request review from team member -2. Address feedback -3. Update based on comments - -**Final checks:** -1. Rebase on main branch -2. Verify no conflicts -3. One last proofread - ---- - -### Documentation Pull Request Template - -**PR description:** - -```markdown -## Documentation Update - -**Plugin Version:** 2.5.1 -**Docs Changed:** [List files] - -### Changes Made - -- [ ] Added documentation for [feature] -- [ ] Updated [section] in [file] -- [ ] Fixed typos in [file] - -### Verification - -- [x] All code examples tested -- [x] All links checked -- [x] Spelling checked -- [x] Cross-references updated -- [x] Version numbers updated - -### Related - -- Related PR: #123 -- Related Issue: #456 -``` - ---- - -## Documentation Metrics - -**Track documentation quality:** - -### Metrics to Monitor - -**Coverage:** -- % of features documented -- % of APIs documented -- % of code examples tested - -**Freshness:** -- Days since last update -- Number of outdated sections -- Deprecated content count - -**Quality:** -- Broken link count -- Spelling error count -- User-reported doc issues - -**Engagement:** -- Doc views (if analytics enabled) -- Time spent reading -- Bounce rate (if high, docs unclear) - ---- - -## Long-Term Maintenance - -### Quarterly Review - -**Every 3 months:** - -1. **Audit all docs:** - - Read each document start to finish - - Test all code examples - - Verify all links - - Update outdated info - -2. **User feedback:** - - Review GitHub issues tagged "documentation" - - Survey users about doc clarity - - Identify gaps in coverage - -3. **Reorganize if needed:** - - Move sections to better fit structure - - Split large docs if too long - - Merge duplicate information - ---- - -### Annual Review - -**Every year:** - -1. **Major cleanup:** - - Remove deprecated content - - Archive old versions - - Rewrite outdated sections - -2. **Structure evaluation:** - - Does structure still make sense? - - Should we add/remove files? - - Are cross-references clear? - -3. **Voice & tone:** - - Is tone consistent? - - Is it still beginner-friendly? - - Does it match product evolution? - ---- - -## Questions? - -**For documentation questions:** -- Create GitHub issue: `[Docs] Your question` -- Tag: `documentation` -- Assign: Documentation maintainer - -**For urgent doc bugs:** -- Ping in Slack: `#sureforms-docs` -- Include: File name, line number, issue description - ---- - -## Conclusion - -**Documentation is code.** - -Treat it with same care: -- Test before committing -- Review changes -- Keep it DRY (Don't Repeat Yourself) -- Refactor when needed - -**Good documentation:** -- Saves support time -- Accelerates onboarding -- Prevents bugs -- Shows we care - -**Thank you for maintaining SureForms docs!** 📚 - ---- - -**Version:** 2.5.0 -**Last Updated:** 2026-02-12 -**Maintainer:** SureForms Team diff --git a/internal-docs/onboarding.md b/internal-docs/onboarding.md deleted file mode 100644 index ab5b83ad3..000000000 --- a/internal-docs/onboarding.md +++ /dev/null @@ -1,1086 +0,0 @@ -# Developer Onboarding - -**Version:** 2.5.0 - ---- - -## Welcome to SureForms - -This guide helps you become productive quickly, whether you have 1 hour, 1 day, or 1 week. - -**What you're working on:** -- **SureForms Free:** AI-powered WordPress form builder (219 PHP files, 50+ blocks) -- **SureForms Pro:** Premium extension (206 PHP files, payments, integrations, advanced features) - -**Architecture:** WordPress + Gutenberg blocks + React 18 + Custom database - ---- - -## Prerequisites - -Before starting: -- [ ] WordPress 6.4+ installed locally -- [ ] Node.js 18+ and npm 8+ -- [ ] PHP 7.4+ with Composer -- [ ] Git configured -- [ ] IDE with PHP/JavaScript support -- [ ] Both plugins cloned from GitHub - -**Test environment:** -```bash -# If using Local by Flywheel or similar -http://localhost:10003/wp-admin/ -Username: admin -Password: admin -``` - ---- - -## 1-Hour Quick Start - -**Goal:** Make your first successful change - -### Step 1: Get Code Running (15 min) - -```bash -# Clone repositories (if not already) -cd /path/to/wp-content/plugins/ -git clone https://github.com/brainstormforce/sureforms.git -git clone https://github.com/brainstormforce/sureforms-pro.git - -# Install dependencies - SureForms Free -cd sureforms/ -npm install -composer install - -# Build assets -npm run build - -# Install dependencies - SureForms Pro -cd ../sureforms-pro/ -npm install -composer install -npm run build -``` - -**Verify installation:** -```bash -# In WordPress admin -wp plugin activate sureforms sureforms-pro -wp plugin list | grep sureforms -``` - -### Step 2: Create Your First Form (15 min) - -**Via WordPress admin:** -1. Go to **SureForms → Add New** -2. Click **Create with AI** or **Start from Blank** -3. Add fields: Email, Name, Message -4. Click **Publish** -5. Add to a page with the SureForms block - -**Via WP-CLI:** -```bash -# Create form programmatically -wp post create \ - --post_type=sureforms_form \ - --post_title="Test Contact Form" \ - --post_status=publish \ - --post_content='' -``` - -### Step 3: Make a Simple Change (20 min) - -**Task:** Change the submit button text - -**Files to modify:** -``` -src/blocks/form/edit.js ← React component -inc/blocks/form/block.php ← Server-side rendering -``` - -**Change 1: Edit the React component** - -Open `src/blocks/form/edit.js`: - -```javascript -// Find around line 45-50 -const { submitButtonText } = attributes; - -// Change default value -const defaultSubmitText = __('Send Message', 'sureforms'); // ← Change this -``` - -**Change 2: Rebuild** - -```bash -npm run build -``` - -**Verify:** Create new form, check if default button text changed. - -### Step 4: Debug Your Change (10 min) - -**Enable debugging:** - -Edit `wp-config.php`: -```php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -define('SCRIPT_DEBUG', true); // Loads unminified JS -``` - -**Check debug log:** -```bash -tail -f wp-content/debug.log -``` - -**Browser console:** -- Open DevTools → Console -- Look for JavaScript errors -- Check Network tab for AJAX failures - ---- - -## 1-Day Deep Dive - -**Goal:** Understand architecture and make meaningful contributions - -### Morning: Core Concepts (4 hours) - -#### Hour 1: Architecture Overview - -**Read these docs first:** -1. [README.md](README.md) - Project overview -2. [architecture.md](architecture.md) - System design -3. [codebase-map.md](codebase-map.md) - File structure - -**Key concepts to understand:** -- **Gutenberg blocks:** React components that render forms -- **Custom post type:** `sureforms_form` stores form configurations -- **Custom database tables:** `wp_sureforms_entries`, `wp_sureforms_payments` -- **REST API:** Handles form submissions, AI generation -- **AJAX handlers:** Payment processing, file uploads - -**Test your understanding:** -- Where is form submission data saved? (Answer: `wp_sureforms_entries` table) -- How does AI form generation work? (Answer: REST endpoint → external middleware → GPT API) -- What's the difference between Free and Pro? (Answer: Pro adds payments, integrations, conditional logic) - -#### Hour 2: Data Flow - -**Trace a form submission from start to finish:** - -``` -User fills form → Frontend validation → AJAX request - ↓ -inc/form-submit.php (Line 88-119: Nonce verification) - ↓ -Field validation (inc/field-validation.php) - ↓ -Sanitization (inc/helper.php::sanitize_by_field_type()) - ↓ -Database insert (inc/database/tables/entries.php::insert()) - ↓ -Email notification (inc/email/email-handler.php) - ↓ -Confirmation message (or redirect) -``` - -**Hands-on exercise:** -1. Enable MySQL query logging -2. Submit a test form -3. Check `wp_sureforms_entries` table -4. Verify entry data matches submission - -```bash -# View recent entries -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 5;" -``` - -#### Hour 3: Payment Processing - -**Understand Stripe payment flow:** - -``` -User clicks "Pay" → Frontend creates payment intent - ↓ -AJAX: wp_ajax_nopriv_srfm_create_payment_intent - ↓ -inc/payments/front-end.php (Line 77-83: Create Stripe PaymentIntent) - ↓ -Stripe API processes payment - ↓ -Webhook received: sureforms/webhook_test - ↓ -inc/payments/stripe/stripe-webhook.php (Line 220-637: Handle events) - ↓ -Update wp_sureforms_payments table - ↓ -Send confirmation email -``` - -**Hands-on exercise:** -1. Set up Stripe test keys -2. Create a payment form -3. Use Stripe test card: `4242 4242 4242 4242` -4. Check `wp_sureforms_payments` table for payment record - -#### Hour 4: Pro Features - -**Explore key Pro additions:** - -**1. User Registration:** -- File: `inc/business/user-registration/processor.php` -- Creates WordPress user accounts from form submissions -- Handles login/password reset - -**2. Native Integrations:** -- Folder: `inc/pro/native-integrations/integrations/` -- 24+ services: Mailchimp, Brevo, HubSpot, Salesforce, etc. -- OAuth authentication with encrypted token storage - -**3. Conditional Logic:** -- Frontend: `src/pro/conditional-logic/` -- Show/hide fields based on user selections -- Real-time UI updates - -**Hands-on exercise:** -1. Create form with registration block -2. Submit form, verify user created: `wp user list` -3. Enable Mailchimp integration (test mode) -4. Submit form, check integration logs - -### Afternoon: Build Your First Feature (4 hours) - -**Challenge:** Add a new field type - -**Example: "Star Rating" field** - -#### Step 1: Plan (30 min) - -**Requirements:** -- Display 5 stars (clickable) -- Save selected rating (1-5) -- Validate: Required if configured -- Display in entries table - -**Files to create/modify:** -``` -src/blocks/star-rating/ ← New React component -inc/blocks/star-rating/block.php ← Server-side rendering -inc/helper.php ← Add sanitization -inc/field-validation.php ← Add validation -``` - -#### Step 2: Create Block Structure (1 hour) - -**Create block files:** - -```bash -# Create directories -mkdir -p src/blocks/star-rating -mkdir -p inc/blocks/star-rating - -# Copy from existing field (Phone is a good template) -cp -r src/blocks/phone/* src/blocks/star-rating/ -cp inc/blocks/phone/block.php inc/blocks/star-rating/ -``` - -**Edit `src/blocks/star-rating/block.json`:** - -```json -{ - "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 3, - "name": "sureforms/star-rating", - "title": "Star Rating", - "category": "sureforms", - "icon": "star-filled", - "description": "Display a star rating input", - "attributes": { - "label": { - "type": "string", - "default": "Rate your experience" - }, - "required": { - "type": "boolean", - "default": false - }, - "maxRating": { - "type": "number", - "default": 5 - } - }, - "supports": { - "html": false - } -} -``` - -**Edit `src/blocks/star-rating/edit.js`:** - -```javascript -import { __ } from '@wordpress/i18n'; -import { useBlockProps } from '@wordpress/block-editor'; -import { TextControl, ToggleControl, RangeControl } from '@wordpress/components'; - -export default function Edit({ attributes, setAttributes }) { - const { label, required, maxRating } = attributes; - - return ( -
- setAttributes({ label: value })} - /> - setAttributes({ required: value })} - /> - setAttributes({ maxRating: value })} - min={1} - max={10} - /> -
- {[...Array(maxRating)].map((_, i) => ( - - ))} -
-
- ); -} -``` - -#### Step 3: Server-Side Rendering (45 min) - -**Edit `inc/blocks/star-rating/block.php`:** - -```php - [__CLASS__, 'render'], - ] - ); - } - - public static function render($attributes) { - $label = esc_html($attributes['label'] ?? 'Rate your experience'); - $required = !empty($attributes['required']); - $max_rating = absint($attributes['maxRating'] ?? 5); - $field_name = 'srfm-star-rating-' . uniqid(); - - ob_start(); - ?> -
- -
- - - ★ - - -
- > -
- { - const stars = container.querySelectorAll('.srfm-star'); - const input = container.nextElementSibling; - const maxRating = parseInt(container.dataset.max); - - stars.forEach(star => { - star.addEventListener('click', function() { - const value = parseInt(this.dataset.value); - input.value = value; - - // Visual update - stars.forEach((s, index) => { - if (index < value) { - s.classList.add('selected'); - } else { - s.classList.remove('selected'); - } - }); - }); - - // Hover effect - star.addEventListener('mouseenter', function() { - const value = parseInt(this.dataset.value); - stars.forEach((s, index) => { - if (index < value) { - s.classList.add('hover'); - } else { - s.classList.remove('hover'); - } - }); - }); - }); - - container.addEventListener('mouseleave', function() { - stars.forEach(s => s.classList.remove('hover')); - }); - }); -}); -``` - -#### Step 5: Register Block (15 min) - -**Edit `inc/blocks/register.php`:** - -```php -// Add to existing blocks registration -Star_Rating\Block::register(); -``` - -**Edit `src/blocks/star-rating/index.js`:** - -```javascript -import { registerBlockType } from '@wordpress/blocks'; -import edit from './edit'; -import metadata from './block.json'; - -registerBlockType(metadata.name, { - edit, - save: () => null, // Server-side rendering -}); -``` - -#### Step 6: Add Sanitization & Validation (30 min) - -**Edit `inc/helper.php`:** - -Find `sanitize_by_field_type()` method and add: - -```php -case 'star-rating': - $value = absint($value); - // Ensure value is between 1 and max rating - return ($value >= 1 && $value <= 10) ? $value : 0; -``` - -**Edit `inc/field-validation.php`:** - -Find validation logic and add: - -```php -if ($field_type === 'star-rating' && $is_required && empty($value)) { - $errors[] = __('Please select a rating', 'sureforms'); -} -``` - -#### Step 7: Build & Test (30 min) - -```bash -# Build assets -npm run build - -# Test in browser -# 1. Create new form -# 2. Add "Star Rating" block -# 3. Configure settings (label, required, max stars) -# 4. Publish form -# 5. Submit test entry -# 6. Verify data saved correctly - -# Check database -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 1;" -``` - ---- - -## 1-Week Mastery - -**Goal:** Expert-level understanding and complex feature development - -### Day 1: Architecture Deep Dive - -**Morning:** -- Complete 1-day onboarding -- Read all internal docs thoroughly -- Map data flow for all major features - -**Afternoon:** -- Trace payment processing end-to-end -- Understand webhook verification -- Study integration architecture - -### Day 2: Security & Best Practices - -**Focus areas:** -1. **Input sanitization:** Understand all 15+ sanitization methods -2. **Output escaping:** When to use `esc_html()`, `esc_attr()`, `esc_url()` -3. **SQL injection prevention:** Always use `$wpdb->prepare()` -4. **CSRF protection:** Nonce verification patterns -5. **Authorization:** `current_user_can()` checks - -**Hands-on exercises:** -- Review `inc/form-submit.php` (Lines 88-119: Security checks) -- Study `inc/helper.php` sanitization methods -- Find and fix intentional security bugs in test code - -**Read:** -- [coding-standards.md](coding-standards.md) -- [ai-agent-guide.md](ai-agent-guide.md) - Security section - -### Day 3: React & Gutenberg Blocks - -**Topics:** -1. **Block architecture:** How Gutenberg blocks work -2. **Attributes:** Data storage and retrieval -3. **Inspector controls:** Settings sidebar -4. **Block variations:** Different field configurations -5. **Dynamic blocks:** Server-side rendering - -**Build complex block:** -- **Multi-step form block** (if not using Pro feature) -- Add step navigation -- Save progress between steps -- Implement validation per step - -**Resources:** -- Official Gutenberg docs: https://developer.wordpress.org/block-editor/ -- Example: Study `src/blocks/phone/` for complex field - -### Day 4: Database & REST API - -**Morning: Database operations** - -**Study these classes:** -- `inc/database/base.php` - Base table class -- `inc/database/tables/entries.php` - Entries CRUD -- `inc/database/tables/payments.php` - Payment records - -**Practice:** -```php -// Create custom query -use SRFM\Inc\Database\Tables\Entries; - -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='], - ['key' => 'status', 'value' => 'published', 'compare' => '='] - ], - 'orderby' => 'created_at', - 'order' => 'DESC', - 'limit' => 20 -]); -``` - -**Afternoon: REST API development** - -**Create custom endpoint:** - -```php -// In new file: inc/rest-api-custom.php -namespace SRFM\Inc; - -class Rest_API_Custom { - public function register_routes() { - register_rest_route('sureforms/v1', '/custom-stats', [ - 'methods' => 'GET', - 'callback' => [$this, 'get_stats'], - 'permission_callback' => [$this, 'check_permissions'] - ]); - } - - public function check_permissions() { - return current_user_can('manage_options'); - } - - public function get_stats($request) { - // Your logic here - return ['success' => true, 'data' => []]; - } -} -``` - -### Day 5: Pro Features & Integrations - -**Morning: Payment processing** - -**Study both gateways:** -- Stripe: `inc/payments/stripe/` -- PayPal (Pro): `inc/business/payments/pay-pal/` - -**Understand webhook handling:** -- Signature verification -- Event processing -- Database updates -- Error handling - -**Afternoon: Build custom integration** - -**Example: Slack integration** - -1. Create integration folder: -``` -inc/pro/native-integrations/integrations/slack/ -├── config.json -└── actions/ - └── send-message.php -``` - -2. Define config: -```json -{ - "name": "Slack", - "slug": "slack", - "auth_method": "webhook_url", - "actions": [ - { - "name": "Send Message", - "slug": "send-message", - "endpoint": "chat.postMessage" - } - ] -} -``` - -3. Implement action: -```php -format_message($form_data); - - $response = wp_remote_post($webhook_url, [ - 'body' => json_encode(['text' => $message]), - 'headers' => ['Content-Type' => 'application/json'] - ]); - - if (is_wp_error($response)) { - return ['success' => false, 'error' => $response->get_error_message()]; - } - - return ['success' => true]; - } - - private function format_message($form_data) { - // Format form data as Slack message - return "New form submission:\n" . print_r($form_data, true); - } -} -``` - ---- - -## Testing Your Changes - -### Local Testing - -**PHP Unit Tests:** -```bash -# Run all tests -composer test - -# Run specific test file -vendor/bin/phpunit tests/unit/test-helper.php - -# Run with coverage -composer test-coverage -``` - -**JavaScript Tests:** -```bash -# Run all JS tests -npm run test:unit - -# Watch mode -npm run test:unit:watch - -# Coverage -npm run test:unit:coverage -``` - -### E2E Testing (Playwright) - -```bash -# Start test environment -npm run play:up - -# Run E2E tests -npm run play:run - -# Interactive mode (see browser) -npm run play:run:interactive - -# Stop environment -npm run play:down -``` - -### Manual Testing Checklist - -Before submitting PR: -- [ ] Form creation works -- [ ] Form submission saves entry -- [ ] Email notifications sent -- [ ] Payment processing works (if touched) -- [ ] No JavaScript console errors -- [ ] No PHP errors in debug.log -- [ ] Works in latest WordPress version -- [ ] Works in Firefox, Chrome, Safari -- [ ] Mobile responsive -- [ ] Accessibility: keyboard navigation works - ---- - -## Code Review Process - -### Before Creating PR - -**Self-review:** -1. Run linters: -```bash -composer lint # PHP_CodeSniffer -npm run lint-js # ESLint -``` - -2. Auto-fix minor issues: -```bash -composer format # phpcbf -npm run lint-js:fix # ESLint --fix -``` - -3. Check for common issues: -- [ ] All inputs sanitized -- [ ] All outputs escaped -- [ ] SQL queries use `prepare()` -- [ ] Nonces verified for AJAX/forms -- [ ] Capabilities checked for admin actions -- [ ] No hardcoded strings (use `__()` for i18n) - -### Creating PR - -**Branch naming:** -```bash -git checkout -b feat/star-rating-field -git checkout -b fix/payment-webhook-error -git checkout -b refactor/database-queries -``` - -**Commit message format:** -``` -type(scope): brief description - -Longer explanation if needed. - -Co-Authored-By: Your Name -``` - -**Types:** feat, fix, docs, style, refactor, test, chore - -**Example:** -``` -feat(blocks): add star rating field - -- Add new star-rating block -- Implement frontend JavaScript -- Add sanitization and validation -- Update docs with new field type - -Co-Authored-By: Claude Sonnet 4.5 -``` - -### PR Checklist - -- [ ] Tests pass: `composer test && npm run test:unit` -- [ ] Linting passes: `composer lint && npm run lint-js` -- [ ] No console errors -- [ ] Database migrations documented (if any) -- [ ] Breaking changes documented -- [ ] Changelog updated -- [ ] Screenshots attached (for UI changes) - ---- - -## Common Tasks & Patterns - -### Add New AJAX Handler - -**Pattern:** -```php -// In controller file -public function register_ajax_actions() { - add_action('wp_ajax_srfm_my_action', [$this, 'handle_my_action']); - add_action('wp_ajax_nopriv_srfm_my_action', [$this, 'handle_my_action']); -} - -public function handle_my_action() { - // CRITICAL: Always verify nonce - check_ajax_referer('srfm_my_action_nonce', 'security'); - - // Check permissions if needed - if (!current_user_can('manage_options')) { - wp_send_json_error(['message' => 'Insufficient permissions']); - } - - // Sanitize inputs - $data = Helper::sanitize_array_recursively($_POST['data'] ?? []); - - // Process... - - wp_send_json_success(['result' => $result]); -} -``` - -### Add New REST Endpoint - -**Pattern:** -```php -// In inc/rest-api.php -public function register_routes() { - register_rest_route('sureforms/v1', '/my-endpoint', [ - 'methods' => 'POST', - 'callback' => [$this, 'handle_request'], - 'permission_callback' => [$this, 'check_permissions'] - ]); -} - -public function check_permissions() { - return current_user_can('manage_options'); -} - -public function handle_request($request) { - // Always verify nonce - $nonce = $request->get_header('X-WP-Nonce'); - if (!wp_verify_nonce($nonce, 'wp_rest')) { - return new \WP_Error('invalid_nonce', 'Invalid nonce', ['status' => 403]); - } - - // Get and sanitize parameters - $params = $request->get_params(); - $clean_params = Helper::sanitize_array_recursively($params); - - // Process... - - return ['success' => true, 'data' => $result]; -} -``` - -### Add WordPress Hook - -**Action hook (notify about event):** -```php -// Trigger action after entry save -do_action('sureforms_after_entry_save', $entry_id, $form_id, $entry_data); - -// Other plugins/themes can listen: -add_action('sureforms_after_entry_save', function($entry_id, $form_id, $data) { - // Custom logic -}, 10, 3); -``` - -**Filter hook (modify data):** -```php -// Allow filtering of email subject -$subject = apply_filters('sureforms_email_subject', $subject, $form_id, $entry_data); - -// Other plugins/themes can modify: -add_filter('sureforms_email_subject', function($subject, $form_id, $data) { - return "[Form $form_id] $subject"; -}, 10, 3); -``` - -### Database Operations - -**Insert entry:** -```php -use SRFM\Inc\Database\Tables\Entries; - -$entry_id = Entries::insert([ - 'form_id' => 123, - 'user_id' => get_current_user_id(), - 'status' => 'published', - 'entry_data' => wp_json_encode($form_data) -]); -``` - -**Update entry:** -```php -Entries::update($entry_id, [ - 'status' => 'spam' -]); -``` - -**Delete entry:** -```php -Entries::delete($entry_id); -``` - -**Query entries:** -```php -$entries = Entries::get_all([ - 'where' => [ - ['key' => 'form_id', 'value' => 123, 'compare' => '='] - ], - 'limit' => 20, - 'offset' => 0 -]); -``` - ---- - -## Debugging Tips - -### Enable Debug Mode - -**wp-config.php:** -```php -define('WP_DEBUG', true); -define('WP_DEBUG_LOG', true); -define('WP_DEBUG_DISPLAY', false); -define('SCRIPT_DEBUG', true); -define('SAVEQUERIES', true); -``` - -### Check Debug Log - -```bash -# Real-time log monitoring -tail -f wp-content/debug.log - -# Search for errors -grep "SureForms" wp-content/debug.log -``` - -### JavaScript Debugging - -**Browser console:** -```javascript -// Check global object -console.log(window.sureforms); - -// Check block attributes -wp.data.select('core/block-editor').getBlocks(); - -// Check form data -document.querySelector('form').addEventListener('submit', (e) => { - console.log('Form data:', new FormData(e.target)); -}); -``` - -### Database Queries - -**Install Query Monitor plugin:** -```bash -wp plugin install query-monitor --activate -``` - -**Check slow queries:** -- Visit any admin page -- Click "Query Monitor" in admin bar -- View "Queries" tab -- Sort by time -- Optimize slow queries - -**Manual query inspection:** -```php -// Add to code temporarily -global $wpdb; -$wpdb->show_errors(); -$wpdb->print_error(); -``` - -### AJAX Debugging - -**Check network tab:** -1. Open DevTools → Network -2. Filter: XHR -3. Submit form -4. Click request -5. Check Response tab for errors - -**Add debug output:** -```php -public function handle_ajax() { - error_log('AJAX data: ' . print_r($_POST, true)); - // ... rest of code -} -``` - ---- - -## Resources - -### Internal Documentation -- [README.md](README.md) - Quick start -- [architecture.md](architecture.md) - System design -- [codebase-map.md](codebase-map.md) - File structure -- [apis.md](apis.md) - API reference -- [coding-standards.md](coding-standards.md) - Code standards -- [ai-agent-guide.md](ai-agent-guide.md) - AI agent guidance -- [troubleshooting.md](troubleshooting.md) - Common problems - -### External Resources -- **WordPress Codex:** https://codex.wordpress.org/ -- **Block Editor Handbook:** https://developer.wordpress.org/block-editor/ -- **REST API Handbook:** https://developer.wordpress.org/rest-api/ -- **React Docs:** https://react.dev/ -- **WP-CLI:** https://wp-cli.org/ - -### Community -- **GitHub Issues:** Report bugs, request features -- **Facebook Group:** https://www.facebook.com/groups/surecart -- **Support:** https://support.brainstormforce.com/ - ---- - -## Next Steps - -After completing onboarding: - -1. **Pick a starter issue:** - - Look for "good first issue" label on GitHub - - Start with documentation or small bug fixes - - Work up to features - -2. **Read code:** - - Pick a feature you use - - Trace it from UI to database - - Understand every line - -3. **Write tests:** - - For code you touch - - Increases confidence - - Helps others understand your changes - -4. **Ask questions:** - - Don't hesitate to ask - - Better to ask than assume - - Document answers for others - -**Welcome to the team!** 🚀 diff --git a/internal-docs/product-vision.md b/internal-docs/product-vision.md deleted file mode 100644 index f41f1951c..000000000 --- a/internal-docs/product-vision.md +++ /dev/null @@ -1,688 +0,0 @@ -# Product Vision - -**Version:** 2.5.0 - ---- - -## Mission - -**Empower anyone to build beautiful, high-converting forms without code.** - -SureForms exists to solve a fundamental problem: creating forms on WordPress is unnecessarily complex, resulting in ugly forms that hurt conversion rates. - ---- - -## The Problem We Solve - -### Pain Points (Before SureForms) - -**1. Complexity** -- Learning curve: New interfaces, proprietary builders -- Time waste: Hours configuring simple contact forms -- Technical barriers: Requires developer for custom fields - -**2. Design Limitations** -- Generic templates that don't match site design -- Limited styling options without CSS knowledge -- Mobile responsiveness as afterthought - -**3. Low Engagement** -- Long, intimidating single-page forms -- No personalization or conditional logic -- High abandonment rates (avg 67% for long forms) - -**4. Spam & Security** -- Constant bot submissions -- Security vulnerabilities in popular plugins -- GDPR compliance challenges - -**5. Integration Friction** -- Payment processing requires multiple plugins -- Manual data export/import to CRMs -- Webhook setup requires developer knowledge - ---- - -## Our Solution - -### Core Differentiators - -**1. Native WordPress (Gutenberg)** -- **Why:** Users already know the interface -- **Benefit:** Zero learning curve, instant productivity -- **Technical:** React blocks, no proprietary builder - -**2. AI-Powered Form Building** -- **Innovation:** First AI form builder for WordPress -- **How:** Natural language → complete functional form in seconds -- **Examples:** - - "Create a job application form" → 12-field form with file upload - - "Simple contact form" → 3 fields, perfectly styled - - "Event RSVP with dietary restrictions" → Conditional logic auto-configured - -**3. Built-in Payments** -- **Why:** No WooCommerce, no add-ons required -- **Gateways:** Stripe (Free), PayPal (Pro) -- **Features:** One-time, subscriptions, custom amounts -- **Security:** PCI-compliant, encrypted credentials - -**4. Mobile-First Design** -- **Approach:** Responsive by default, not opt-in -- **Testing:** Every block tested on iOS/Android -- **Performance:** Fast load times on 3G networks - -**5. Engagement Features (Pro)** -- **Conversational Forms:** Chat-like, one question at a time -- **Multi-Step Forms:** Break long forms into digestible steps -- **Conditional Logic:** Show/hide based on answers -- **Result:** 3x higher completion rates vs single-page forms - ---- - -## Target Users - -### Primary Personas - -#### 1. Website Owner (40% of users) -**Profile:** -- Small business owner or solopreneur -- Limited technical skills -- DIY mentality -- Budget-conscious - -**Needs:** -- Contact forms, quote requests, bookings -- Easy setup (< 10 minutes) -- Professional appearance -- Spam protection - -**Pain Points:** -- Frustrated with complex form builders -- Can't afford developer -- Generic templates don't match brand - -**How SureForms Helps:** -- AI creates form in 30 seconds -- Instant Form feature (no embedding needed) -- Built-in anti-spam (reCAPTCHA, Honeypot) -- Modern, customizable design - ---- - -#### 2. WordPress Designer (30% of users) -**Profile:** -- Freelancer or agency designer -- Strong design skills, basic code knowledge -- Builds sites for clients -- Values aesthetics and UX - -**Needs:** -- Forms that match site design -- Custom styling without CSS -- Fast deployment -- Client-friendly interface - -**Pain Points:** -- Form plugins look "generic" -- CSS overrides are tedious -- Clients can't update forms themselves -- Other plugins don't use Gutenberg - -**How SureForms Helps:** -- Gutenberg-native (feels like page building) -- Extensive styling options in UI -- Client can edit without breaking design -- Block patterns for reusability - ---- - -#### 3. WordPress Developer (20% of users) -**Profile:** -- Full-stack developer -- Builds custom WordPress solutions -- Values clean code and extensibility -- Performance-conscious - -**Needs:** -- Developer-friendly APIs -- Hooks and filters -- Custom field types -- Database access -- Integration capabilities - -**Pain Points:** -- Other plugins have messy codebases -- Limited extensibility -- Poor documentation -- Performance issues (N+1 queries, bloat) - -**How SureForms Helps:** -- Clean, modern codebase (PSR-12-inspired) -- Extensive hooks: 50+ actions/filters -- Well-documented APIs -- Optimized queries (custom tables, not post meta) -- GitHub access for contributions - ---- - -#### 4. E-commerce Store Owner (10% of users) -**Profile:** -- Runs WooCommerce or custom store -- Needs payment forms (not full checkout) -- Sells services, memberships, donations -- Wants simplicity - -**Needs:** -- Accept payments without WooCommerce -- Subscription billing -- Custom pricing fields -- Receipt emails - -**Pain Points:** -- WooCommerce is overkill for simple payments -- Other payment forms require add-ons -- Can't customize payment flow -- Poor mobile checkout experience - -**How SureForms Helps:** -- Built-in Stripe/PayPal (no plugins) -- Subscription support included -- Custom amount fields (donations, tips) -- Mobile-optimized payment UI - ---- - -## Feature Philosophy - -### Design Principles - -**1. Simplicity Over Features** -- Don't add features just because competitors have them -- Every feature must solve real user problem -- Hide complexity behind smart defaults - -**Example:** -- ❌ Bad: 50 font options overwhelming users -- ✅ Good: 5 curated fonts + custom font option - -**2. Progressive Disclosure** -- Show basic options first -- Advanced settings collapsed by default -- Help text on hover, not always visible - -**Example:** -- Default form settings: 4 essential options -- Advanced panel: 15+ options (collapsed) - -**3. Convention Over Configuration** -- Smart defaults based on common use cases -- Zero config for 80% of users -- Power users can customize - -**Example:** -- Email notification auto-configured with sensible template -- User can override if needed - -**4. Performance First** -- Lazy load non-critical assets -- Minimize HTTP requests -- Database queries optimized (no N+1) -- CSS/JS minified and cached - -**Metrics:** -- Page load impact: < 50KB additional assets -- Time to Interactive: < 2 seconds on 3G -- Database queries: Max 5 per form render - -**5. Accessibility Built-In** -- WCAG 2.1 Level AA compliance -- Keyboard navigation -- Screen reader support -- Focus indicators - -**Testing:** -- Every block tested with NVDA/JAWS -- Keyboard-only testing required -- Color contrast checked - ---- - -## Free vs Pro Strategy - -### Free Plugin (Core Experience) - -**Philosophy:** Full-featured, not crippled trial - -**Included:** -- Unlimited forms -- Unlimited submissions -- All 15+ field types -- Email notifications -- Spam protection (reCAPTCHA, Honeypot) -- Stripe payments (one-time, subscriptions) -- Form analytics -- Export entries (CSV) -- GDPR compliance -- Instant Forms - -**Limitations:** -- No PayPal -- No native integrations (Mailchimp, etc.) -- No conditional logic -- No multi-step/conversational forms -- No user registration -- No PDF generation - -**Goal:** Provide genuine value, build trust, convert 5-10% to Pro - ---- - -### Pro Plugin (Power Features) - -**Philosophy:** Advanced features for serious users - -**Added Value:** -- **Payments:** PayPal (one-time, subscriptions) -- **Integrations:** 24+ native (Mailchimp, Brevo, HubSpot, Salesforce, etc.) -- **Logic:** Conditional show/hide, calculations -- **Forms:** Multi-step, conversational, save & resume -- **Users:** Registration, login, password reset -- **Output:** PDF generation from submissions -- **Advanced Fields:** Upload (images, files), signature, calculator -- **Priority Support:** < 24hr response time - -**Pricing Tiers:** -- **Essential:** $99/year (3 sites) -- **Plus:** $199/year (20 sites) -- **Agency:** $299/year (unlimited sites) - -**Conversion Strategy:** -- Free users see "Pro" badge on locked features -- No nag screens or popups -- Upgrade CTA in logical places (when user needs feature) -- 14-day money-back guarantee - ---- - -## Competitive Landscape - -### Direct Competitors - -**1. Gravity Forms** -- **Strengths:** Mature, extensive add-ons, trusted -- **Weaknesses:** Old UI, not Gutenberg-native, expensive -- **Our Advantage:** Modern interface, AI builder, lower cost - -**2. WPForms** -- **Strengths:** Beginner-friendly, drag-and-drop -- **Weaknesses:** Generic designs, limited styling -- **Our Advantage:** Better design flexibility, Gutenberg-native - -**3. Formidable Forms** -- **Strengths:** Advanced features, views/reporting -- **Weaknesses:** Steep learning curve, performance issues -- **Our Advantage:** Simpler, faster, AI-assisted - -**4. Fluent Forms** -- **Strengths:** Conversational forms, modern UI -- **Weaknesses:** Not Gutenberg-native, separate builder -- **Our Advantage:** Native Gutenberg, simpler UX - -**5. Contact Form 7** -- **Strengths:** Free, lightweight, popular -- **Weaknesses:** No UI, requires shortcodes, ugly default styles -- **Our Advantage:** Visual builder, beautiful defaults, AI - ---- - -### Market Positioning - -**SureForms:** The AI-powered Gutenberg form builder - -**Tagline:** "Beautiful forms without code" - -**Positioning Statement:** -> For WordPress users who want high-converting forms without complexity, SureForms is the AI-powered form builder that creates beautiful, mobile-first forms in seconds—unlike outdated plugins that require hours of configuration and result in generic-looking forms. - -**Why Users Choose SureForms:** -1. **Speed:** AI creates forms in 30 seconds vs 30 minutes -2. **Design:** Modern, mobile-first vs generic templates -3. **Ease:** Gutenberg-native vs proprietary builder -4. **Value:** Built-in payments vs add-ons required - ---- - -## Product Roadmap - -### Current Focus (2026 Q1) - -**Theme:** Stability & Performance - -**Priorities:** -1. Bug fixes from user reports -2. Performance optimization (lazy loading, query optimization) -3. Security hardening (code audit, penetration testing) -4. Documentation improvements -5. Accessibility compliance (WCAG 2.2 Level AA) - ---- - -### Near-Term (2026 Q2-Q3) - -**Theme:** Advanced Features & Integrations - -**Planned:** -1. **More Integrations:** - - Google Sheets (native, no Zapier) - - Airtable - - Notion - - Slack (native) - -2. **Field Types:** - - Star rating - - Matrix/grid (Pro) - - File upload enhancements (drag & drop, preview) - -3. **Conditional Logic Enhancements (Pro):** - - Show/hide blocks (not just fields) - - Calculation fields (price quotes, BMI calculators) - - Conditional email notifications - -4. **Analytics:** - - Conversion funnel visualization - - A/B testing (form variations) - - Heatmaps (where users drop off) - -5. **Templates:** - - 100+ pre-built form templates - - Industry-specific (real estate, healthcare, education) - - Import/export custom templates - ---- - -### Long-Term Vision (2027+) - -**Theme:** AI-Driven Personalization - -**Research Areas:** -1. **AI Form Optimization:** - - Auto-suggest field improvements based on completion rates - - Predictive text for common fields - - Smart field ordering (ML-driven) - -2. **Advanced Conversational Forms:** - - Voice input support - - Natural language processing for responses - - Branching logic based on sentiment - -3. **Global Expansion:** - - Multi-language support (WPML, Polylang) - - Currency localization - - Regional compliance (CCPA, PIPEDA, etc.) - -4. **Enterprise Features:** - - Team collaboration (roles, permissions) - - Advanced approval workflows - - Audit logs - - White-label options - -5. **Mobile App:** - - iOS/Android app for managing entries - - Push notifications for new submissions - - Offline form viewing - ---- - -## Success Metrics - -### North Star Metric -**Active Forms:** Number of forms receiving at least 1 submission per month - -**Why:** Indicates genuine usage, not just installs - -**Target:** 100,000 active forms by end of 2026 - ---- - -### Secondary Metrics - -**Growth:** -- New installations per month: 50,000+ -- Activation rate: 60% (user creates first form within 7 days) -- Retention: 80% still active after 30 days - -**Engagement:** -- Forms created per user: 3.5 average -- Submissions per form: 25/month average -- Feature adoption (Pro): 40% use conditional logic - -**Conversion:** -- Free → Pro conversion: 5-7% -- Trial → Paid: 25% -- Annual renewal rate: 85% - -**Quality:** -- Support ticket volume: < 2% of active users -- Bug reports: < 0.5% of installations -- 4.5+ star rating on WordPress.org - -**Performance:** -- Average page load impact: < 40KB -- Time to first form submission: < 5 minutes (from install) -- Support response time: < 12 hours - ---- - -## User Feedback Integration - -### How We Listen - -**1. Support Tickets** -- Every ticket tagged by topic -- Monthly review of common issues -- Feature requests tracked in GitHub - -**2. User Surveys** -- Annual user satisfaction survey -- Post-purchase survey (Pro users) -- Exit survey (churned Pro users) - -**3. Analytics** -- Feature usage tracking (opt-in) -- Error logging (anonymized) -- Performance metrics - -**4. Community** -- Facebook group discussions -- GitHub issues and discussions -- WordPress.org support forum - -**5. Direct Outreach** -- User interviews (quarterly) -- Beta tester program -- Power user advisory board - ---- - -### Decision Framework - -**Feature Requests Evaluation:** - -**Criteria:** -1. **Impact:** How many users need this? (1-10) -2. **Effort:** Development complexity? (1-10) -3. **Strategic Fit:** Aligns with vision? (Yes/No) -4. **Competitive:** Do competitors have it? (Yes/No) -5. **Revenue:** Drives conversions? (Yes/No) - -**Scoring:** -- Impact / Effort = Priority score -- Strategic Fit = multiplier (2x if yes) -- Build if score > 5 - -**Example:** -- Feature: "Drag & drop file upload" -- Impact: 8 (many requests) -- Effort: 4 (moderate complexity) -- Strategic Fit: Yes (better UX) -- Score: (8/4) × 2 = 4 → **Build later** - ---- - -## Brand & Voice - -### Brand Personality - -**Adjectives:** -- Approachable (not intimidating) -- Modern (not trendy) -- Reliable (not boring) -- Empowering (not condescending) - -**Tone:** -- Friendly but professional -- Clear over clever -- Helpful without being pushy -- Honest about limitations - ---- - -### Writing Guidelines - -**Do:** -- Use "you" (conversational) -- Short sentences -- Active voice -- Explain "why" not just "how" - -**Don't:** -- Jargon without explanation -- Marketing fluff ("revolutionary", "game-changing") -- Passive voice ("the form was created") -- Unnecessary exclamation marks!!!! - -**Examples:** - -❌ Bad: "SureForms revolutionizes form building with cutting-edge AI technology!" - -✅ Good: "SureForms uses AI to create forms in seconds. Describe what you need, and we'll build it." - ---- - -❌ Bad: "The form submission process has been optimized for maximum conversion potential." - -✅ Good: "We designed our forms to load fast and look great on mobile, so more people complete them." - ---- - -## Technical Vision - -### Architecture Goals - -**1. Performance** -- Custom database tables (not post meta) -- Lazy loading for non-critical assets -- Query optimization (no N+1) -- CDN-friendly (static assets versioned) - -**2. Scalability** -- Handle 100,000+ submissions per form -- Efficient database queries (indexed columns) -- Background processing for heavy tasks (webhooks, PDFs) -- Caching strategy (transients, object cache) - -**3. Security** -- Input sanitization (all user data) -- Output escaping (all rendered content) -- Nonce verification (all AJAX/REST) -- SQL injection prevention (prepared statements) -- Regular security audits - -**4. Extensibility** -- 50+ hooks (actions and filters) -- Clean, documented APIs -- Developer-friendly codebase -- Backward compatibility promise - -**5. Maintainability** -- WordPress coding standards (PHP_CodeSniffer) -- ESLint for JavaScript -- Automated testing (PHPUnit, Playwright) -- Clear documentation (inline and external) - ---- - -## Values & Principles - -**1. User Privacy** -- No tracking without consent -- GDPR compliance built-in -- Data portability (easy export) -- Clear privacy policy - -**2. Open Source (Free Plugin)** -- Public GitHub repository -- Accept community contributions -- Transparent roadmap -- Active maintenance - -**3. Quality Over Speed** -- Test thoroughly before release -- Fix bugs before adding features -- Code review required for all changes -- No "move fast and break things" - -**4. Accessibility** -- WCAG 2.1 Level AA minimum -- Keyboard navigation always -- Screen reader testing required -- Color contrast compliance - -**5. Sustainability** -- Reasonable pricing (not subscription trap) -- Long-term support commitment -- No vendor lock-in (data always exportable) -- Transparent upgrade policies - ---- - -## What We Won't Do - -**Out of Scope:** - -**1. Full CRM System** -- Why: Bloat, complexity, competing with specialists -- Alternative: Integrate with existing CRMs (HubSpot, Salesforce) - -**2. Email Marketing Platform** -- Why: Already solved by Mailchimp, Brevo, etc. -- Alternative: Native integrations with 24+ email platforms - -**3. Complete E-commerce Solution** -- Why: WooCommerce exists, does it well -- Alternative: Simple payment forms (our niche) - -**4. Website Builder** -- Why: Outside core competency (forms) -- Alternative: Work seamlessly with any page builder - -**5. Survey & Quiz Builder** -- Why: Different use case, different UX requirements -- Alternative: Forms work for simple surveys - ---- - -## Conclusion - -**Core Belief:** -Forms are critical touchpoints between businesses and customers. They should be beautiful, fast, and easy to create. - -**Our Promise:** -We'll keep building tools that make form creation accessible to everyone, without sacrificing power or flexibility. - -**For Developers:** -Your feedback shapes this product. Keep the issues and PRs coming. We're listening. - ---- - -**Next:** [UI & Copy Guidelines](ui-and-copy.md) diff --git a/internal-docs/troubleshooting.md b/internal-docs/troubleshooting.md deleted file mode 100644 index b7f256af5..000000000 --- a/internal-docs/troubleshooting.md +++ /dev/null @@ -1,1031 +0,0 @@ -# Troubleshooting Guide - -**Version:** 2.5.0 - ---- - -## Quick Diagnostic - -**Before diving deep, check these common issues:** - -```bash -# 1. WordPress & PHP versions -wp core version -php -v - -# 2. Plugin status -wp plugin list | grep sureforms - -# 3. Theme compatibility -wp theme list --status=active - -# 4. Recent errors -tail -50 wp-content/debug.log | grep -i "sureforms\|fatal\|error" - -# 5. Database tables exist -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - ---- - -## Installation & Activation Issues - -### Plugin Won't Activate - -**Symptom:** "Plugin activation failed" or white screen - -**Common Causes:** - -**1. PHP Version Too Old** -```bash -php -v -# Required: PHP 7.4+ -``` - -**Fix:** -```bash -# Update PHP (contact host if shared hosting) -# Or add to wp-config.php temporarily to see error: -define('WP_DEBUG', true); -define('WP_DEBUG_DISPLAY', true); -``` - -**2. WordPress Version Too Old** -```bash -wp core version -# Required: WordPress 6.4+ -``` - -**Fix:** -```bash -wp core update -``` - -**3. Conflicting Plugin** - -**Check for conflicts:** -```bash -# Deactivate all other plugins -wp plugin deactivate --all --exclude=sureforms,sureforms-pro - -# Try activating SureForms -wp plugin activate sureforms - -# Reactivate plugins one by one -wp plugin activate plugin-name -``` - -**Common conflicts:** -- Old caching plugins (W3 Total Cache < 2.0) -- Security plugins with aggressive rules -- Other form builders (namespace collisions) - -**4. Memory Limit Too Low** - -**Check current limit:** -```bash -wp eval 'echo WP_MEMORY_LIMIT;' -``` - -**Fix (wp-config.php):** -```php -define('WP_MEMORY_LIMIT', '256M'); -``` - ---- - -### Pro Plugin Shows "Base Plugin Required" - -**Symptom:** SureForms Pro won't activate - -**Diagnosis:** -```bash -wp plugin list | grep sureforms -# Ensure both sureforms AND sureforms-pro are installed -``` - -**Fix:** -1. Install SureForms Free first -2. Activate SureForms Free -3. Then activate SureForms Pro - -**Check version compatibility:** -```bash -# Free and Pro versions should match -# Both should be 2.5.0 (or same major.minor) -``` - ---- - -### Database Tables Not Created - -**Symptom:** Form submissions fail, entries not showing - -**Check tables exist:** -```bash -wp db query "SHOW TABLES LIKE '%sureforms%';" -``` - -**Expected output:** -``` -wp_sureforms_entries -wp_sureforms_payments -wp_sureforms_integrations -wp_sureforms_save_resume -``` - -**Fix: Force database creation** -```bash -# Deactivate plugin -wp plugin deactivate sureforms sureforms-pro - -# Delete plugin (backup first!) -wp plugin delete sureforms - -# Reinstall -wp plugin install sureforms --activate - -# Tables should be created on activation -``` - -**Manual creation (last resort):** - -Read schema from: -- `inc/database/tables/entries.php` -- `inc/database/tables/payments.php` - -Run CREATE TABLE statements manually. - ---- - -## Form Builder (Editor) Issues - -### Forms Won't Load in Editor - -**Symptom:** Blank screen or infinite loading spinner - -**Diagnosis:** - -**1. Check browser console:** -``` -Open DevTools → Console -Look for JavaScript errors -``` - -**Common errors:** - -**a) "wp.blockEditor is undefined"** -- **Cause:** Outdated WordPress version -- **Fix:** `wp core update` - -**b) "React version mismatch"** -- **Cause:** Another plugin using old React version -- **Fix:** Deactivate other plugins one by one to find conflict - -**c) "Uncaught SyntaxError"** -- **Cause:** JavaScript file corrupted or not minified correctly -- **Fix:** Rebuild assets: `npm run build` in plugin directory - -**2. Check network tab:** -``` -DevTools → Network -Filter: JS -Look for failed requests (red, 404, 500) -``` - -**Common failures:** -- `form-editor.js` (404) → Rebuild assets -- `chunk-vendors.js` (500) → Server misconfiguration - -**3. Enable SCRIPT_DEBUG:** - -In `wp-config.php`: -```php -define('SCRIPT_DEBUG', true); -``` - -This loads unminified JS, easier to debug. - ---- - -### Blocks Not Appearing in Inserter - -**Symptom:** SureForms blocks missing from block library - -**Diagnosis:** -```bash -# Check if blocks are registered -wp eval "print_r(get_option('srfm_blocks_registration'));" -``` - -**Fixes:** - -**1. Re-register blocks:** -```bash -wp plugin deactivate sureforms -wp plugin activate sureforms -``` - -**2. Clear block cache:** -```bash -wp cache flush -wp transient delete --all -``` - -**3. Check block.json files exist:** -```bash -find wp-content/plugins/sureforms/inc/blocks -name "block.json" -# Should return multiple files -``` - ---- - -### Form Preview Shows "Invalid Block" - -**Symptom:** Form renders as "This block contains unexpected or invalid content" - -**Cause:** Block HTML structure changed, saved form has old structure - -**Fix:** - -**Option 1: Update block (preserves data):** -1. Click "Attempt Block Recovery" -2. Verify content looks correct -3. Update form - -**Option 2: Clear and rebuild:** -1. Delete the invalid block -2. Add fresh block -3. Reconfigure settings - -**Prevention:** -- Always test after plugin updates -- Keep staging environment for testing - ---- - -## Form Submission Issues - -### Form Submissions Not Saving - -**Symptom:** User submits form, success message shows, but no entry in database - -**Diagnosis:** - -**1. Check debug log:** -```bash -tail -f wp-content/debug.log -# Submit form while watching log -``` - -**Common errors:** -- "Database insert failed" → Check table permissions -- "Nonce verification failed" → Caching issue (see below) -- "Call to undefined method" → PHP version or missing dependency - -**2. Check database directly:** -```bash -# Get latest entry -wp db query "SELECT * FROM wp_sureforms_entries ORDER BY id DESC LIMIT 1;" -``` - -**3. Test with minimal form:** -Create form with ONLY: -- Email field -- Submit button - -If this works, issue is with specific field type. - -**Fixes:** - -**Database Permissions:** -```bash -# Check MySQL user has INSERT privilege -wp db query "SHOW GRANTS;" -``` - -**Fix permissions (MySQL):** -```sql -GRANT INSERT, UPDATE, DELETE ON database_name.* TO 'wp_user'@'localhost'; -FLUSH PRIVILEGES; -``` - -**Caching Interference:** - -Caching plugins cache nonce values, causing verification failures. - -**Fix:** -1. Exclude `/wp-admin/admin-ajax.php` from cache -2. Exclude REST API `/wp-json/` from cache -3. Or disable caching temporarily to test - ---- - -### Email Notifications Not Sending - -**Symptom:** Form submitted successfully, but no email received - -**Diagnosis:** - -**1. Check if emails are being sent at all:** -```bash -# Install WP Mail SMTP or similar -wp plugin install wp-mail-smtp --activate - -# Or test with simple command: -wp eval "wp_mail('your@email.com', 'Test', 'Testing SureForms');" -``` - -**2. Check SureForms email settings:** -```bash -# View form meta -wp post meta list | grep email -``` - -**3. Check spam folder** -- WordPress default `from` address: `wordpress@yourdomain.com` -- Often flagged as spam - -**Fixes:** - -**Configure SMTP:** - -Install WP Mail SMTP plugin: -```bash -wp plugin install wp-mail-smtp --activate -``` - -Configure with: -- Gmail -- SendGrid -- Mailgun -- Amazon SES - -**Check email template:** - -In form settings → Email Notification: -- Verify "To" address is correct -- Check "From" address is valid domain -- Test with simple subject/message - -**Server mail() function:** - -Some hosts disable PHP `mail()` function. - -Test: -```bash -php -r "mail('test@example.com', 'Test', 'Test message');" -``` - -If no email received, `mail()` is disabled. Use SMTP. - ---- - -### Form Validation Not Working - -**Symptom:** Form submits even with empty required fields - -**Causes:** - -**1. JavaScript disabled** (frontend validation skipped) -**2. Theme CSS hiding error messages** -**3. Custom JavaScript conflict** - -**Diagnosis:** - -**Check browser console:** -``` -Look for JavaScript errors -Check if srfm-validation.js loaded -``` - -**Test with default theme:** -```bash -wp theme activate twentytwentythree -# Submit form again -``` - -If works with default theme → Theme conflict. - -**Fixes:** - -**Theme conflict:** - -Add to theme's `functions.php`: -```php -add_action('wp_enqueue_scripts', function() { - // Ensure SureForms scripts load - wp_enqueue_script('srfm-frontend'); -}, 20); -``` - -**CSS hiding errors:** - -Check if theme has: -```css -.srfm-error { display: none !important; } -``` - -Remove or override. - ---- - -## Payment Processing Issues - -### Stripe Payments Failing - -**Symptom:** "Payment failed" error after entering card details - -**Diagnosis:** - -**1. Check Stripe API keys:** -```bash -# In WordPress admin: SureForms → Settings → Payments → Stripe -# Verify: -# - Using correct keys (test vs live) -# - Keys match Stripe dashboard -``` - -**2. Check Stripe webhook:** -```bash -# In Stripe Dashboard → Developers → Webhooks -# Verify webhook URL is: -https://yoursite.com/wp-json/sureforms/webhook_test - -# Check recent webhook deliveries for errors -``` - -**3. Check browser console:** -``` -DevTools → Console -Look for Stripe.js errors -``` - -**Common Errors:** - -**"Invalid API Key"** -- Using test key in live mode (or vice versa) -- API key revoked in Stripe dashboard -- Fix: Copy fresh keys from Stripe - -**"Payment Intent creation failed"** -- Amount is $0 or negative -- Currency mismatch -- Fix: Check form configuration, amount field - -**"Webhook signature verification failed"** -- Webhook secret incorrect -- Middleware issue -- Fix: Copy webhook signing secret from Stripe, update settings - -**Fixes:** - -**Test mode checklist:** -```bash -# 1. Use test API keys (starts with pk_test_ / sk_test_) -# 2. Test card: 4242 4242 4242 4242 -# 3. Any future expiry date -# 4. Any 3-digit CVC -``` - -**Live mode checklist:** -```bash -# 1. Use live API keys (starts with pk_live_ / sk_live_) -# 2. SSL certificate valid (https://) -# 3. Webhook verified in Stripe dashboard -# 4. Test with real card (refund immediately) -``` - ---- - -### PayPal Payments Failing (Pro) - -**Symptom:** Redirected to PayPal but payment doesn't process - -**Diagnosis:** - -**Check PayPal credentials:** -```bash -# SureForms → Settings → Payments → PayPal -# Verify: -# - Client ID matches PayPal dashboard -# - Secret matches -# - Using sandbox for testing, live for production -``` - -**Check webhook endpoint:** -```bash -# PayPal Dashboard → Apps & Credentials → Webhooks -# Webhook URL should be: -https://yoursite.com/wp-json/sureforms-pro/paypal-live-webhook -``` - -**Fixes:** - -**Test in sandbox mode first:** -1. Create PayPal sandbox account: https://developer.paypal.com/ -2. Use sandbox credentials in SureForms -3. Test with sandbox buyer account - -**Common issues:** -- **Wrong environment:** Using sandbox credentials in live mode -- **Webhook not subscribed:** Must subscribe to payment events in PayPal dashboard -- **SSL certificate:** PayPal requires valid HTTPS - ---- - -## Integration Issues (Pro) - -### Native Integration Not Connecting - -**Symptom:** "Connection failed" when adding integration (Mailchimp, HubSpot, etc.) - -**Diagnosis:** - -**1. Check OAuth redirect URL:** - -For OAuth integrations (Mailchimp, HubSpot, Salesforce): -``` -Redirect URL must be: -https://yoursite.com/wp-json/sureforms-pro/v1/oauth/callback -``` - -**2. Test API credentials:** - -For API key integrations (Brevo, etc.): -```bash -# Test API key directly -curl -X GET "https://api.brevo.com/v3/account" \ - -H "api-key: YOUR_API_KEY" -``` - -Should return account info, not error. - -**3. Check error logs:** -```bash -tail -f wp-content/debug.log | grep -i "integration\|oauth" -``` - -**Fixes:** - -**OAuth issues:** -- Ensure site uses HTTPS (required for OAuth) -- Whitelist redirect URL in service's developer console -- Check OAuth app has correct permissions/scopes - -**API key issues:** -- Regenerate key in service dashboard -- Copy entire key (no spaces, no quotes) -- Check key has required permissions (read/write) - ---- - -### Webhook Not Firing - -**Symptom:** Form submitted, but data not sent to integrated service - -**Diagnosis:** - -**Check webhook logs:** - -Install Query Monitor plugin: -```bash -wp plugin install query-monitor --activate -``` - -Submit form, check "HTTP API Calls" panel for webhook requests. - -**Manually trigger webhook:** -```bash -# Find integration ID -wp db query "SELECT * FROM wp_sureforms_integrations;" - -# Trigger webhook manually (developer test) -wp eval "do_action('sureforms_after_entry_save', 123, 456, []);" -``` - -**Fixes:** - -**Webhook URL validation:** - -In `inc/pro/integrations/webhooks.php`, ensure URL is valid: -- HTTPS only (no HTTP) -- No localhost (unless testing) -- Responds with 200 OK - -**Timeout issues:** - -Increase timeout in `wp-config.php`: -```php -define('WP_HTTP_BLOCK_EXTERNAL', false); -define('WP_ACCESSIBLE_HOSTS', 'api.mailchimp.com,api.hubspot.com'); -``` - ---- - -## Performance Issues - -### Form Editor Slow to Load - -**Symptom:** Takes 10+ seconds to load form in editor - -**Diagnosis:** - -**1. Check database size:** -```bash -wp db query "SELECT COUNT(*) FROM wp_sureforms_entries;" -# If > 100,000 entries, database may be slow -``` - -**2. Check server resources:** -```bash -# Memory usage -free -h - -# CPU usage -top - -# Disk I/O -iostat -``` - -**3. Profile with Query Monitor:** -```bash -wp plugin install query-monitor --activate -# Open form editor, check QM panel for slow queries -``` - -**Fixes:** - -**Optimize database:** -```bash -# Clean old entries (backup first!) -wp db query "DELETE FROM wp_sureforms_entries WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR);" - -# Optimize tables -wp db optimize -``` - -**Increase PHP limits:** - -In `php.ini` or `.htaccess`: -``` -max_execution_time = 300 -memory_limit = 256M -``` - -**Enable object caching:** - -Install Redis or Memcached: -```bash -wp plugin install redis-cache --activate -wp redis enable -``` - ---- - -### Frontend Form Loads Slowly - -**Symptom:** Form takes 5+ seconds to appear on page - -**Diagnosis:** - -**Check asset loading:** -``` -DevTools → Network → Slow 3G simulation -Watch which assets are slow -``` - -**Common bottlenecks:** -- Google Fonts loading slowly -- Large CSS/JS files -- Unoptimized images in form - -**Fixes:** - -**Lazy load non-critical assets:** - -In `functions.php`: -```php -add_filter('script_loader_tag', function($tag, $handle) { - if ($handle === 'srfm-frontend') { - return str_replace(' src', ' defer src', $tag); - } - return $tag; -}, 10, 2); -``` - -**Use CDN for Google Fonts:** - -In form settings → Design → Typography: -- Limit to 1-2 font families -- Use system fonts for faster load (e.g., -apple-system) - -**Minify and combine assets:** -```bash -npm run build # Ensures assets are minified -``` - -**Enable caching:** - -Install caching plugin: -```bash -wp plugin install wp-super-cache --activate -``` - -Configure to cache pages with forms. - ---- - -## Block Compatibility Issues - -### Form Breaks After Theme Update - -**Symptom:** Form displays incorrectly or not at all after theme update - -**Diagnosis:** - -**Compare theme CSS:** - -Check if new theme has conflicting styles: -```css -/* Common conflicts */ -.srfm-form input { /* Theme override */ } -.srfm-field { /* Theme override */ } -``` - -**Test with default theme:** -```bash -wp theme activate twentytwentythree -``` - -If works → Theme issue. - -**Fixes:** - -**Add theme compatibility CSS:** - -In child theme `style.css`: -```css -/* Reset SureForms blocks */ -.srfm-form, -.srfm-field, -.srfm-form input, -.srfm-form textarea { - all: revert; -} - -/* Then apply minimal SureForms styles */ -``` - -**Use !important (last resort):** - -In SureForms settings → Custom CSS: -```css -.srfm-form input { - border: 1px solid #ccc !important; - padding: 10px !important; -} -``` - ---- - -### Conflicts with Page Builders - -**Symptom:** Forms don't work inside Elementor/Divi/Beaver Builder - -**Common Issues:** - -**1. JavaScript conflicts:** -- Page builder loads own jQuery version -- Conflicts with SureForms scripts - -**Fix:** -```php -// In functions.php -add_action('wp_enqueue_scripts', function() { - if (class_exists('Elementor\Plugin')) { - wp_dequeue_script('jquery'); - wp_enqueue_script('jquery'); - } -}, 100); -``` - -**2. CSS specificity:** -- Page builder CSS overrides SureForms - -**Fix:** - -Use SureForms settings → Custom CSS with higher specificity: -```css -.elementor-widget-container .srfm-form input { - /* Your styles */ -} -``` - ---- - -## Database & Query Issues - -### "Too many connections" Error - -**Symptom:** Site crashes during high form submission volume - -**Diagnosis:** -```bash -wp db query "SHOW STATUS LIKE 'max_used_connections';" -wp db query "SHOW VARIABLES LIKE 'max_connections';" -``` - -**Fixes:** - -**Increase max connections (MySQL):** - -In `my.cnf`: -``` -[mysqld] -max_connections = 500 -``` - -**Use persistent connections:** - -In `wp-config.php`: -```php -define('DB_CHARSET', 'utf8mb4'); -define('DB_COLLATE', ''); -define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_PERSISTENT); -``` - -**Add connection pooling:** - -Use ProxySQL or PgBouncer for connection pooling. - ---- - -### Slow Query: "SELECT * FROM wp_sureforms_entries" - -**Symptom:** Admin page very slow when viewing entries - -**Diagnosis:** -```bash -wp plugin install query-monitor --activate -# View entries page, check QM for slow queries -``` - -**Fixes:** - -**Add database indexes:** -```sql -ALTER TABLE wp_sureforms_entries -ADD INDEX idx_form_id (form_id), -ADD INDEX idx_created_at (created_at), -ADD INDEX idx_status (status); -``` - -**Limit entries displayed:** - -In admin, reduce entries per page from 100 to 20. - -**Paginate large result sets:** - -Ensure code uses `LIMIT` and `OFFSET`: -```php -$entries = Entries::get_all([ - 'limit' => 20, - 'offset' => ($page - 1) * 20 -]); -``` - ---- - -## WordPress.org Review Issues - -### Plugin Rejected for Security - -**Common reasons:** - -**1. Direct database calls without prepare():** -```php -// ❌ Bad -$wpdb->query("DELETE FROM table WHERE id = $id"); - -// ✅ Good -$wpdb->query($wpdb->prepare("DELETE FROM table WHERE id = %d", $id)); -``` - -**2. Unsanitized user input:** -```php -// ❌ Bad -echo $_POST['name']; - -// ✅ Good -echo esc_html(sanitize_text_field($_POST['name'] ?? '')); -``` - -**3. Missing nonce verification:** -```php -// ❌ Bad -if (isset($_POST['action'])) { do_action(); } - -// ✅ Good -if (isset($_POST['action']) && check_ajax_referer('my_action_nonce')) { - do_action(); -} -``` - -**Fix:** - -Run WordPress Coding Standards checker: -```bash -composer require --dev wp-coding-standards/wpcs -vendor/bin/phpcs --standard=WordPress inc/ -``` - -Fix all errors before resubmitting. - ---- - -## Getting Help - -### Before Asking for Help - -**Gather this information:** - -```bash -# 1. WordPress & PHP versions -wp core version -php -v - -# 2. Plugin version -wp plugin list | grep sureforms - -# 3. Active theme -wp theme list --status=active - -# 4. Other active plugins -wp plugin list --status=active - -# 5. Recent errors -tail -50 wp-content/debug.log - -# 6. Browser/OS (if frontend issue) -# Example: Chrome 120 on macOS 14 -``` - -### Where to Get Help - -**1. Documentation** (check first) -- This troubleshooting guide -- [FAQ](faq.md) -- [Architecture](architecture.md) - -**2. GitHub Issues** (bugs & feature requests) -- Search existing issues first -- Provide minimal reproduction steps -- Include system info from above - -**3. Support Portal** (Pro users) -- https://support.brainstormforce.com/ -- < 24hr response time for Pro users - -**4. Community** -- Facebook group: https://www.facebook.com/groups/surecart -- WordPress.org forum (Free only) - ---- - -## Still Stuck? - -If none of the above solutions work: - -**Create detailed bug report:** - -```markdown -**Environment:** -- WordPress: 6.4.2 -- PHP: 8.1 -- SureForms: 2.5.0 (Free/Pro) -- Theme: Astra 4.5.0 -- Browser: Chrome 120 - -**Steps to Reproduce:** -1. Create form with email field -2. Mark field as required -3. Submit form with empty email -4. [Describe unexpected behavior] - -**Expected:** Validation error shows -**Actual:** Form submits anyway - -**Debug Log:** -[Paste relevant errors from debug.log] - -**Screenshots:** -[Attach if visual issue] -``` - -Submit to: https://github.com/brainstormforce/sureforms/issues - ---- - -**Next:** [FAQ](faq.md) diff --git a/internal-docs/ui-and-copy.md b/internal-docs/ui-and-copy.md deleted file mode 100644 index e12a9a45a..000000000 --- a/internal-docs/ui-and-copy.md +++ /dev/null @@ -1,1077 +0,0 @@ -# UI & Copy Guidelines - -**Version:** 2.5.0 - ---- - -## User Experience Philosophy - -**Principle:** Every word and interaction should make the user feel capable, not confused. - -**User Mindset:** -- "I just want to create a form quickly" -- "I don't have time to read documentation" -- "I'm not a developer" - -**Our Responsibility:** -- Make obvious what to do next -- Explain why, not just how -- Never blame the user - ---- - -## User Journeys - -### Journey 1: First-Time User Creates Contact Form - -**Goal:** Create and publish first form in < 5 minutes - -**Steps:** - -**1. Plugin Activation (30 seconds)** - -``` -User activates plugin - ↓ -Redirect to welcome screen - ↓ -Show 2 options: - [Create with AI] [Start from Blank] -``` - -**Copy:** -``` -Welcome to SureForms! - -Let's create your first form. - -[Create with AI] ← Recommended for beginners - Let AI build your form from a simple description - -[Start from Blank] - Build your form block-by-block -``` - -**Design notes:** -- Large, friendly buttons -- "Recommended" badge on AI option -- No walls of text - ---- - -**2. AI Form Creation (2 minutes)** - -``` -User clicks "Create with AI" - ↓ -Modal appears with text input - ↓ -User types: "simple contact form" - ↓ -AI generates form (3-5 seconds) - ↓ -Form opens in editor, ready to publish -``` - -**Copy:** - -``` -Create Form with AI - -Describe the form you need: -┌─────────────────────────────────────┐ -│ Example: "job application with │ -│ file upload" or "event RSVP" │ -└─────────────────────────────────────┘ - -[Cancel] [Generate Form →] -``` - -**Loading state:** -``` -✨ Creating your form... - -AI is adding fields based on your description. -This usually takes 3-5 seconds. -``` - -**Success state:** -``` -✅ Your form is ready! - -We added: -• Name field -• Email field -• Message field -• Submit button - -You can add, remove, or rearrange fields below. - -[Publish Form] -``` - -**Design notes:** -- Loading spinner + encouraging message -- Success message lists what was created -- Clear next action (Publish) - ---- - -**3. Form Publishing (1 minute)** - -``` -User clicks "Publish" - ↓ -Gutenberg publish panel opens - ↓ -SureForms shows additional options -``` - -**Copy in publish panel:** - -``` -📋 Form Settings - -Where should submissions be sent? - -Email: [admin@yoursite.com ▼] - -After submission, show: -○ Confirmation message -○ Redirect to page -● Confirmation message (selected by default) - -Message: -┌─────────────────────────────────────┐ -│ Thank you! We'll respond within │ -│ 24 hours. │ -└─────────────────────────────────────┘ - -[Publish Form] -``` - -**Design notes:** -- Sensible defaults (confirmation message, admin email) -- Inline editing (no separate settings page) -- Jargon-free labels - ---- - -**4. Embedding Form (30 seconds)** - -``` -User wants to add form to page - ↓ -Edit page in Gutenberg - ↓ -Add SureForms block - ↓ -Select form from dropdown -``` - -**Copy in block inserter:** - -``` -SureForms - -Display a form on your page. - -📝 No form yet? Create one in SureForms > Add New -``` - -**Copy in block settings:** - -``` -Form - -Select a form: -[Contact Form ▼] [Create New] - -Display settings: -☑ Show form title -☑ Show form description -☐ Hide labels (show placeholders only) -``` - -**Design notes:** -- Help text guides to form creation if needed -- Checkbox labels explain what they do -- Preview updates in real-time - ---- - -### Journey 2: Pro User Sets Up Payment Form - -**Goal:** Create payment form and collect first payment - -**Complexity:** Higher (involves Stripe setup) - -**Steps:** - -**1. Payment Gateway Setup (5 minutes)** - -``` -User navigates to: -SureForms → Settings → Payments → Stripe -``` - -**Copy:** - -``` -Stripe Payments - -Accept credit card payments with Stripe. - -🔒 Secure: We never store card details. Payments are processed directly by Stripe. - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Test Mode (for testing) - -☑ Enable test mode - -Use these while testing: - Test card: 4242 4242 4242 4242 - Any future expiry, any CVC - -Publishable Key (starts with pk_test_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -Secret Key (starts with sk_test_) -┌─────────────────────────────────────┐ -│ •••••••••••••••••••••••••••••••• │ -└─────────────────────────────────────┘ - -Where to get keys: -→ Stripe Dashboard > Developers > API Keys - https://dashboard.stripe.com/apikeys - -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Live Mode (for real payments) - -⚠️ Only use after testing - -Publishable Key (starts with pk_live_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -Secret Key (starts with sk_live_) -┌─────────────────────────────────────┐ -│ │ -└─────────────────────────────────────┘ - -[Save Settings] -``` - -**Design notes:** -- Test mode emphasized first -- Clear instructions where to get keys -- Security reassurance ("We never store") -- Direct link to Stripe dashboard - ---- - -**2. Creating Payment Form (3 minutes)** - -``` -User creates new form - ↓ -Adds "Payment" block - ↓ -Configures amount and currency -``` - -**Copy in Payment block settings:** - -``` -Payment Details - -Amount -○ Fixed amount - ┌──────┐ - │ 50 │ USD ▼ - └──────┘ - -● Let user choose amount - Min: [10] Max: [1000] USD ▼ - Default: [50] - -Payment Type -● One-time payment -○ Subscription - Interval: [Monthly ▼] - -Button Text -┌─────────────────────────────────────┐ -│ Pay Now │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Radio buttons for mutually exclusive options -- Currency dropdown next to amount -- Clear labels (no "recurring" jargon, use "subscription") - ---- - -**3. Testing Payment (2 minutes)** - -**Copy on frontend form:** - -``` -Payment Information - -Amount: $50.00 - -Card Number -┌─────────────────────────────────────┐ -│ 1234 5678 9012 3456 │ [Card icon] -└─────────────────────────────────────┘ - -Expiry CVC -┌──────────┐ ┌─────┐ -│ MM / YY │ │ 123 │ -└──────────┘ └─────┘ - -🔒 Secure payment powered by Stripe - Your card details are encrypted and never stored. - -[Pay $50.00] -``` - -**Success message after payment:** - -``` -✅ Payment Successful! - -Receipt sent to: john@example.com - -Transaction ID: ch_1A2B3C4D5E6F - -[View Receipt] [Back to Home] -``` - -**Error message if payment fails:** - -``` -❌ Payment Failed - -Your card was declined. - -Common reasons: -• Insufficient funds -• Incorrect card number or expiry date -• Card requires 3D Secure verification - -Please try again or use a different card. - -[Try Again] -``` - -**Design notes:** -- Security reassurance prominent -- Success message includes receipt email and transaction ID -- Error message explains why and how to fix - ---- - -### Journey 3: Power User Builds Conditional Logic Form (Pro) - -**Goal:** Show/hide fields based on user selection - -**Example:** Event RSVP with dietary restrictions (only show if attending) - -**Steps:** - -**1. Creating Base Form (2 minutes)** - -``` -User adds fields: - - "Will you attend?" (Multiple Choice: Yes/No) - - "Dietary restrictions" (Dropdown) -``` - -**2. Adding Conditional Logic (3 minutes)** - -``` -User clicks "Dietary restrictions" block - ↓ -Opens block settings panel - ↓ -Enables conditional logic -``` - -**Copy in block settings:** - -``` -Conditional Logic - -Show this field only when certain conditions are met. - -☑ Enable conditional logic - -Show this field when: - -[Will you attend? ▼] [is ▼] [Yes ▼] - -[+ Add Condition] - -Logic: -● Show if ALL conditions match (AND) -○ Show if ANY condition matches (OR) -``` - -**Design notes:** -- Toggle to enable -- Dropdown-based condition builder (no code) -- Visual feedback: field grays out in editor preview when hidden - ---- - -**3. Testing Logic (1 minute)** - -**Copy in editor preview mode:** - -``` -💡 Preview Mode - -This is how your form will appear to users. - -Try selecting different options to see conditional logic in action. - -[Exit Preview] -``` - -**Behavior:** -- User selects "Yes" → Dietary field appears -- User selects "No" → Dietary field disappears - -**Design notes:** -- Clear indication of preview mode -- Real-time updates (no reload) - ---- - -## Microcopy Guidelines - -### Field Labels - -**Be concise and conversational** - -❌ Bad: "Please enter your electronic mail address" -✅ Good: "Email" - -❌ Bad: "Input telephone number (optional)" -✅ Good: "Phone (optional)" - -**Use sentence case, not title case** - -❌ Bad: "First Name" -✅ Good: "First name" - -❌ Bad: "Company Name (If Applicable)" -✅ Good: "Company name (if applicable)" - ---- - -### Help Text - -**Explain why or provide examples** - -**Email field:** -❌ Bad: "Enter email" -✅ Good: "We'll send a confirmation to this email" - -**Phone field:** -❌ Bad: "Phone number" -✅ Good: "We'll only call if there's an issue with your order" - -**File upload:** -❌ Bad: "Upload file" -✅ Good: "Upload your resume (PDF or DOCX, max 10MB)" - ---- - -### Error Messages - -**Be specific and actionable** - -**Email validation:** -❌ Bad: "Invalid email" -✅ Good: "Please enter a valid email (e.g., name@example.com)" - -**Required field:** -❌ Bad: "This field is required" -✅ Good: "Please enter your name" - -**File size:** -❌ Bad: "File too large" -✅ Good: "File must be under 10MB. Yours is 15MB. Try compressing it." - -**Payment failed:** -❌ Bad: "Transaction error" -✅ Good: "Your card was declined. Please check your card details or try a different card." - ---- - -### Success Messages - -**Be enthusiastic but not over-the-top** - -**Form submission:** -❌ Bad: "Form submitted" -✅ Good: "Thanks! We'll get back to you within 24 hours." - -**Payment successful:** -❌ Bad: "Payment processed successfully. Transaction ID: 1234." -✅ Good: "✅ Payment received! Receipt sent to your email." - -**Registration:** -❌ Bad: "Account created. Please log in." -✅ Good: "Welcome! Check your email to verify your account." - ---- - -### Button Labels - -**Use verbs that describe the action** - -❌ Bad: "Submit" -✅ Good: "Send Message" - -❌ Bad: "Click Here" -✅ Good: "Download Receipt" - -❌ Bad: "Proceed" -✅ Good: "Continue to Payment" - -**For payment buttons, include amount** - -❌ Bad: "Pay" -✅ Good: "Pay $50.00" - -❌ Bad: "Subscribe" -✅ Good: "Subscribe for $9.99/month" - ---- - -### Settings & Options - -**Explain consequences, not just features** - -**Email notification:** -❌ Bad: "Send email notification" -✅ Good: "Email me when someone submits this form" - -**Required field:** -❌ Bad: "Required" -✅ Good: "Make this field required" (checkbox label) - -**Conditional logic:** -❌ Bad: "Enable conditional logic" -✅ Good: "Show or hide this field based on other answers" - ---- - -## Visual Design Patterns - -### Empty States - -**When user has no forms yet:** - -``` -┌─────────────────────────────────────────┐ -│ │ -│ 📝 │ -│ │ -│ No forms yet │ -│ │ -│ Forms help you collect information │ -│ from your website visitors. │ -│ │ -│ [Create Your First Form] │ -│ │ -└─────────────────────────────────────────┘ -``` - -**Copy principles:** -- Icon relevant to context -- 1-sentence explanation -- Clear call-to-action button - ---- - -**When form has no submissions:** - -``` -┌─────────────────────────────────────────┐ -│ │ -│ 📭 │ -│ │ -│ No submissions yet │ -│ │ -│ Share this form to start collecting │ -│ responses. │ -│ │ -│ [Copy Form Link] [Embed on Page] │ -│ │ -└─────────────────────────────────────────┘ -``` - -**Design notes:** -- Actionable next steps -- Multiple options (link vs embed) - ---- - -### Loading States - -**Form submission in progress:** - -``` -┌─────────────────────────────────────┐ -│ ⏳ Sending... │ -│ │ -│ Please don't close this page. │ -└─────────────────────────────────────┘ -``` - -**AI form generation:** - -``` -┌─────────────────────────────────────┐ -│ ✨ Creating your form... │ -│ │ -│ [████████░░] 80% │ -│ │ -│ Adding email validation... │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Spinner or progress indicator -- Explain what's happening -- Show progress if possible - ---- - -### Confirmation Dialogs - -**Deleting a form:** - -``` -┌─────────────────────────────────────┐ -│ Delete "Contact Form"? │ -│ │ -│ This will permanently delete: │ -│ • The form │ -│ • 47 submissions │ -│ • All settings │ -│ │ -│ This cannot be undone. │ -│ │ -│ [Cancel] [Delete Form] │ -└─────────────────────────────────────┘ -``` - -**Design notes:** -- Specific about what will be deleted -- Use red/destructive style for delete button -- Cancel button should be default (easier to accidentally click) - ---- - -### Tooltips & Hints - -**When to use:** -- Explaining technical terms -- Providing examples -- Showing keyboard shortcuts - -**Format:** - -``` -Field Name [?] - ↓ (on hover) -┌─────────────────────────────────────┐ -│ This is the internal name used in │ -│ the database. │ -│ │ -│ Example: "first_name" │ -└─────────────────────────────────────┘ -``` - -**Guidelines:** -- Keep under 2 sentences -- Provide example if helpful -- Don't repeat the label - ---- - -## Accessibility - -### Screen Reader Text - -**Form structure:** - -```html -
-
-

Personal Information

- -
-
-``` - -**Error announcements:** - -```html -
- Please fix 2 errors before submitting. -
- - - - Please enter a valid email address - -``` - ---- - -### Keyboard Navigation - -**Required interactions:** -- Tab through all fields -- Space to toggle checkboxes/radio buttons -- Enter to submit form -- Escape to close modals - -**Visual focus indicators:** - -```css -.srfm-field input:focus { - outline: 2px solid #0073aa; - outline-offset: 2px; -} -``` - -**Never:** -```css -:focus { - outline: none; /* ❌ Never remove focus outline */ -} -``` - ---- - -### Color Contrast - -**WCAG 2.1 Level AA Requirements:** -- Normal text: 4.5:1 minimum -- Large text (18pt+): 3:1 minimum -- UI components: 3:1 minimum - -**Test tools:** -- WebAIM Contrast Checker -- Chrome DevTools (Lighthouse) - -**Safe color combinations:** -- White text on dark backgrounds (#333 or darker) -- Dark text on light backgrounds (#F0F0F0 or lighter) -- Avoid gray text on gray backgrounds - ---- - -## Tone & Voice - -### Voice Characteristics - -**Friendly but professional** -- Use "we" and "you" -- Conversational, not formal -- Helpful, not condescending - -**Example:** -❌ Formal: "The system has detected an error in your input." -✅ Friendly: "Oops! We couldn't save that. Check for any errors above." - ---- - -**Clear over clever** -- Avoid puns and wordplay -- Be direct -- Technical accuracy over marketing fluff - -**Example:** -❌ Clever: "Houston, we have a problem!" -✅ Clear: "Something went wrong. Please try again." - ---- - -**Empowering, not blaming** -- Focus on solutions, not problems -- Use "we" for errors, "you" for successes - -**Example:** -❌ Blaming: "You entered an invalid email." -✅ Empowering: "Hmm, that email doesn't look quite right. Mind double-checking it?" - ---- - -### Writing for Different Contexts - -**First-time users:** -- More explanation -- Examples provided -- Encouraging tone - -**Example:** -``` -Welcome to SureForms! - -Creating your first form is easy. We'll walk you through each step. - -Let's start by choosing what kind of form you need. -``` - ---- - -**Power users:** -- Less explanation -- Assume knowledge -- Efficiency-focused - -**Example:** -``` -Advanced Settings - -Show field if: [condition builder] -Custom CSS class: [input] -``` - ---- - -**Error states:** -- Apologetic but not dramatic -- Specific about what went wrong -- Clear next steps - -**Example:** -``` -We couldn't save your changes. - -The connection to the server timed out. - -Please try again. If this keeps happening, check your internet connection. - -[Try Again] -``` - ---- - -**Success states:** -- Positive reinforcement -- What happens next -- Optional next action - -**Example:** -``` -✅ Form published! - -Your form is now live at: -https://yoursite.com/contact - -[View Form] [Create Another] -``` - ---- - -## Form Field Best Practices - -### Required vs Optional - -**Default:** Make fields optional - -**Require only when absolutely necessary:** -- Name (if sending personalized response) -- Email (if you need to contact them) -- Payment details (if collecting payment) - -**Label optional fields:** -``` -Phone (optional) -Company name (optional) -``` - -**Don't label required fields:** -❌ Bad: "Email (required)" -✅ Good: "Email" (with * indicator) - ---- - -### Field Order - -**Logical flow:** -1. Personal info (Name, Email) -2. Specific questions -3. Payment details (if applicable) -4. Submit button - -**Example contact form:** -``` -1. Name -2. Email -3. Subject -4. Message -5. [Submit] -``` - -**Example order form:** -``` -1. Name -2. Email -3. Product selection -4. Quantity -5. Payment details -6. [Complete Purchase] -``` - ---- - -### Placeholder Text - -**Use sparingly** - -Only for examples, not as labels: - -❌ Bad: -``` -Label: [empty] -Placeholder: "Enter your email" -``` - -✅ Good: -``` -Label: Email -Placeholder: "name@example.com" -``` - -**Never use placeholders as only label** (accessibility issue) - ---- - -### Multi-Step Forms (Pro) - -**Step indicators:** - -``` -Step 1 of 3: Your Information -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -●━━━━━━━━━━━━○━━━━━━━━━━━━━○ - -[Fields here] - -[Continue to Step 2 →] -``` - -**Navigation:** -- Show progress (Step 1 of 3) -- Visual progress bar -- Allow back navigation (not just forward) - -**Button labels:** -- Step 1: "Continue" (not "Next") -- Step 2: "Continue" -- Step 3: "Submit Form" (or "Complete Order") - ---- - -## Internationalization (i18n) - -### Text Translation - -**All user-facing strings must be translatable:** - -```php -// ✅ Good -__('Email', 'sureforms'); -_e('Submit Form', 'sureforms'); - -// ❌ Bad -echo 'Email'; // Hardcoded English -``` - -**Placeholders for dynamic content:** - -```php -// ✅ Good -sprintf(__('You have %d new submissions', 'sureforms'), $count); - -// ❌ Bad -echo "You have $count new submissions"; -``` - ---- - -### Date & Number Formatting - -**Use WordPress functions:** - -```php -// Dates -echo date_i18n(get_option('date_format'), $timestamp); - -// Numbers -echo number_format_i18n($number); - -// Currency -echo '$' . number_format_i18n($amount, 2); -``` - ---- - -### RTL Support - -**CSS for right-to-left languages:** - -```css -/* Use logical properties */ -.srfm-field { - margin-inline-start: 10px; /* Not margin-left */ - padding-inline-end: 20px; /* Not padding-right */ -} -``` - -**Test with RTL languages:** -- Arabic -- Hebrew -- Persian - ---- - -## Quality Checklist - -Before shipping UI copy: - -- [ ] Spell check passed -- [ ] Grammar correct -- [ ] Tone matches guidelines -- [ ] Actionable error messages -- [ ] Examples provided where helpful -- [ ] Accessibility: screen reader friendly -- [ ] Accessibility: color contrast ≥ 4.5:1 -- [ ] Keyboard navigation works -- [ ] i18n: All strings translatable -- [ ] RTL languages supported -- [ ] Tested with real users (if major UI) - ---- - -**Next:** [FAQ](faq.md) diff --git a/tests/play/specs/CLAUDE.md b/tests/play/specs/CLAUDE.md deleted file mode 100644 index 113b91abd..000000000 --- a/tests/play/specs/CLAUDE.md +++ /dev/null @@ -1,324 +0,0 @@ -# Playwright E2E Specs — Writing Guide - -Rules and patterns for writing reliable SureForms E2E tests. Each section is a lesson learned from real failures. - ---- - -## Auth: Never do a full form-login inside tests - -**Pattern:** `globalSetup` logs in once and saves `storageState.json`. All workers restore it. - -**Why:** Multiple workers calling the WordPress login form simultaneously causes nonce collisions — WordPress redirects to `?reauth=1`, breaking every test in those workers. - -**Correct:** Let `storageState` handle auth. The `loginAsAdmin` util now just navigates to `/wp-admin` and relies on the pre-loaded cookies. - -```js -// DO — storageState is already loaded by playwright.config.js -test.beforeEach(async ({ page }) => { - await loginAsAdmin(page); // navigates to /wp-admin, skips login form -}); - -// DON'T — never re-do a full form login inside specs -await page.goto('/wp-login.php'); -await page.fill('#user_login', 'admin'); -``` - ---- - -## Adding blocks: sidebar vs programmatic insertion - -**Only these blocks appear in the quick-action sidebar by default:** -`input`, `email`, `textarea`, `checkbox`, `number`, `inline-button`, `advanced-heading`, `payment` - -**All other blocks** (`phone`, `url`, `multi-choice`, `dropdown`, `gdpr`, `rating`, `date`, etc.) **must be inserted programmatically.** - -Use `addFieldBlock(page, 'slug')` from `formHelpers.js` — it automatically routes sidebar blocks via click and all other blocks via `insertBlock`. - -```js -// This handles routing automatically -await addFieldBlock(page, 'gdpr'); // programmatic -await addFieldBlock(page, 'input'); // sidebar click -await addFieldBlock(page, 'dropdown'); // programmatic -``` - -**Never** try to open the WP block inserter toolbar to add non-sidebar blocks — it is fragile and frequently breaks tests. - ---- - -## WP 6.9+ iframe editor: no `data-type`, use data store - -WP 6.9 renders the block editor inside an `iframe[name="editor-canvas"]` and **removed the `data-type` attribute** from block wrapper elements. - -**Never** use `page.locator('.wp-block[data-type="srfm/..."]')` — it will find nothing. - -To **select a block** (e.g. to open its settings panel), use `selectBlock(page, 'slug')` from `formHelpers.js`. It dispatches `selectBlock` via the Gutenberg data store: - -```js -const { selectBlock } = require('../utils/formHelpers'); - -await addFieldBlock(page, 'email'); -await selectBlock(page, 'email'); // selects the block via data store -await openBlockSettingsTab(page); // now the sidebar shows this block's settings -``` - -To **verify a block exists**, `addFieldBlock` already checks via `wp.data.select('core/block-editor').getBlocks()` — no need for a separate DOM assertion. - ---- - -## Field CSS selectors - -Always use the most specific class. Do not rely on element order (`.nth(N)`) unless you are certain only N elements exist. - -| Field | Frontend input selector | -|---|---| -| Single line input | `input.srfm-input-input` | -| Email | `input.srfm-input-email` | -| Email confirm | `input.srfm-input-email-confirm` — **NOT** `input.srfm-input-email` | -| Phone | `input.srfm-input-phone` | -| Number | `input.srfm-input-number` | -| URL | `input.srfm-input-url` | -| Textarea | `textarea.srfm-textarea-text` | -| Checkbox | `input[type="checkbox"]` inside `.srfm-checkbox-input-wrap` | -| Submit button | `#srfm-submit-btn` | -| Success box | `.srfm-success-box` | -| Error message | `.srfm-error-message` | - ---- - -## TomSelect dropdowns - -The SureForms dropdown field uses TomSelect. The native ``. Filling it simulates a bot: - -```js -await page.evaluate(() => { - const f = document.querySelector('[name="srfm-honeypot-field"]'); - if (f) f.value = 'spambot'; -}); -``` - -Server returns `wp_send_json_error` → `.srfm-success-box` must NOT appear. - -## Helpful utilities - -All utilities live in `tests/play/utils/`. - -| Utility | Purpose | -|---|---| -| `loginAsAdmin(page)` | Navigates to `/wp-admin`, restores auth from storageState | -| `createBlankForm(page)` | Creates a new blank SureForms form in the editor | -| `addFieldBlock(page, slug)` | Adds a field block (sidebar or programmatic) | -| `selectBlock(page, slug)` | Selects a block in the editor via the data store (iframe-safe) | -| `publishFormAndGetURL(page)` | Publishes the current form and returns its front-end URL | -| `openFormSettingsDialog(page, navItem)` | Opens Form Behavior dialog to a specific nav item | -| `setFormTitle(page, title)` | Sets the editor post title via `editPost` (avoids fragile UI title input) | -| `createWPPage(page, title, content)` | Creates a published WP page via REST API | - -When a new utility is needed, add it here rather than inlining complex selectors in spec files. diff --git a/tests/play/specs/TODO.md b/tests/play/specs/TODO.md deleted file mode 100644 index fd594df9a..000000000 --- a/tests/play/specs/TODO.md +++ /dev/null @@ -1,244 +0,0 @@ -# E2E Test Plan — SureForms Free Plugin - -Track progress for all Playwright test cases. -**Update this file every time a test is added or a test ID changes.** - -Last updated: 2026-03-16 -Branch convention: `test/e2e-p{N}-test-cases` - ---- - -## How to update this file - -- When you **add a test**, tick its checkbox and fill in the `Spec file` column. -- When you **add a new test ID** that wasn't listed here, add a row in the correct section. -- Fix the `Last updated` date above. - ---- - -## Coverage status key - -| Symbol | Meaning | -|---|---| -| ✅ | Implemented and passing | -| ⏭️ | Implemented but skipped (requires external keys / infra) | -| ❌ | Not yet implemented | -| 🔢 | Implemented but test ID needs renumbering | - ---- - -## Group 1 — Field types (basic submit) - -All tests live in `field-types.spec.js`. -Single-line input and email are covered in `form-creation-submission.spec.js` (not repeated here). - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 1.1 | Textarea — submit multi-line text | ✅ | field-types.spec.js | -| 1.2 | Number — submit a valid number | ✅ | field-types.spec.js | -| 1.3 | Phone — submit a valid phone number | ✅ | field-types.spec.js | -| 1.4 | URL — submit a valid URL | ✅ | field-types.spec.js | -| 1.5 | Checkbox — check and submit | ✅ | field-types.spec.js | -| 1.6 | Multi-choice (radio) — pick one option and submit | ✅ | field-types.spec.js | -| 1.7 | Multi-choice (multi-select) — pick multiple options and submit | ✅ | field-types.spec.js | -| 1.8 | Dropdown — select one option and submit | ✅ | field-types.spec.js | -| 1.9 | *(reserved — inline-button / advanced-heading behaviour TBD)* | ❌ | — | -| 1.10 | GDPR — check consent and submit | ✅ | field-types.spec.js | - ---- - -## Group 2 — Field validation - -All tests live in `field-validation.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 2.1 | Email — invalid format shows error | ✅ | field-validation.spec.js | -| 2.2 | Email confirmation — mismatch shows error | ✅ | field-validation.spec.js | -| 2.3 | Email confirmation — matching emails submit successfully | ✅ | field-validation.spec.js | -| 2.4 | URL — invalid format shows error | ✅ | field-validation.spec.js | -| 2.5 | Number — value below minimum shows error | ✅ | field-validation.spec.js | -| 2.6 | Number — value above maximum shows error | ✅ | field-validation.spec.js | -| 2.7 | Required single-line input — submit empty shows error | ✅ | field-validation.spec.js | -| 2.8 | Required email — submit empty shows error | ✅ | field-validation.spec.js | -| 2.9 | Required phone — submit empty shows error | ✅ | field-validation.spec.js | -| 2.10 | Required textarea — submit empty shows error | ✅ | field-validation.spec.js | -| 2.11 | Required checkbox — submit unchecked shows error | ✅ | field-validation.spec.js | -| 2.12 | Required GDPR — submit without consent shows error | ✅ | field-validation.spec.js | -| 2.13 | Required dropdown — submit without selection shows error | ✅ | field-validation.spec.js | -| 2.14 | Required multi-choice — submit without selection shows error | ✅ | field-validation.spec.js | -| 2.15 | Character limit — input/textarea rejects text over max-chars | ❌ | — | - ---- - -## Group 3 — Form settings (confirmation behaviour) - -All tests live in `form-settings.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 3.1 | Redirect on submission — user lands on configured URL | ✅ | form-settings.spec.js | -| 3.2 | Custom success message — configured text shown after submit | ✅ | form-settings.spec.js | -| 3.3 | Store entries disabled — submission succeeds but no entry stored | ✅ | form-settings.spec.js | -| 3.4 | Page confirmation type — redirect to a configured WordPress page | ✅ | form-settings.spec.js | -| 3.5 | Store entries enabled (default) — entry appears in admin after submit | ✅ | form-settings.spec.js | -| 3.6 | Per-form GDPR compliance: do-not-store-entries enabled → no entry created | ✅ | form-settings.spec.js (covered by 3.3) | -| 3.7 | Per-form compliance: auto-delete setting saves and is visible in editor | ❌ | — | - ---- - -## Group 4 — Entries management - -All tests live in `entries.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 4.1 | Submitted entry appears in the admin entries list | ✅ | entries.spec.js | -| 4.2 | Entry detail contains the correct submitted field values | ✅ | entries.spec.js | -| 4.3 | Bulk delete removes selected entries | ✅ | entries.spec.js | -| 4.4 | CSV export downloads a file | ✅ | entries.spec.js | -| 4.5 | Mark entry as read / unread — status updates in the admin list | ✅ | entries.spec.js | - ---- - -## Group 5 — Form embedding - -All tests live in `form-embedding.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 5.1 | Shortcode-embedded form renders on a page | ✅ | form-embedding.spec.js | -| 5.2 | Shortcode-embedded form submits successfully | ✅ | form-embedding.spec.js | -| 5.3 | Gutenberg block-embedded form renders on a page | ✅ | form-embedding.spec.js | -| 5.4 | Gutenberg block-embedded form submits successfully | ✅ | form-embedding.spec.js | - ---- - -## Group 6 — Email notifications - -All tests live in `email-notifications.spec.js`. -All tests are currently skipped — require MailHog wired into the wp-env Docker setup. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 6.1 | Admin notification email is sent after form submission | ⏭️ | email-notifications.spec.js | -| 6.2 | Notification email body contains submitted field values | ⏭️ | email-notifications.spec.js | -| 6.3 | User confirmation email is sent to submitter's address | ❌ | — | - ---- - -## Group 7 — CAPTCHA - -All tests live in `captcha.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 7.2 | reCAPTCHA v2 — submit without solving is blocked | ⏭️ | captcha.spec.js | -| 7.3 | Cloudflare Turnstile — submit without solving is blocked | ❌ | — | -| 7.4 | hCaptcha — submit without solving is blocked | ❌ | — | -| 7.5 | reCAPTCHA v3 — submit with low score is blocked | ❌ | — | -| 7.7 | CAPTCHA disabled — form submits normally without CAPTCHA widget | ✅ | captcha.spec.js | - -> **Note:** 7.3–7.5 require external API keys; add them with `test.skip` pattern matching 7.2. - ---- - -## Group 8 — Payments (Stripe) - -All tests live in `payments.spec.js`. -All tests are currently skipped — require Stripe test keys. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 8.1 | Payment field renders the Stripe card widget | ⏭️ | payments.spec.js | -| 8.2 | Valid Stripe test card — payment succeeds and success message shown | ⏭️ | payments.spec.js | -| 8.3 | Declined Stripe test card — error message shown | ⏭️ | payments.spec.js | -| 8.6 | Successful payment creates an entry in the admin entries list | ⏭️ | payments.spec.js | - ---- - -## Group 9 — Global settings - -All tests live in `global-settings.spec.js`. - -| ID | Test case | Status | Spec file | -|-----|-----------|--------|-----------| -| 9.1 | Global settings page loads without a JS error | ✅ | global-settings.spec.js | -| 9.2 | All main settings tabs are clickable and stay on settings page | ✅ | global-settings.spec.js | - ---- - -## Group 10 — Spam protection - -All tests live in `spam-protection.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 10.1 | Honeypot — bot fills hidden field → submission is blocked | ✅ | spam-protection.spec.js | -| 10.2 | Double-submit prevention — submit button disabled after first click | ✅ | spam-protection.spec.js | - ---- - -## Group 11 — Form restrictions - -All tests live in `form-restrictions.spec.js`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 11.1 | Entry limit — form shows restriction message after limit reached | ✅ | form-restrictions.spec.js | -| 11.2 | Scheduling — restriction message shown when schedule has ended | ✅ | form-restrictions.spec.js | -| 11.3 | Scheduling — restriction message shown when schedule has not started yet | ✅ | form-restrictions.spec.js | - ---- - -## Group 12 — Form lifecycle - -All tests live in `form-lifecycle.spec.js`. - -> **✅ Renaming done:** Comments in `form-lifecycle.spec.js` updated from `8.x` to `12.x`. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 12.1 | Duplicate form — copy appears in forms list with "(Copy)" suffix | ✅ | form-lifecycle.spec.js | -| 12.2 | Trash form — form disappears from All Forms view | ✅ | form-lifecycle.spec.js | -| 12.3 | Restore form — form reappears in All Forms view after restore from Trash | ✅ | form-lifecycle.spec.js | -| 12.4 | Delete permanently — form is fully removed and not restorable | ✅ | form-lifecycle.spec.js | - ---- - -## Group 13 — AI form generation - -All tests will live in `ai-form-builder.spec.js`. -Tests that require an API key should follow the `test.skip` pattern used in payments and CAPTCHA. - -| ID | Test case | Status | Spec file | -|------|-----------|--------|-----------| -| 13.1 | AI generation panel opens from the "Create Form" flow | ❌ | — | -| 13.2 | Generating a form with a prompt produces at least one block | ❌ | — | - ---- - -## Suggested implementation order - -Work these in priority order — top of each section first: - -### Next up (P1 — no external dependencies) - -- [x] **11.3** Scheduling not-started-yet *(done — form-restrictions.spec.js)* -- [x] **12.4** Delete permanently from trash *(done — form-lifecycle.spec.js)* -- [x] **2.7–2.10** Required validation for remaining field types *(done — field-validation.spec.js)* -- [x] **4.5** Entry read/unread status toggle *(done — entries.spec.js)* -- [x] **3.6** Per-form do-not-store-entries compliance *(already covered by test 3.3)* -- [ ] **2.15** Character limit — low E2E value (browser `maxlength` attribute, no server-side check); defer unless a custom error message is confirmed - -### Later (P2 — needs infra or API keys, use skip pattern) - -- [ ] **6.3** User confirmation email (needs MailHog) -- [ ] **7.3–7.5** Additional CAPTCHA types (need external keys) -- [ ] **13.1–13.2** AI form generation (needs AI API key) - -### Nice to have - -- [ ] **3.7** Auto-delete entries compliance setting -- [ ] **9.x** Global settings save (email From Name/From Email) -- [ ] **1.9** Inline button / advanced heading block behaviour