| @@ -749,8 +749,43 @@ | ||
| 749 | 749 | ); |
| 750 | 750 | } |
| 751 | 751 | |
| 752 | 752 | /** |
| 753 | + * Resolve the submitting user, surviving REST's nonce-less de-authentication. | |
| 754 | + * | |
| 755 | + * The public form endpoints authenticate with the HMAC Submit_Token rather than | |
| 756 | + * a nonce, because the form markup is page-cacheable and core answers a nonce | |
| 757 | + * that fails verification with a hard 403 — a value baked into a cached page | |
| 758 | + * would break submissions once it aged out. | |
| 759 | + * | |
| 760 | + * The trade-off is that `rest_cookie_check_errors()` treats a cookie-carrying | |
| 761 | + * REST request with no nonce as anonymous and calls `wp_set_current_user( 0 )` | |
| 762 | + * before dispatch. So `get_current_user_id()` returns 0 during a submission even | |
| 763 | + * when the visitor is signed in, which silently drops entry attribution and | |
| 764 | + * blanks every `{user_*}` smart tag. | |
| 765 | + * | |
| 766 | + * `wp_validate_auth_cookie()` reads the logged-in cookie directly and is | |
| 767 | + * unaffected by that reset. It verifies the cookie's HMAC, so the identity is | |
| 768 | + * authenticated, not merely asserted — this is the same check core itself uses | |
| 769 | + * for cookie auth, and the pattern already used by the Pro login route. | |
| 770 | + * | |
| 771 | + * Returns 0 for genuinely anonymous submissions, so callers can keep treating | |
| 772 | + * falsy as "not logged in". | |
| 773 | + * | |
| 774 | + * @since 2.12.6 | |
| 775 | + * @return int User ID, or 0 when the submitter is not signed in. | |
| 776 | + */ | |
| 777 | + public static function get_submitting_user_id() { | |
| 778 | + $user_id = get_current_user_id(); | |
| 779 | + | |
| 780 | + if ( $user_id ) { | |
| 781 | + return $user_id; | |
| 782 | + } | |
| 783 | + | |
| 784 | + return absint( wp_validate_auth_cookie( '', 'logged_in' ) ); | |
| 785 | + } | |
| 786 | + | |
| 787 | + /** | |
| 753 | 788 | * Check if the current user has a given capability. |
| 754 | 789 | * |
| 755 | 790 | * @param string $capability The capability to check. |
| 756 | 791 | * @param array<mixed> $args Optional. Additional arguments to pass to the capability check. |
| @@ -2108,8 +2143,29 @@ | ||
| 2108 | 2143 | return defined( 'SRFM_PRO_VER' ); |
| 2109 | 2144 | } |
| 2110 | 2145 | |
| 2111 | 2146 | /** |
| 2147 | + * Whether SureForms promotional content should be hidden. | |
| 2148 | + * | |
| 2149 | + * Covers review requests, cross-sell banners and announcements. The free | |
| 2150 | + * plugin never hides them on its own; SureForms Pro's Distraction Free mode | |
| 2151 | + * turns this on through the filter. | |
| 2152 | + * | |
| 2153 | + * @since 2.12.8 | |
| 2154 | + * @return bool | |
| 2155 | + */ | |
| 2156 | + public static function hide_promotions() { | |
| 2157 | + /** | |
| 2158 | + * Filter whether SureForms hides its promotional content in wp-admin. | |
| 2159 | + * | |
| 2160 | + * @since 2.12.8 | |
| 2161 | + * | |
| 2162 | + * @param bool $hide Default false. | |
| 2163 | + */ | |
| 2164 | + return (bool) apply_filters( 'srfm_hide_promotions', false ); | |
| 2165 | + } | |
| 2166 | + | |
| 2167 | + /** | |
| 2112 | 2168 | * Verifies the request by checking the nonce and user capabilities. |
| 2113 | 2169 | * |
| 2114 | 2170 | * @param string $request_type The type of request, either 'rest' or 'ajax'. |
| 2115 | 2171 | * @param string $nonce_action The action name for the nonce. |
| @@ -2174,8 +2230,62 @@ | ||
| 2174 | 2230 | return implode( '-', array_slice( explode( '-', explode( '-lbl-', $field_name )[0] ), 0, 2 ) ); |
| 2175 | 2231 | } |
| 2176 | 2232 | |
| 2177 | 2233 | /** |
| 2234 | + * The active caching plugin, if there is one. | |
| 2235 | + * | |
| 2236 | + * Caching matters to SureForms because a cached page serves the same HTML to | |
| 2237 | + * everyone: the submission token is embedded at render time, and an | |
| 2238 | + * aggressively cached or JS-combining setup can serve a stale token or reorder | |
| 2239 | + * the scripts a form depends on. This is what surfaces that to the site owner | |
| 2240 | + * before it turns into "my form stopped working". | |
| 2241 | + * | |
| 2242 | + * @since 2.12.6 | |
| 2243 | + * @return string Human-readable plugin name, or '' when none is active. | |
| 2244 | + */ | |
| 2245 | + public static function get_active_caching_plugin() { | |
| 2246 | + $entry = self::get_active_caching_plugin_entry(); | |
| 2247 | + | |
| 2248 | + return null === $entry ? '' : $entry[0]; | |
| 2249 | + } | |
| 2250 | + | |
| 2251 | + /** | |
| 2252 | + * Setup guide for the active caching plugin. | |
| 2253 | + * | |
| 2254 | + * Six of the recognised plugins have a guide of their own; the rest, and any | |
| 2255 | + * site with none detected, get the general one. Sending someone to a page that | |
| 2256 | + * names the plugin they actually run is the difference between advice they can | |
| 2257 | + * follow and advice they have to translate. | |
| 2258 | + * | |
| 2259 | + * Falls back to the general guide rather than returning nothing, so the notice | |
| 2260 | + * always has somewhere to send them. | |
| 2261 | + * | |
| 2262 | + * @since 2.12.7 | |
| 2263 | + * @param string $medium Placement the link is rendered in, used as utm_medium. | |
| 2264 | + * Two surfaces show this guide -- the dashboard notice and | |
| 2265 | + * the onboarding step -- and a shared value would make the | |
| 2266 | + * two indistinguishable in reporting, which is the whole | |
| 2267 | + * point of the attribution. | |
| 2268 | + * @return string Absolute documentation URL. | |
| 2269 | + */ | |
| 2270 | + public static function get_caching_plugin_doc_url( $medium = 'form_checks_notice' ) { | |
| 2271 | + $entry = self::get_active_caching_plugin_entry(); | |
| 2272 | + $slug = null === $entry || '' === $entry[1] ? 'how-to-set-up-sureforms-with-caching-plugins' : $entry[1]; | |
| 2273 | + | |
| 2274 | + // Through the central builder rather than hardcoding the domain, so the | |
| 2275 | + // link carries the same UTM attribution as every other doc link and a | |
| 2276 | + // domain change is one edit. utm_content is the slug, so the caller can be | |
| 2277 | + // told which guide people actually open. | |
| 2278 | + return self::get_sureforms_website_url( | |
| 2279 | + 'docs/' . $slug . '/', | |
| 2280 | + [ | |
| 2281 | + 'utm_medium' => self::get_string_value( $medium ), | |
| 2282 | + 'utm_content' => $slug, | |
| 2283 | + ] | |
| 2284 | + ); | |
| 2285 | + } | |
| 2286 | + | |
| 2287 | + /** | |
| 2178 | 2288 | * Check if any of the top 10 popular WordPress SMTP plugins is active using array_intersect. |
| 2179 | 2289 | * |
| 2180 | 2290 | * @since 1.9.1 |
| 2181 | 2291 | * @return bool True if any SMTP plugin is active, false otherwise. |
| @@ -2702,7 +2812,80 @@ | ||
| 2702 | 2812 | * @return int |
| 2703 | 2813 | */ |
| 2704 | 2814 | private static function get_geo_failure_ttl() { |
| 2705 | 2815 | return self::get_integer_value( apply_filters( 'srfm_geo_failure_ttl', 5 * MINUTE_IN_SECONDS ) ); |
| 2816 | + } | |
| 2817 | + | |
| 2818 | + /** | |
| 2819 | + * Caching plugins SureForms recognises, and the guide for each. | |
| 2820 | + * | |
| 2821 | + * `path => [ display name, doc slug ]`. An empty slug means there is no | |
| 2822 | + * plugin-specific guide and the general one applies. Name and slug live in one | |
| 2823 | + * array on purpose: keyed separately they drift, and a doc link that silently | |
| 2824 | + * degrades to the generic page is the kind of regression nobody reports. | |
| 2825 | + * | |
| 2826 | + * Order is precedence: the first active plugin in this list wins. The six with | |
| 2827 | + * their own guide are listed first on purpose, so a site running two caching | |
| 2828 | + * plugins is pointed at the specific guide rather than whichever plugin the | |
| 2829 | + * old alphabetical order happened to reach first. That flips the winner on a | |
| 2830 | + * few pairs -- WP Fastest Cache over WP Super Cache, SiteGround Optimizer and | |
| 2831 | + * Autoptimize over their partners -- and in each case the new winner is the | |
| 2832 | + * one that has something to say. Reordering this array changes which guide a | |
| 2833 | + * two-plugin site sees. | |
| 2834 | + * | |
| 2835 | + * @since 2.12.7 | |
| 2836 | + * @return array<string,array{0:string,1:string}> | |
| 2837 | + */ | |
| 2838 | + private static function get_known_caching_plugins() { | |
| 2839 | + return [ | |
| 2840 | + 'litespeed-cache/litespeed-cache.php' => [ 'LiteSpeed Cache', 'how-to-set-up-sureforms-with-litespeed-cache' ], | |
| 2841 | + 'wp-rocket/wp-rocket.php' => [ 'WP Rocket', 'how-to-set-up-sureforms-with-wp-rocket' ], | |
| 2842 | + 'w3-total-cache/w3-total-cache.php' => [ 'W3 Total Cache', 'how-to-set-up-sureforms-with-w3-total-cache' ], | |
| 2843 | + 'wp-fastest-cache/wpFastestCache.php' => [ 'WP Fastest Cache', 'how-to-set-up-sureforms-with-wp-fastest-cache' ], | |
| 2844 | + 'sg-cachepress/sg-cachepress.php' => [ 'SiteGround Optimizer', 'how-to-set-up-sureforms-with-siteground-optimizer' ], | |
| 2845 | + 'autoptimize/autoptimize.php' => [ 'Autoptimize', 'how-to-set-up-sureforms-with-autoptimize' ], | |
| 2846 | + 'wp-super-cache/wp-cache.php' => [ 'WP Super Cache', '' ], | |
| 2847 | + 'wp-optimize/wp-optimize.php' => [ 'WP-Optimize', '' ], | |
| 2848 | + 'cache-enabler/cache-enabler.php' => [ 'Cache Enabler', '' ], | |
| 2849 | + 'comet-cache/comet-cache.php' => [ 'Comet Cache', '' ], | |
| 2850 | + 'hummingbird-performance/wp-hummingbird.php' => [ 'Hummingbird', '' ], | |
| 2851 | + 'breeze/breeze.php' => [ 'Breeze', '' ], | |
| 2852 | + 'nitropack/main.php' => [ 'NitroPack', '' ], | |
| 2853 | + 'swift-performance-lite/performance.php' => [ 'Swift Performance Lite', '' ], | |
| 2854 | + 'wp-cloudflare-page-cache/wp-cloudflare-page-cache.php' => [ 'Super Page Cache', '' ], | |
| 2855 | + 'flying-press/flying-press.php' => [ 'FlyingPress', '' ], | |
| 2856 | + 'redis-cache/redis-cache.php' => [ 'Redis Object Cache', '' ], | |
| 2857 | + 'powered-cache/powered-cache.php' => [ 'Powered Cache', '' ], | |
| 2858 | + 'docket-cache/docket-cache.php' => [ 'Docket Cache', '' ], | |
| 2859 | + 'seraphinite-accelerator/plugin_root.php' => [ 'Seraphinite Accelerator', '' ], | |
| 2860 | + ]; | |
| 2861 | + } | |
| 2862 | + | |
| 2863 | + /** | |
| 2864 | + * The active caching plugin's entry, if there is one. | |
| 2865 | + * | |
| 2866 | + * Detection is by plugin path, mirroring is_any_smtp_plugin_active(), including | |
| 2867 | + * the multisite network-active merge. First match in | |
| 2868 | + * get_known_caching_plugins() wins; that array's order is the precedence. | |
| 2869 | + * | |
| 2870 | + * @since 2.12.7 | |
| 2871 | + * @return array{0:string,1:string}|null Name and doc slug, or null when none is active. | |
| 2872 | + */ | |
| 2873 | + private static function get_active_caching_plugin_entry() { | |
| 2874 | + $active_plugins = (array) get_option( 'active_plugins', [] ); | |
| 2875 | + | |
| 2876 | + // For multisite, merge sitewide active plugins. | |
| 2877 | + if ( is_multisite() ) { | |
| 2878 | + $network_plugins = (array) get_site_option( 'active_sitewide_plugins', [] ); | |
| 2879 | + $active_plugins = array_merge( $active_plugins, array_keys( $network_plugins ) ); | |
| 2880 | + } | |
| 2881 | + | |
| 2882 | + foreach ( self::get_known_caching_plugins() as $path => $entry ) { | |
| 2883 | + if ( in_array( $path, $active_plugins, true ) ) { | |
| 2884 | + return $entry; | |
| 2885 | + } | |
| 2886 | + } | |
| 2887 | + | |
| 2888 | + return null; | |
| 2706 | 2889 | } |
| 2707 | 2890 | |
| 2708 | 2891 | } |