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 +142 -119 3.3.2 → 3.4.6 View file →
@@ -6,62 +6,8 @@
6 6 * @author ConvertKit
7 7 */
8 8
9 9 /**
10 - * Gets the subscriber ID for the given email address, storing
11 - * it in the `ck_subscriber_id` cookie if it exists.
12 - *
13 - * Typically called when the user completes a ConvertKit Form
14 - * that has either "Auto-confirm new subscribers" or
15 - * "Send subscriber to thank you page" enabled (both scenarios
16 - * include a ck_subscriber_id).
17 - *
18 - * @since 1.9.6
19 - *
20 - * @param {string} emailAddress Email Address
21 - */
22 -function convertStoreSubscriberEmailAsIDInCookie(emailAddress) {
23 - if (convertkit.debug) {
24 - console.log('convertStoreSubscriberEmailAsIDInCookie');
25 - console.log(emailAddress);
26 - }
27 -
28 - fetch(convertkit.ajaxurl, {
29 - method: 'POST',
30 - headers: {
31 - 'Content-Type': 'application/x-www-form-urlencoded',
32 - 'X-WP-Nonce': convertkit.nonce,
33 - },
34 - body: new URLSearchParams({
35 - email: emailAddress,
36 - }),
37 - })
38 - .then(function (response) {
39 - if (convertkit.debug) {
40 - console.log(response);
41 - }
42 -
43 - return response.json();
44 - })
45 - .then(function (result) {
46 - if (convertkit.debug) {
47 - console.log(result);
48 - }
49 -
50 - // Emit custom event with subscriber ID.
51 - convertKitEmitCustomEvent('convertkit_user_subscribed', {
52 - id: result.id,
53 - email: emailAddress,
54 - });
55 - })
56 - .catch(function (error) {
57 - if (convertkit.debug) {
58 - console.error(error);
59 - }
60 - });
61 -}
62 -
63 -/**
64 10 * Remove the url subscriber_id url param
65 11 *
66 12 * The 'ck_subscriber_id' should only be set on URLs included on
67 13 * links from a ConvertKit email with no other URL parameters.
@@ -106,23 +52,8 @@
106 52 });
107 53 }
108 54
109 55 /**
110 - * Utility function to pause for the given number of milliseconds
111 - *
112 - * @since 1.9.6
113 - * @param {number} milliseconds Number of milliseconds to pause for.
114 - */
115 -function convertKitSleep(milliseconds) {
116 - const start = new Date().getTime();
117 - for (let i = 0; i < 1e7; i++) {
118 - if (new Date().getTime() - start > milliseconds) {
119 - break;
120 - }
121 - }
122 -}
123 -
124 -/**
125 56 * Emit a custom event with optional detail data.
126 57 *
127 58 * This function creates and dispatches a custom event with the specified
128 59 * event name and detail data.
@@ -136,8 +67,35 @@
136 67 const event = new CustomEvent(eventName, { detail });
137 68 document.dispatchEvent(event);
138 69 }
139 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 +
140 98 /* eslint-disable no-unused-vars */
141 99 /**
142 100 * Handles form submissions when reCAPTCHA is enabled.
143 101 *
@@ -143,79 +101,144 @@
143 101 *
144 102 * @param {string} token reCAPTCHA token.
145 103 */
146 104 function convertKitRecaptchaFormSubmit(token) {
147 - // Find submit button with the data-callback attribute.
148 - const submitButton = document.querySelector(
149 - '[type="submit"][data-callback="convertKitRecaptchaFormSubmit"]'
150 - );
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 + );
151 111
152 112 // Get the parent form of the submit button.
153 113 const form = submitButton.closest('form');
154 114
155 - // Submit the form.
156 - 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();
157 118 }
158 119
159 120 // Scope the function to the window object as webpack will wrap everything in a closure,
160 121 // resulting in the function not being available globally.
161 122 window.convertKitRecaptchaFormSubmit = convertKitRecaptchaFormSubmit;
162 -/* eslint-enable no-unused-vars */
163 123
164 124 /**
165 - * Register events on frontend.
125 + * Holds the form and submit button awaiting a Cloudflare Turnstile token.
166 126 *
167 - * @since 3.2.0
127 + * @since 3.4.6
168 128 */
169 -if (typeof convertkit !== 'undefined') {
170 - document.addEventListener('DOMContentLoaded', function () {
171 - // Removes `ck_subscriber_id` from the URI.
172 - convertKitRemoveSubscriberIDFromURL(window.location.href);
129 +let convertKitTurnstileForm = null;
130 +let convertKitTurnstileSubmitter = null;
173 131
174 - // Store subscriber ID as a cookie from the email address used when a ConvertKit Form is submitted.
175 - document.addEventListener('click', function (e) {
176 - // Check if the form submit button was clicked, or the span element was clicked and its parent is the form submit button.
177 - if (
178 - !e.target.matches('.formkit-submit') &&
179 - (!e.target.parentElement ||
180 - !e.target.parentElement.matches('.formkit-submit'))
181 - ) {
182 - if (convertkit.debug) {
183 - console.log('not a ck form');
184 - }
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 + }
185 140
186 - return;
187 - }
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 + }
188 148
189 - // Get email address.
190 - const emailAddress = document.querySelector(
191 - 'input[name="email_address"]'
192 - ).value;
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 + }
193 157
194 - // If the email address is empty, don't attempt to get the subscriber ID by email.
195 - if (!emailAddress.length) {
196 - if (convertkit.debug) {
197 - console.log('email empty');
198 - }
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 + }
199 162
200 - return;
201 - }
163 + // Prevent the submission until a token is generated.
164 + e.preventDefault();
165 + e.stopImmediatePropagation();
202 166
203 - // If the email address is invalid, don't attempt to get the subscriber ID by email.
204 - const validator =
205 - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
206 - if (!validator.test(emailAddress.toLowerCase())) {
207 - if (convertkit.debug) {
208 - console.log('email not an email address');
209 - }
167 + // Store the form and submit button, so the callback submits this form.
168 + convertKitTurnstileForm = form;
169 + convertKitTurnstileSubmitter = e.submitter || null;
210 170
211 - return;
212 - }
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 + }
213 175
214 - // Wait a moment before sending the AJAX request.
215 - convertKitSleep(2000);
216 - convertStoreSubscriberEmailAsIDInCookie(emailAddress);
217 - });
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.
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 + *
205 + * @param {string} token Turnstile response token.
206 + */
207 +function convertKitTurnstileFormSubmit(token) {
208 + // Bail if no form is awaiting a token.
209 + if (convertKitTurnstileForm === null) {
210 + return;
211 + }
212 +
213 + const form = convertKitTurnstileForm;
214 + const submitter = convertKitTurnstileSubmitter;
215 + convertKitTurnstileForm = null;
216 + convertKitTurnstileSubmitter = null;
217 +
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();
224 + }
225 +}
226 +
227 +// Scope the function to the window object as webpack will wrap everything in a closure,
228 +// resulting in the function not being available globally.
229 +window.convertKitTurnstileFormSubmit = convertKitTurnstileFormSubmit;
230 +/* eslint-enable no-unused-vars */
231 +
232 +/**
233 + * Register events on frontend.
234 + *
235 + * @since 3.2.0
236 + */
237 +if (typeof convertkit !== 'undefined') {
238 + document.addEventListener('DOMContentLoaded', function () {
239 + // Removes `ck_subscriber_id` from the URI.
240 + convertKitRemoveSubscriberIDFromURL(window.location.href);
218 241
219 242 // Set a cookie if any scripts with data-kit-limit-per-session attribute exist.
220 243 if (
221 244 document.querySelectorAll('script[data-kit-limit-per-session="1"]')