get(['generated_script_page_ids']); if (is_wp_error($forms) || !is_array($forms)) { return []; } foreach ($forms as $form) { $pages = json_decode(isset($form->generated_script_page_ids) ? $form->generated_script_page_ids : '', true); if (!is_array($pages)) { continue; } foreach (array_keys($pages) as $postId) { $postId = absint($postId); if ($postId) { $postIds[] = $postId; } } } } catch (\Throwable $err) { Log::debug_log('Cache purge page lookup failed: ' . $err->getMessage()); return []; } return array_values(array_unique($postIds)); } /** * Purge the cached pages that render a Bit Form. * * Purges page by page, or flushes everything once the page count passes * bitform_cache_purge_page_limit. */ public static function purgeCaches() { $postIds = apply_filters('bitform_cache_purge_post_ids', self::$purgePostIds); $postIds = is_array($postIds) ? array_values(array_filter(array_map('absint', $postIds))) : []; // No page has rendered a form, so nothing cached can be stale. if (empty($postIds)) { do_action('bitform_purge_caches', [], 'none'); return; } $limit = (int) apply_filters('bitform_cache_purge_page_limit', 50); if ($limit > 0 && count($postIds) > $limit) { self::purgeEverything(); do_action('bitform_purge_caches', $postIds, 'everything'); return; } $urls = []; foreach ($postIds as $postId) { $url = get_permalink($postId); if (is_string($url) && '' !== $url) { $urls[] = $url; } } self::purgePages($postIds, $urls); self::purgeWholeCacheForPluginsWithoutPageApi(); // $mode is 'pages', 'everything' or 'none'. do_action('bitform_purge_caches', $postIds, 'pages'); } /** * Per-page purge for every caching plugin that exposes one. * * @param array $postIds * @param array $urls * * @return void */ private static function purgePages($postIds, $urls) { // WP Rocket self::tryPurge(function () use ($postIds) { if (function_exists('rocket_clean_post')) { foreach ($postIds as $postId) { \rocket_clean_post($postId); } } }); // LiteSpeed Cache self::tryPurge(function () use ($postIds) { if (has_action('litespeed_purge_post')) { foreach ($postIds as $postId) { do_action('litespeed_purge_post', $postId); } } }); // W3 Total Cache self::tryPurge(function () use ($postIds) { if (function_exists('w3tc_flush_post')) { foreach ($postIds as $postId) { \w3tc_flush_post($postId); } } }); // WP Super Cache self::tryPurge(function () use ($urls) { if (function_exists('wpsc_delete_url_cache')) { foreach ($urls as $url) { \wpsc_delete_url_cache($url); } } }); // Cache Enabler self::tryPurge(function () use ($postIds) { if (class_exists('\Cache_Enabler')) { foreach ($postIds as $postId) { \Cache_Enabler::clear_page_cache_by_post_id($postId); } } }); // WP-Optimize self::tryPurge(function () use ($postIds) { if (class_exists('\WPO_Page_Cache')) { foreach ($postIds as $postId) { \WPO_Page_Cache::delete_single_post_cache($postId); } } }); // SiteGround Optimizer self::tryPurge(function () use ($urls) { if (function_exists('sg_cachepress_purge_cache')) { foreach ($urls as $url) { \sg_cachepress_purge_cache($url); } } }); // NitroPack self::tryPurge(function () use ($urls) { if (function_exists('nitropack_purge_url')) { foreach ($urls as $url) { \nitropack_purge_url($url); } } }); // Nginx Helper (server-level FastCGI / Redis page cache) self::tryPurge(function () use ($urls) { if (defined('NGINX_HELPER_BASENAME') || class_exists('Nginx_Helper')) { foreach ($urls as $url) { do_action('rt_nginx_helper_purge_url', $url); } } }); // Elementor stores rendered document HTML in post meta with a 24h TTL. self::tryPurge(function () use ($postIds) { if (class_exists('\Elementor\Core\Base\Document')) { foreach ($postIds as $postId) { delete_post_meta($postId, \Elementor\Core\Base\Document::CACHE_META_KEY); } } }); } /** * Full flush for caching plugins that expose no per-page purge API. * * Each call is a no-op unless that plugin is installed. Disabled by the * bitform_cache_full_purge_fallback filter. * * @return void */ private static function purgeWholeCacheForPluginsWithoutPageApi() { if (!apply_filters('bitform_cache_full_purge_fallback', true)) { return; } // FlyingPress self::tryPurge(function () { if (class_exists('\FlyingPress\Purge')) { \FlyingPress\Purge::purge_everything(); } }); // Breeze self::tryPurge(function () { if (class_exists('\Breeze_PurgeCache')) { \Breeze_PurgeCache::breeze_cache_flush(); } }); // Hummingbird self::tryPurge(function () { if (has_action('wphb_clear_page_cache')) { do_action('wphb_clear_page_cache'); } }); // Swift Performance self::tryPurge(function () { if (class_exists('\Swift_Performance_Cache')) { \Swift_Performance_Cache::clear_all_cache(); } }); // Comet Cache self::tryPurge(function () { if (class_exists('\comet_cache')) { \comet_cache::clear(); } }); // WP Fastest Cache self::tryPurge(function () { if (class_exists('WpFastestCache')) { do_action('wpfc_clear_all_cache', true); } }); // WP Engine self::tryPurge(function () { if (class_exists('\WpeCommon')) { \WpeCommon::purge_varnish_cache(); } }); } /** * Flush every installed caching plugin's whole cache. * * @return void */ private static function purgeEverything() { self::tryPurge(function () { if (function_exists('rocket_clean_domain')) { \rocket_clean_domain(); } }); self::tryPurge(function () { if (has_action('litespeed_purge_all')) { do_action('litespeed_purge_all'); } }); self::tryPurge(function () { if (function_exists('w3tc_flush_all')) { \w3tc_flush_all(); } }); self::tryPurge(function () { if (function_exists('wp_cache_clear_cache')) { is_multisite() ? \wp_cache_clear_cache(get_current_blog_id()) : \wp_cache_clear_cache(); } }); self::tryPurge(function () { if (class_exists('\Cache_Enabler')) { \Cache_Enabler::clear_complete_cache(); } }); self::tryPurge(function () { if (class_exists('\WPO_Page_Cache')) { \WPO_Page_Cache::instance()->purge(); } }); self::tryPurge(function () { if (function_exists('sg_cachepress_purge_everything')) { \sg_cachepress_purge_everything(); } }); self::tryPurge(function () { if (function_exists('nitropack_purge')) { \nitropack_purge(); } }); self::tryPurge(function () { if (defined('NGINX_HELPER_BASENAME') || class_exists('Nginx_Helper')) { do_action('rt_nginx_helper_purge_all'); } }); // Elementor's document cache, CSS files and asset data. self::tryPurge(function () { if (class_exists('\Elementor\Plugin')) { \Elementor\Plugin::$instance->files_manager->clear_cache(); } }); self::purgeWholeCacheForPluginsWithoutPageApi(); } /** * Run one purge, swallowing third-party failures. * * @param callable $purge * * @return void */ private static function tryPurge($purge) { try { $purge(); } catch (\Throwable $err) { Log::debug_log('Cache purge failed: ' . $err->getMessage()); } } /** * Substrings optimizers match against a script's URL, handle or inline body. * * @return array */ public static function exclusionPatterns() { $patterns = [ 'bf_globals', // inline config content 'bitform', // handles + generated file names (bitform-js-*, bitforms_*) 'bitforms/form-scripts', // uploads path of the generated runtime bundle 'bit-form-all-script', // runtime bundle handle ]; $filtered = apply_filters('bitform_cache_exclusion_patterns', $patterns); return is_array($filtered) ? $filtered : $patterns; } /** * Append patterns to an array-based optimizer filter. Typed loosely because * optimizers pass mixed shapes through these filters. * * @param mixed $exclusions * * @return mixed */ public static function appendExclusionsArray($exclusions) { if (!is_array($exclusions)) { return $exclusions; } return array_values(array_unique(array_merge($exclusions, self::exclusionPatterns()))); } /** * Append exclusion patterns to a comma-separated string filter (Autoptimize). * * @param mixed $exclusions * * @return mixed */ public static function appendExclusionsString($exclusions) { if (!is_string($exclusions)) { return $exclusions; } $parts = array_filter(array_map('trim', explode(',', $exclusions))); $parts = array_unique(array_merge($parts, self::exclusionPatterns())); return implode(',', $parts); } /** * Append script handles to a handle-based filter (SiteGround). * * SiteGround matches exact handle names and our per-form inline handles are * dynamic (bitform-bf-globals-bitforms_15_123_1), so scan the registered * queue rather than passing prefixes, which it would not match. * * @param mixed $handles * * @return mixed */ public static function appendHandles($handles) { if (!is_array($handles)) { return $handles; } $bitformHandles = self::HANDLE_PREFIXES; $wpScripts = wp_scripts(); if (!empty($wpScripts->registered)) { foreach (array_keys($wpScripts->registered) as $registeredHandle) { if (self::isBitformHandle($registeredHandle)) { $bitformHandles[] = $registeredHandle; } } } return array_values(array_unique(array_merge($handles, $bitformHandles))); } /** * Whether a script handle belongs to Bit Form's frontend runtime. * * @param mixed $handle * * @return bool */ private static function isBitformHandle($handle) { if (!is_string($handle) || '' === $handle) { return false; } foreach (self::HANDLE_PREFIXES as $prefix) { if (0 === strpos($handle, $prefix)) { return true; } } return false; } /** * Whether a tag or file path carries one of the exclusion patterns. * * @param mixed $value * * @return bool */ private static function matchesExclusionPattern($value) { if (!is_string($value) || '' === $value) { return false; } foreach (self::exclusionPatterns() as $pattern) { if (is_string($pattern) && '' !== $pattern && false !== strpos($value, $pattern)) { return true; } } return false; } /** * Return false for Bit Form handles on boolean, handle-keyed optimizer * filters (Hummingbird's minify/combine/defer/inline, js_do_concat). * * @param mixed $value * @param mixed $handle * * @return mixed */ public static function denyForBitformHandle($value, $handle = '') { return self::isBitformHandle($handle) ? false : $value; } /** * Deny W3 Total Cache tag minification for Bit Form scripts. * * @param mixed $doMinification * @param mixed $scriptTag * @param mixed $file * * @return mixed */ public static function denyTagMinification($doMinification, $scriptTag = '', $file = '') { if (self::matchesExclusionPattern($scriptTag) || self::matchesExclusionPattern($file)) { return false; } return $doMinification; } /** * Stamp exclusion attributes on Bit Form