Changeset 3447143
- Timestamp:
- 01/26/2026 01:53:05 PM (6 weeks ago)
- Location:
- griffinforms-form-builder/trunk
- Files:
-
- 1 added
- 11 edited
-
admin/ajax/forms.php (modified) (2 diffs)
-
admin/html/pages/lists/submissions.php (modified) (1 diff)
-
admin/js/griffinforms-lists.js (modified) (1 diff)
-
config.php (modified) (1 diff)
-
frontend/ajax/submission.php (modified) (1 diff)
-
frontend/html/forms/formpage.php (modified) (8 diffs)
-
frontend/html/security/antispam.php (modified) (2 diffs)
-
frontend/html/security/captcha/recaptcha.php (modified) (4 diffs)
-
griffinforms.php (modified) (1 diff)
-
includes/schedulers/processscheduler.php (modified) (1 diff)
-
includes/schedulers/submissionretentioncleanup.php (added)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
griffinforms-form-builder/trunk/admin/ajax/forms.php
r3377809 r3447143 320 320 } 321 321 } 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 } 322 340 323 341 public function __construct() … … 333 351 // Removed preview fetch endpoints (server renders previews in cards) 334 352 add_action('wp_ajax_deleteForms', [$this, 'deleteForms']); 353 add_action('wp_ajax_griffinforms_run_retention_cleanup', [$this, 'runRetentionCleanup']); 335 354 } 336 355 } -
griffinforms-form-builder/trunk/admin/html/pages/lists/submissions.php
r3353005 r3447143 275 275 echo ' '; 276 276 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>'; 277 281 echo '</div>'; 278 282 } -
griffinforms-form-builder/trunk/admin/js/griffinforms-lists.js
r3394319 r3447143 124 124 }); 125 125 }); 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 }); 126 173 }); -
griffinforms-form-builder/trunk/config.php
r3446601 r3447143 5 5 class Config 6 6 { 7 public const VERSION = '2.1. 6.0';7 public const VERSION = '2.1.7.0'; 8 8 public const DB_VER = '1.0'; 9 9 public const PHP_REQUIRED = '8.2'; -
griffinforms-form-builder/trunk/frontend/ajax/submission.php
r3425584 r3447143 174 174 } 175 175 176 $this->truncateSubmissions($this->data['form']);176 // Retention cleanup moved to scheduler to avoid slowing submissions. 177 177 $this->response['success'] = true; 178 178 $form_name = $this->resolveFormName((int) ($this->data['form'] ?? 0)); -
griffinforms-form-builder/trunk/frontend/html/forms/formpage.php
r3421663 r3447143 22 22 } 23 23 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 . '>'; 25 27 $html .= $this->formPageHeader(); 26 28 $html .= $this->pageRows(); … … 275 277 const resumeDefaultGateway = reviewContainer.attr("data-gf-resume-default-gateway") || ""; 276 278 const paymentStyleTokens = getPaymentStyleTokens(); 279 window.gfCaptchaHandlers = window.gfCaptchaHandlers || {}; 280 window.gfCaptchaInitFns = window.gfCaptchaInitFns || {}; 277 281 const paymentFlowState = { 278 282 gateway: null, … … 382 386 antispamData["honeypot"] = honeypotField.length ? honeypotField.val() : ""; 383 387 388 const activePage = $(".griffinforms-formpage-container[data-griffinforms-state='active']").first(); 384 389 const captchaContext = <?php echo wp_json_encode($this->antispam->getCaptchaClientContext($this->data['position'] ?? '')); ?>; 385 390 const providerEnabled = captchaContext && captchaContext.enabled; … … 418 423 }); 419 424 } 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 } 421 435 if (v2Token) { 422 436 antispamData["recaptcha"] = v2Token; … … 429 443 proceedWithSubmission(data, formData); 430 444 } 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 } 433 456 if (!turnstileToken) { 434 457 alert("<?php esc_html_e('Please complete the Turnstile challenge.', 'griffinforms-form-builder'); ?>"); … … 440 463 proceedWithSubmission(data, formData); 441 464 } 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 } 444 476 if (!hcaptchaToken) { 445 477 alert("<?php esc_html_e('Please complete the hCaptcha challenge.', 'griffinforms-form-builder'); ?>"); … … 545 577 } 546 578 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 547 657 function resetPaymentFlowState() { 548 658 paymentFlowState.gateway = null; … … 677 787 } 678 788 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 679 816 if (reviewBackBtn.length) { 680 817 reviewBackBtn.on("click", function(){ -
griffinforms-form-builder/trunk/frontend/html/security/antispam.php
r3421663 r3447143 132 132 $this->errors += 1; 133 133 $this->error_notices[] = $result['message'] ?? __('Unable to submit. CAPTCHA assessment failed.', 'griffinforms-form-builder'); 134 $this->logCaptchaSkip('CAPTCHA validation failed for position "' . $position . '".'); 134 135 } 135 136 } … … 194 195 } 195 196 196 return in_array($position, ['single', 'first' ], true);197 return in_array($position, ['single', 'first', 'middle', 'last'], true); 197 198 } 198 199 -
griffinforms-form-builder/trunk/frontend/html/security/captcha/recaptcha.php
r3421663 r3447143 49 49 $version = $this->getVersion(); 50 50 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); 52 52 } elseif ($version === 'v3') { 53 53 $url = 'https://www.google.com/recaptcha/api.js?render=' . rawurlencode($this->getSiteKey()); … … 69 69 'version' => $this->getVersion(), 70 70 'site_key' => $this->getSiteKey(), 71 'limit_to_first_page' => ($this->getVersion() === 'v2'),71 'limit_to_first_page' => false, 72 72 ]; 73 73 } … … 107 107 108 108 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 } 109 119 return ['success' => false, 'message' => __('Unable to submit. reCAPTCHA assessment failed.', 'griffinforms-form-builder')]; 110 120 } 111 121 112 122 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 } 113 133 return ['success' => false, 'message' => __('Unable to submit. reCAPTCHA score too low.', 'griffinforms-form-builder')]; 114 134 } … … 124 144 125 145 if ($this->getVersion() === 'v2') { 126 return in_array($position, ['first', 'single' ], true);146 return in_array($position, ['first', 'single', 'middle', 'last'], true); 127 147 } 128 148 -
griffinforms-form-builder/trunk/griffinforms.php
r3446601 r3447143 4 4 * Plugin URI: https://griffinforms.com/ 5 5 * 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.06 * Version: 2.1.7.0 7 7 * Requires at least: 6.6 8 8 * Requires PHP: 8.2 -
griffinforms-form-builder/trunk/includes/schedulers/processscheduler.php
r3421663 r3447143 30 30 $log_cleanup->register(); 31 31 32 // Submission retention cleanup scheduler 33 $submission_retention = SubmissionRetentionCleanup::getInstance(); 34 $submission_retention->register(); 35 32 36 // Job queue scheduler 33 37 $job_scheduler = JobScheduler::getInstance(); -
griffinforms-form-builder/trunk/readme.txt
r3446601 r3447143 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.2 7 Stable tag: 2.1. 6.07 Stable tag: 2.1.7.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 172 172 == Changelog == 173 173 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 174 178 = 2.1.6.0 – 2026-01-25 = 175 179 * Feature: Added an Academic templates category with new admissions, program inquiry, and recommendation request forms. … … 474 478 == Upgrade Notice == 475 479 480 = 2.1.7.0 = 481 Multi-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 476 483 = 2.1.6.0 = 477 484 Adds 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.