Plugin Directory

Changeset 3447143


Ignore:
Timestamp:
01/26/2026 01:53:05 PM (6 weeks ago)
Author:
griffinforms
Message:

Release 2.1.7.0

Location:
griffinforms-form-builder/trunk
Files:
1 added
11 edited

Legend:

Unmodified
Added
Removed
  • griffinforms-form-builder/trunk/admin/ajax/forms.php

    r3377809 r3447143  
    320320        }
    321321    }
     322
     323    public function runRetentionCleanup()
     324    {
     325        $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
     326        if (!wp_verify_nonce($nonce, 'run_retention_cleanup')) {
     327            wp_send_json_error(['message' => __('Invalid nonce.', 'griffinforms-form-builder')]);
     328            wp_die();
     329        }
     330
     331        $form_id = isset($_POST['form_id']) ? absint(wp_unslash($_POST['form_id'])) : 0;
     332        if (!$form_id) {
     333            wp_send_json_error(['message' => __('Invalid form ID.', 'griffinforms-form-builder')]);
     334            wp_die();
     335        }
     336
     337        $result = \GriffinForms\Includes\Schedulers\SubmissionRetentionCleanup::getInstance()->runForForm($form_id);
     338        wp_send_json_success($result);
     339    }
    322340   
    323341    public function __construct()
     
    333351        // Removed preview fetch endpoints (server renders previews in cards)
    334352        add_action('wp_ajax_deleteForms', [$this, 'deleteForms']);
     353        add_action('wp_ajax_griffinforms_run_retention_cleanup', [$this, 'runRetentionCleanup']);
    335354    }
    336355}
  • griffinforms-form-builder/trunk/admin/html/pages/lists/submissions.php

    r3353005 r3447143  
    275275            echo ' ';
    276276            echo wp_kses_post($this->lang->formSettingsNotice($form_url));
     277            $nonce = wp_create_nonce('run_retention_cleanup');
     278            echo ' <a href="#" class="gf-run-retention-cleanup" data-gf-form-id="' . absint($this->form_id) . '" data-gf-nonce="' . esc_attr($nonce) . '">';
     279            echo esc_html__('Clean Up Now', 'griffinforms-form-builder');
     280            echo '</a>';
    277281            echo '</div>';
    278282        }
  • griffinforms-form-builder/trunk/admin/js/griffinforms-lists.js

    r3394319 r3447143  
    124124        });
    125125    });
     126
     127    $(document).on('click', '.gf-run-retention-cleanup', function(e) {
     128        e.preventDefault();
     129
     130        var $button = $(this);
     131        var formId = $button.data('gf-form-id');
     132        var nonce = $button.data('gf-nonce');
     133
     134        if (!formId || !nonce) {
     135            return;
     136        }
     137
     138        $button.addClass('disabled').attr('aria-disabled', 'true');
     139        var originalText = $button.text();
     140        $button.text('Cleaning...');
     141
     142        $.ajax({
     143            url: ajaxurl,
     144            type: 'POST',
     145            dataType: 'json',
     146            data: {
     147                action: 'griffinforms_run_retention_cleanup',
     148                form_id: formId,
     149                nonce: nonce
     150            },
     151            success: function(response) {
     152                if (response && response.success) {
     153                    var deleted = response.data && typeof response.data.deleted !== 'undefined' ? response.data.deleted : 0;
     154                    var notice = '<div class="notice notice-success is-dismissible gf-is-dismissible"><p>Cleanup completed. Deleted ' + deleted + ' submissions.</p><button type="button" class="notice-dismiss gf-dismiss-notice"><span class="screen-reader-text">Dismiss this notice.</span></button></div>';
     155                    $(notice).insertBefore('.wp-header-end');
     156                    setTimeout(function() { location.reload(); }, 800);
     157                } else {
     158                    var message = response && response.data && response.data.message ? response.data.message : 'Cleanup failed.';
     159                    var errorNotice = '<div class="notice notice-error is-dismissible gf-is-dismissible"><p>' + message + '</p><button type="button" class="notice-dismiss gf-dismiss-notice"><span class="screen-reader-text">Dismiss this notice.</span></button></div>';
     160                    $(errorNotice).insertBefore('.wp-header-end');
     161                    $button.removeClass('disabled').attr('aria-disabled', 'false');
     162                    $button.text(originalText);
     163                }
     164            },
     165            error: function() {
     166                var errorNotice = '<div class="notice notice-error is-dismissible gf-is-dismissible"><p>Cleanup failed. Please try again.</p><button type="button" class="notice-dismiss gf-dismiss-notice"><span class="screen-reader-text">Dismiss this notice.</span></button></div>';
     167                $(errorNotice).insertBefore('.wp-header-end');
     168                $button.removeClass('disabled').attr('aria-disabled', 'false');
     169                $button.text(originalText);
     170            }
     171        });
     172    });
    126173});
  • griffinforms-form-builder/trunk/config.php

    r3446601 r3447143  
    55class Config
    66{
    7     public const VERSION = '2.1.6.0';
     7    public const VERSION = '2.1.7.0';
    88    public const DB_VER = '1.0';
    99    public const PHP_REQUIRED = '8.2';
  • griffinforms-form-builder/trunk/frontend/ajax/submission.php

    r3425584 r3447143  
    174174                    }
    175175   
    176                         $this->truncateSubmissions($this->data['form']);
     176                        // Retention cleanup moved to scheduler to avoid slowing submissions.
    177177                        $this->response['success'] = true;
    178178                        $form_name = $this->resolveFormName((int) ($this->data['form'] ?? 0));
  • griffinforms-form-builder/trunk/frontend/html/forms/formpage.php

    r3421663 r3447143  
    2222        }
    2323
    24         $html = '<div id="' . esc_attr($this->html_ids['container']) . '" class="griffinforms-formpage-container row" data-gf-page-type="page"' . $attr . '>';
     24        $captcha_context = $this->antispam->getCaptchaClientContext($this->data['position'] ?? '');
     25        $captcha_context_attr = esc_attr(wp_json_encode($captcha_context));
     26        $html = '<div id="' . esc_attr($this->html_ids['container']) . '" class="griffinforms-formpage-container row" data-gf-page-type="page" data-gf-form-id="' . absint($this->data['form']) . '" data-gf-captcha-context="' . $captcha_context_attr . '"' . $attr . '>';
    2527        $html .= $this->formPageHeader();
    2628        $html .= $this->pageRows();
     
    275277            const resumeDefaultGateway = reviewContainer.attr("data-gf-resume-default-gateway") || "";
    276278            const paymentStyleTokens = getPaymentStyleTokens();
     279            window.gfCaptchaHandlers = window.gfCaptchaHandlers || {};
     280            window.gfCaptchaInitFns = window.gfCaptchaInitFns || {};
    277281            const paymentFlowState = {
    278282                gateway: null,
     
    382386                antispamData["honeypot"] = honeypotField.length ? honeypotField.val() : "";
    383387
     388                const activePage = $(".griffinforms-formpage-container[data-griffinforms-state='active']").first();
    384389                const captchaContext = <?php echo wp_json_encode($this->antispam->getCaptchaClientContext($this->data['position'] ?? '')); ?>;
    385390                const providerEnabled = captchaContext && captchaContext.enabled;
     
    418423                    });
    419424                } else if (providerEnabled && captchaContext.id === "recaptcha" && captchaContext.version === "v2") {
    420                     let v2Token = grecaptcha.getResponse();
     425                    let v2Token = "";
     426                    if (activePage.length) {
     427                        const widgetId = activePage.data("gfRecaptchaWidgetId");
     428                        if (typeof widgetId !== "undefined") {
     429                            v2Token = grecaptcha.getResponse(widgetId);
     430                        }
     431                    }
     432                    if (!v2Token) {
     433                        v2Token = grecaptcha.getResponse();
     434                    }
    421435                    if (v2Token) {
    422436                        antispamData["recaptcha"] = v2Token;
     
    429443                    proceedWithSubmission(data, formData);
    430444                } else if (providerEnabled && captchaContext.id === "cloudflare_turnstile") {
    431                     const turnstileField = document.querySelector('input[name=\"cf-turnstile-response\"]');
    432                     const turnstileToken = turnstileField ? turnstileField.value.trim() : "";
     445                    let turnstileToken = "";
     446                    if (typeof turnstile !== "undefined" && typeof turnstile.getResponse === "function" && activePage.length) {
     447                        const widgetId = activePage.data("gfTurnstileWidgetId");
     448                        if (typeof widgetId !== "undefined") {
     449                            turnstileToken = turnstile.getResponse(widgetId) || "";
     450                        }
     451                    }
     452                    if (!turnstileToken) {
     453                        const turnstileField = document.querySelector('input[name=\"cf-turnstile-response\"]');
     454                        turnstileToken = turnstileField ? turnstileField.value.trim() : "";
     455                    }
    433456                    if (!turnstileToken) {
    434457                        alert("<?php esc_html_e('Please complete the Turnstile challenge.', 'griffinforms-form-builder'); ?>");
     
    440463                    proceedWithSubmission(data, formData);
    441464                } else if (providerEnabled && captchaContext.id === "hcaptcha") {
    442                     const hcaptchaField = document.querySelector('[name=\"h-captcha-response\"]');
    443                     const hcaptchaToken = hcaptchaField ? hcaptchaField.value.trim() : "";
     465                    let hcaptchaToken = "";
     466                    if (typeof hcaptcha !== "undefined" && typeof hcaptcha.getResponse === "function" && activePage.length) {
     467                        const widgetId = activePage.data("gfHcaptchaWidgetId");
     468                        if (typeof widgetId !== "undefined") {
     469                            hcaptchaToken = hcaptcha.getResponse(widgetId) || "";
     470                        }
     471                    }
     472                    if (!hcaptchaToken) {
     473                        const hcaptchaField = document.querySelector('[name=\"h-captcha-response\"]');
     474                        hcaptchaToken = hcaptchaField ? hcaptchaField.value.trim() : "";
     475                    }
    444476                    if (!hcaptchaToken) {
    445477                        alert("<?php esc_html_e('Please complete the hCaptcha challenge.', 'griffinforms-form-builder'); ?>");
     
    545577            }
    546578
     579            function parseCaptchaContext(pageEl) {
     580                const raw = pageEl.attr("data-gf-captcha-context");
     581                if (!raw) {
     582                    return null;
     583                }
     584                try {
     585                    return JSON.parse(raw);
     586                } catch (e) {
     587                    return null;
     588                }
     589            }
     590
     591            function initCaptchaForPage(pageEl) {
     592                if (!pageEl || !pageEl.length) {
     593                    return;
     594                }
     595                if (pageEl.attr("data-gf-form-id") !== String(formId)) {
     596                    return;
     597                }
     598                const captchaContext = parseCaptchaContext(pageEl);
     599                if (!captchaContext || !captchaContext.enabled) {
     600                    return;
     601                }
     602
     603                if (captchaContext.id === "recaptcha" && captchaContext.version === "v2") {
     604                    if (typeof grecaptcha === "undefined" || typeof grecaptcha.render !== "function") {
     605                        return;
     606                    }
     607                    const existingId = pageEl.data("gfRecaptchaWidgetId");
     608                    if (typeof existingId !== "undefined" && typeof grecaptcha.reset === "function") {
     609                        grecaptcha.reset(existingId);
     610                        return;
     611                    }
     612                    pageEl.find(".g-recaptcha").each(function () {
     613                        const el = this;
     614                        el.innerHTML = "";
     615                        const widgetId = grecaptcha.render(el, { sitekey: captchaContext.site_key });
     616                        pageEl.data("gfRecaptchaWidgetId", widgetId);
     617                    });
     618                    return;
     619                }
     620
     621                if (captchaContext.id === "cloudflare_turnstile") {
     622                    if (typeof turnstile === "undefined" || typeof turnstile.render !== "function") {
     623                        return;
     624                    }
     625                    const existingId = pageEl.data("gfTurnstileWidgetId");
     626                    if (typeof existingId !== "undefined" && typeof turnstile.reset === "function") {
     627                        turnstile.reset(existingId);
     628                        return;
     629                    }
     630                    pageEl.find(".cf-turnstile").each(function () {
     631                        const el = this;
     632                        el.innerHTML = "";
     633                        const widgetId = turnstile.render(el, { sitekey: captchaContext.site_key });
     634                        pageEl.data("gfTurnstileWidgetId", widgetId);
     635                    });
     636                    return;
     637                }
     638
     639                if (captchaContext.id === "hcaptcha") {
     640                    if (typeof hcaptcha === "undefined" || typeof hcaptcha.render !== "function") {
     641                        return;
     642                    }
     643                    const existingId = pageEl.data("gfHcaptchaWidgetId");
     644                    if (typeof existingId !== "undefined" && typeof hcaptcha.reset === "function") {
     645                        hcaptcha.reset(existingId);
     646                        return;
     647                    }
     648                    pageEl.find(".h-captcha").each(function () {
     649                        const el = this;
     650                        el.innerHTML = "";
     651                        const widgetId = hcaptcha.render(el, { sitekey: captchaContext.site_key });
     652                        pageEl.data("gfHcaptchaWidgetId", widgetId);
     653                    });
     654                }
     655            }
     656
    547657            function resetPaymentFlowState() {
    548658                paymentFlowState.gateway = null;
     
    677787            }
    678788
     789            if (!window.gfCaptchaHandlers[formId]) {
     790                window.gfCaptchaHandlers[formId] = true;
     791                $(document).on("pageFlipped", function(event, payload) {
     792                    if (payload && payload.thePage) {
     793                        initCaptchaForPage(payload.thePage);
     794                    }
     795                });
     796            }
     797            window.gfCaptchaInitFns[formId] = initCaptchaForPage;
     798
     799            if (typeof window.griffinformsRecaptchaReady !== "function") {
     800                window.griffinformsRecaptchaReady = function () {
     801                    const activePages = $(".griffinforms-formpage-container[data-griffinforms-state='active']");
     802                    activePages.each(function () {
     803                        const pageEl = $(this);
     804                        const pageFormId = pageEl.attr("data-gf-form-id");
     805                        if (pageFormId && window.gfCaptchaInitFns && window.gfCaptchaInitFns[pageFormId]) {
     806                            window.gfCaptchaInitFns[pageFormId](pageEl);
     807                        }
     808                    });
     809                };
     810            }
     811
     812            if (!resumeMode) {
     813                initCaptchaForPage(firstPage);
     814            }
     815
    679816            if (reviewBackBtn.length) {
    680817                reviewBackBtn.on("click", function(){
  • griffinforms-form-builder/trunk/frontend/html/security/antispam.php

    r3421663 r3447143  
    132132                $this->errors += 1;
    133133                $this->error_notices[] = $result['message'] ?? __('Unable to submit. CAPTCHA assessment failed.', 'griffinforms-form-builder');
     134                $this->logCaptchaSkip('CAPTCHA validation failed for position "' . $position . '".');
    134135            }
    135136        }
     
    194195        }
    195196
    196         return in_array($position, ['single', 'first'], true);
     197        return in_array($position, ['single', 'first', 'middle', 'last'], true);
    197198    }
    198199
  • griffinforms-form-builder/trunk/frontend/html/security/captcha/recaptcha.php

    r3421663 r3447143  
    4949        $version = $this->getVersion();
    5050        if ($version === 'v2') {
    51             wp_enqueue_script('griffinforms-google-recaptcha', 'https://www.google.com/recaptcha/api.js', [], '2.0', false);
     51            wp_enqueue_script('griffinforms-google-recaptcha', 'https://www.google.com/recaptcha/api.js?render=explicit&onload=griffinformsRecaptchaReady', [], '2.0', false);
    5252        } elseif ($version === 'v3') {
    5353            $url = 'https://www.google.com/recaptcha/api.js?render=' . rawurlencode($this->getSiteKey());
     
    6969            'version' => $this->getVersion(),
    7070            'site_key' => $this->getSiteKey(),
    71             'limit_to_first_page' => ($this->getVersion() === 'v2'),
     71            'limit_to_first_page' => false,
    7272        ];
    7373    }
     
    107107
    108108        if (($result['success'] ?? false) !== true) {
     109            if ($this->log) {
     110                $this->log->add(
     111                    'warning',
     112                    'form',
     113                    (int) $this->form_id,
     114                    get_current_user_id(),
     115                    'reCAPTCHA validation failed. Codes: ' . wp_json_encode($result['error-codes'] ?? []),
     116                    'security'
     117                );
     118            }
    109119            return ['success' => false, 'message' => __('Unable to submit. reCAPTCHA assessment failed.', 'griffinforms-form-builder')];
    110120        }
    111121
    112122        if ($version === 'v3' && isset($result['score']) && (float) $result['score'] < 0.5) {
     123            if ($this->log) {
     124                $this->log->add(
     125                    'warning',
     126                    'form',
     127                    (int) $this->form_id,
     128                    get_current_user_id(),
     129                    'reCAPTCHA v3 score too low. Score: ' . wp_json_encode($result['score']) . '. Action: ' . wp_json_encode($result['action'] ?? ''),
     130                    'security'
     131                );
     132            }
    113133            return ['success' => false, 'message' => __('Unable to submit. reCAPTCHA score too low.', 'griffinforms-form-builder')];
    114134        }
     
    124144
    125145        if ($this->getVersion() === 'v2') {
    126             return in_array($position, ['first', 'single'], true);
     146            return in_array($position, ['first', 'single', 'middle', 'last'], true);
    127147        }
    128148
  • griffinforms-form-builder/trunk/griffinforms.php

    r3446601 r3447143  
    44 * Plugin URI:        https://griffinforms.com/
    55 * Description:       A powerful and flexible form builder for WordPress. Create multi-page forms with drag-and-drop ease, custom validations, and full submission management.
    6  * Version:           2.1.6.0
     6 * Version:           2.1.7.0
    77 * Requires at least: 6.6
    88 * Requires PHP:      8.2
  • griffinforms-form-builder/trunk/includes/schedulers/processscheduler.php

    r3421663 r3447143  
    3030        $log_cleanup->register();
    3131
     32        // Submission retention cleanup scheduler
     33        $submission_retention = SubmissionRetentionCleanup::getInstance();
     34        $submission_retention->register();
     35
    3236        // Job queue scheduler
    3337        $job_scheduler = JobScheduler::getInstance();
  • griffinforms-form-builder/trunk/readme.txt

    r3446601 r3447143  
    55Tested up to: 6.9
    66Requires PHP: 8.2
    7 Stable tag: 2.1.6.0
     7Stable tag: 2.1.7.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    172172== Changelog ==
    173173
     174= 2.1.7.0 – 2026-01-26 =
     175* Improvement: CAPTCHA widgets now render on every page of multi-page forms (reCAPTCHA v2/v3, Turnstile, hCaptcha).
     176* Improvement: Submission retention cleanup now runs on a scheduled job with an admin “Clean Up Now” action for immediate pruning.
     177
    174178= 2.1.6.0 – 2026-01-25 =
    175179* Feature: Added an Academic templates category with new admissions, program inquiry, and recommendation request forms.
     
    474478== Upgrade Notice ==
    475479
     480= 2.1.7.0 =
     481Multi-page CAPTCHA now renders on every page, and submission retention cleanup runs on a scheduled job with a manual “Clean Up Now” action. Recommended update.
     482
    476483= 2.1.6.0 =
    477484Adds a new Academic templates category with new and upgraded higher-education forms, plus conditional logic enhancements. Recommended update for anyone using templates.
Note: See TracChangeset for help on using the changeset viewer.