Instantly share code, notes, and snippets.
Last active
June 23, 2026 15:09
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save xlplugins/c52ec052cfccd70e7b768ae06b526ed0 to your computer and use it in GitHub Desktop.
Happy Head - Move Turnstile widget above Order Now button.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Happy Head - Move Turnstile widget above Order Now button. | |
| * | |
| * WFACP re-renders .woocommerce-checkout-payment on every update_checkout AJAX call. | |
| * ECFT outputs a bare .cf-turnstile div; Cloudflare only auto-renders on first page load, | |
| * so we explicitly call turnstile.render() after each checkout fragment refresh. | |
| */ | |
| class Happy_Head_ECFT_Checkout_Position { | |
| public function __construct() { | |
| add_action( 'wfacp_after_checkout_page_found', [ $this, 'register_placement' ] ); | |
| add_action( 'wfacp_before_process_checkout_template_loader', [ $this, 'register_placement' ] ); | |
| add_action( 'wp', [ $this, 'remove_default_placement' ], 15 ); | |
| add_action( 'wp_footer', [ $this, 'enqueue_rerender_script' ], 999 ); | |
| add_action( 'wp_footer', [ $this, 'enqueue_styles' ], 999 ); | |
| add_filter( 'woocommerce_update_order_review_fragments', function ( $fragments ) { | |
| unset( $fragments['.woocommerce-checkout-payment'] ); | |
| return $fragments; | |
| } ); | |
| } | |
| public function register_placement() { | |
| if ( ! $this->is_enabled() ) { | |
| return; | |
| } | |
| if ( has_action( 'wfacp_woocommerce_review_order_before_submit', [ $this, 'render_widget' ] ) ) { | |
| return; | |
| } | |
| add_action( 'wfacp_woocommerce_review_order_before_submit', [ $this, 'render_widget' ], 10 ); | |
| } | |
| public function render_widget() { | |
| // Use ECFT's configured validation message — same text the server sends via wc_add_notice(). | |
| $error_msg = ecft_get_option( 'ecft_validation_error_message' ); | |
| echo '<div class="ecft-inline-error" style="display:none;" role="alert">' | |
| . '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px">' | |
| . '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/>' | |
| . '<line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>' | |
| . '</svg>' | |
| . '<span style="margin:0;">' . esc_html( $error_msg ) . '</span>' | |
| . '</div>'; | |
| ecft_render_turnstile_wrapper(); | |
| } | |
| public function remove_default_placement() { | |
| if ( ! $this->is_enabled() ) { | |
| return; | |
| } | |
| remove_action( 'woocommerce_checkout_after_customer_details', 'ecft_render_turnstile_wrapper' ); | |
| } | |
| private function is_enabled() { | |
| return function_exists( 'ecft_render_turnstile_wrapper' ) | |
| && function_exists( 'ecft_get_option' ) | |
| && 'yes' === ecft_get_option( 'ecft_enable_woo_checkout' ); | |
| } | |
| public function enqueue_styles() { | |
| if ( ! is_checkout() || ! $this->is_enabled() ) { | |
| return; | |
| } | |
| echo '<style> | |
| .ecft-turnstile, .cf-turnstile { margin: 16px 0 18px !important; text-align: left !important; } | |
| .ecft-turnstile > div, .ecft-turnstile iframe, | |
| .cf-turnstile > div, .cf-turnstile iframe { margin-left: 0 !important; margin-right: auto !important; } | |
| .ecft-inline-error { | |
| display: none; | |
| align-items: flex-start; | |
| gap: 8px; | |
| background: #fff5f5; | |
| border: 1px solid #f87171; | |
| border-radius: 4px; | |
| padding: 10px 14px; | |
| margin-bottom: 10px; | |
| color: #dc2626; | |
| font-size: 14px; | |
| line-height: 1.4; | |
| } | |
| </style>'; | |
| } | |
| public function enqueue_rerender_script() { | |
| if ( ! is_checkout() || ! $this->is_enabled() || is_user_logged_in() ) { | |
| return; | |
| } | |
| ?> | |
| <script> | |
| (function ($) { | |
| function happyHeadEcftRerenderTurnstile() { | |
| if (typeof turnstile === 'undefined') { | |
| return; | |
| } | |
| document.querySelectorAll('#payment .ecft-turnstile.cf-turnstile').forEach(function (el) { | |
| if (el.querySelector('iframe') || el.querySelector('input[name="cf-turnstile-response"]')) { | |
| return; | |
| } | |
| turnstile.render(el, { | |
| sitekey: el.getAttribute('data-sitekey'), | |
| theme: el.getAttribute('data-theme') || 'auto', | |
| language: el.getAttribute('data-language') || 'auto', | |
| size: el.getAttribute('data-size') || 'normal', | |
| appearance: el.getAttribute('data-appearance') || 'always' | |
| }); | |
| }); | |
| } | |
| // Pre-submit: show inline error when place_order clicked with no token yet. | |
| document.addEventListener('click', function (e) { | |
| var btn = e.target.closest | |
| ? e.target.closest('#place_order, button[name="woocommerce_checkout_place_order"]') | |
| : null; | |
| if (!btn) { return; } | |
| var tokenInput = document.querySelector('[name="cf-turnstile-response"]'); | |
| if (!tokenInput || tokenInput.value) { return; } | |
| e.preventDefault(); | |
| e.stopImmediatePropagation(); | |
| var err = document.querySelector('.ecft-inline-error'); | |
| if (err) { | |
| err.style.display = 'flex'; | |
| err.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| } | |
| }, true); | |
| // Post-submit: WC fires checkout_error with the notices HTML after a failed AJAX checkout. | |
| // Find ECFT's notice in that HTML, show it inline, and remove it from the top notice area. | |
| $(document.body).on('checkout_error', function (e, noticesHtml) { | |
| var err = document.querySelector('.ecft-inline-error'); | |
| var msgEl = err ? err.querySelector('span') : null; | |
| if (!err || !msgEl) { return; } | |
| var ecftMsg = msgEl.textContent.trim(); | |
| // Parse the notices HTML WC returned and look for ECFT's message. | |
| var tmp = document.createElement('div'); | |
| tmp.innerHTML = noticesHtml || ''; | |
| var items = tmp.querySelectorAll('li'); | |
| var matched = Array.prototype.slice.call(items).some(function (li) { | |
| return li.textContent.trim() === ecftMsg; | |
| }); | |
| if (!matched) { return; } | |
| // Show inline. | |
| err.style.display = 'flex'; | |
| err.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| // checkout_error fires AFTER WC/FunnelKit has already rendered the notices into the DOM. | |
| // FunnelKit's wfacp_submit_error() prepends into .woocommerce-noticegroup-checkout (lowercase). | |
| // WC's own submit_error() uses .woocommerce-NoticeGroup-checkout (capitalized). | |
| // Scan all notice containers immediately — no timeout needed. | |
| document.querySelectorAll([ | |
| '.woocommerce-noticegroup-checkout li', | |
| '.woocommerce-NoticeGroup-checkout li', | |
| '.woocommerce-notices-wrapper li', | |
| '.wfacp-notices-wrapper li', | |
| '.woocommerce-error li' | |
| ].join(', ')).forEach(function (li) { | |
| if (li.textContent.trim() === ecftMsg) { | |
| var ul = li.parentElement; | |
| li.remove(); | |
| if (ul && ul.children.length === 0) { ul.remove(); } | |
| } | |
| }); | |
| }); | |
| // Hide inline error once Cloudflare fills the token (watches widget DOM changes). | |
| function watchToken() { | |
| var widget = document.querySelector('.ecft-turnstile, .cf-turnstile'); | |
| if (!widget) { return; } | |
| new MutationObserver(function () { | |
| var tokenInput = widget.querySelector('[name="cf-turnstile-response"]'); | |
| var err = document.querySelector('.ecft-inline-error'); | |
| if (tokenInput && tokenInput.value && err) { | |
| err.style.display = 'none'; | |
| } | |
| }).observe(widget, { subtree: true, childList: true, attributes: true }); | |
| } | |
| $(function () { | |
| happyHeadEcftRerenderTurnstile(); | |
| watchToken(); | |
| }); | |
| $(document.body).on('updated_checkout wfacp_updated_checkout', happyHeadEcftRerenderTurnstile); | |
| })(jQuery); | |
| </script> | |
| <?php | |
| } | |
| } | |
| new Happy_Head_ECFT_Checkout_Position(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment