PluginProbe
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages / 3.4.6
Kit (formerly ConvertKit) – Email Newsletter, Email Marketing, Membership, Subscribers and Landing Pages v3.4.6
3.4.6 3.4.5 3.4.4 3.4.3 3.4.2 3.4.1 3.4.0 3.3.9 3.3.8 3.3.7 3.3.6 3.3.5 3.3.4 3.3.3 3.3.2 3.3.1 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 All 199 releases
← All changes | resources/frontend/js/convertkit.js +126 -23 3.3.7 → 3.4.6 View file →
@@ -67,8 +67,35 @@
67 67 const event = new CustomEvent(eventName, { detail });
68 68 document.dispatchEvent(event);
69 69 }
70 70
71 +/**
72 + * Holds the most recently clicked reCAPTCHA submit button, so the reCAPTCHA
73 + * callback submits that button's form when a page has multiple forms.
74 + *
75 + * @since 3.4.6
76 + */
77 +let convertKitRecaptchaSubmitButton = null;
78 +
79 +// Store the clicked reCAPTCHA submit button. This runs in the capture phase,
80 +// before reCAPTCHA's own click handler on the button.
81 +document.addEventListener(
82 + 'click',
83 + function (e) {
84 + if (!(e.target instanceof Element)) {
85 + return;
86 + }
87 +
88 + const button = e.target.closest(
89 + '[type="submit"][data-callback="convertKitRecaptchaFormSubmit"]'
90 + );
91 + if (button) {
92 + convertKitRecaptchaSubmitButton = button;
93 + }
94 + },
95 + true
96 +);
97 +
71 98 /* eslint-disable no-unused-vars */
72 99 /**
73 100 * Handles form submissions when reCAPTCHA is enabled.
74 101 *
@@ -74,18 +101,21 @@
74 101 *
75 102 * @param {string} token reCAPTCHA token.
76 103 */
77 104 function convertKitRecaptchaFormSubmit(token) {
78 - // Find submit button with the data-callback attribute.
79 - const submitButton = document.querySelector(
80 - '[type="submit"][data-callback="convertKitRecaptchaFormSubmit"]'
81 - );
105 + // Use the clicked submit button, falling back to the first reCAPTCHA submit button on the page.
106 + const submitButton =
107 + convertKitRecaptchaSubmitButton ||
108 + document.querySelector(
109 + '[type="submit"][data-callback="convertKitRecaptchaFormSubmit"]'
110 + );
82 111
83 112 // Get the parent form of the submit button.
84 113 const form = submitButton.closest('form');
85 114
86 - // Submit the form.
87 - form.submit();
115 + // Submit the form, using requestSubmit() so any submit event listeners are honored
116 + // e.g. the Member Content login form, which submits using AJAX.
117 + form.requestSubmit();
88 118 }
89 119
90 120 // Scope the function to the window object as webpack will wrap everything in a closure,
91 121 // resulting in the function not being available globally.
@@ -91,35 +121,108 @@
91 121 // resulting in the function not being available globally.
92 122 window.convertKitRecaptchaFormSubmit = convertKitRecaptchaFormSubmit;
93 123
94 124 /**
95 - * Handles form submissions when Cloudflare Turnstile is enabled.
125 + * Holds the form and submit button awaiting a Cloudflare Turnstile token.
96 126 *
97 - * Turnstile auto-populates a hidden `cf-turnstile-response` input inside the
98 - * enclosing form once the challenge is solved, so we just need to submit the
99 - * containing form when the callback fires.
127 + * @since 3.4.6
128 + */
129 +let convertKitTurnstileForm = null;
130 +let convertKitTurnstileSubmitter = null;
131 +
132 +// Generate a Cloudflare Turnstile token when a form is submitted, before other submit listeners run.
133 +document.addEventListener(
134 + 'submit',
135 + function (e) {
136 + const form = e.target;
137 + if (!(form instanceof HTMLFormElement)) {
138 + return;
139 + }
140 +
141 + // Bail if the form doesn't contain a Turnstile widget.
142 + const widget = form.querySelector(
143 + '.cf-turnstile[data-callback="convertKitTurnstileFormSubmit"]'
144 + );
145 + if (!widget) {
146 + return;
147 + }
148 +
149 + // Permit the submission if the form has a token, resetting the widget as each token can only be used once.
150 + const response = form.querySelector('[name="cf-turnstile-response"]');
151 + if (response !== null && response.value !== '') {
152 + setTimeout(function () {
153 + convertKitTurnstileReset(widget);
154 + }, 0);
155 + return;
156 + }
157 +
158 + // Permit the submission if the Turnstile script isn't loaded, so the server returns an error.
159 + if (typeof window.turnstile === 'undefined') {
160 + return;
161 + }
162 +
163 + // Prevent the submission until a token is generated.
164 + e.preventDefault();
165 + e.stopImmediatePropagation();
166 +
167 + // Store the form and submit button, so the callback submits this form.
168 + convertKitTurnstileForm = form;
169 + convertKitTurnstileSubmitter = e.submitter || null;
170 +
171 + // Render the widget if it was inserted after the page loaded e.g. following a failed Member Content login.
172 + if (response === null) {
173 + window.turnstile.render(widget);
174 + }
175 +
176 + // Generate a token.
177 + window.turnstile.execute(widget);
178 + },
179 + true
180 +);
181 +
182 +/**
183 + * Resets the given Cloudflare Turnstile widget, so a new token is generated on the
184 + * next submission.
100 185 *
186 + * @since 3.4.6
187 + *
188 + * @param {Element} widget Turnstile widget.
189 + */
190 +function convertKitTurnstileReset(widget) {
191 + if (typeof window.turnstile === 'undefined' || !widget.isConnected) {
192 + return;
193 + }
194 +
195 + window.turnstile.reset(widget);
196 +}
197 +
198 +/**
199 + * Submits the form awaiting a Cloudflare Turnstile token, once Turnstile has
200 + * generated the token.
201 + *
202 + * Turnstile populates a hidden `cf-turnstile-response` input inside the form,
203 + * which is included when the form is submitted.
204 + *
101 205 * @param {string} token Turnstile response token.
102 206 */
103 207 function convertKitTurnstileFormSubmit(token) {
104 - // Find the Turnstile widget div with the data-callback attribute.
105 - const widget = document.querySelector(
106 - '.cf-turnstile[data-callback="convertKitTurnstileFormSubmit"]'
107 - );
108 -
109 - if (!widget) {
208 + // Bail if no form is awaiting a token.
209 + if (convertKitTurnstileForm === null) {
110 210 return;
111 211 }
112 212
113 - // Get the parent form of the widget.
114 - const form = widget.closest('form');
213 + const form = convertKitTurnstileForm;
214 + const submitter = convertKitTurnstileSubmitter;
215 + convertKitTurnstileForm = null;
216 + convertKitTurnstileSubmitter = null;
115 217
116 - if (!form) {
117 - return;
218 + // Submit the form, using requestSubmit() so any submit event listeners are honored
219 + // e.g. the Member Content login form, which submits using AJAX.
220 + if (submitter !== null && submitter.form === form) {
221 + form.requestSubmit(submitter);
222 + } else {
223 + form.requestSubmit();
118 224 }
119 -
120 - // Submit the form.
121 - form.submit();
122 225 }
123 226
124 227 // Scope the function to the window object as webpack will wrap everything in a closure,
125 228 // resulting in the function not being available globally.