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 +192 -188 2.2.0 → 3.4.6 View file →
@@ -2,250 +2,254 @@
2 2 * Frontend functionality for subscribers and tags.
3 3 *
4 4 * @since 1.9.6
5 5 *
6 - * @package ConvertKit
7 6 * @author ConvertKit
8 7 */
9 8
10 9 /**
11 - * Tags the given subscriber ID with the given tag
10 + * Remove the url subscriber_id url param
12 11 *
13 - * @since 1.9.6
12 + * The 'ck_subscriber_id' should only be set on URLs included on
13 + * links from a ConvertKit email with no other URL parameters.
14 + * This function removes the parameters so a customer won't share
15 + * a URL with their subscriber ID in it.
14 16 *
15 - * @param int subscriber_id Subscriber ID
16 - * @param string tag Tag
17 - * @param int post_id WordPress Post ID
17 + * @param {string} url URL.
18 18 */
19 -function convertKitTagSubscriber( subscriber_id, tag, post_id ) {
19 +function convertKitRemoveSubscriberIDFromURL(url) {
20 + // Parse URL.
21 + const url_object = new URL(url);
22 + const ck_subscriber_id = url_object.searchParams.get('ck_subscriber_id');
20 23
21 - if ( convertkit.debug ) {
22 - console.log( 'convertKitTagSubscriber' );
23 - console.log( convertkit );
24 - console.log( subscriber_id );
25 - console.log( tag );
26 - console.log( post_id );
24 + // If ck_subscriber_id is null, it's not included in the URL.
25 + // Don't modify the URL.
26 + if (ck_subscriber_id === null) {
27 + return;
27 28 }
28 29
29 - ( function( $ ) {
30 + // Remove ck_subscriber_id from URL params.
31 + url_object.searchParams.delete('ck_subscriber_id');
30 32
31 - $.ajax(
32 - {
33 - type: 'POST',
34 - data: {
35 - action: 'convertkit_tag_subscriber',
36 - convertkit_nonce: convertkit.nonce,
37 - subscriber_id: subscriber_id,
38 - tag: tag,
39 - post_id: post_id
40 - },
41 - url: convertkit.ajaxurl,
42 - success: function ( response ) {
43 - if ( convertkit.debug ) {
44 - console.log( response );
45 - }
46 - convertKitRemoveSubscriberIDFromURL( window.location.href );
47 - }
48 - }
49 - ).fail(
50 - function (response) {
51 - if ( convertkit.debug ) {
52 - console.log( response );
53 - }
54 - convertKitRemoveSubscriberIDFromURL( window.location.href );
55 - }
56 - );
33 + // Get title and string of parameters.
34 + const title = document.getElementsByTagName('title')[0].innerHTML;
35 + let params = url_object.searchParams.toString();
57 36
58 - } )( jQuery );
37 + // Only add '?' if there are parameters.
38 + if (params.length > 0) {
39 + params = '?' + params;
40 + }
59 41
42 + // Update history.
43 + window.history.replaceState(
44 + null,
45 + title,
46 + url_object.pathname + params + url_object.hash
47 + );
48 +
49 + // Emit custom event with the removed subscriber ID.
50 + convertKitEmitCustomEvent('kit_subscriber_id_removed_from_url', {
51 + id: ck_subscriber_id,
52 + });
60 53 }
61 54
62 55 /**
63 - * Gets the subscriber ID for the given email address, storing
64 - * it in the `ck_subscriber_id` cookie if it exists.
56 + * Emit a custom event with optional detail data.
65 57 *
66 - * Typically called when the user completes a ConvertKit Form
67 - * that has either "Auto-confirm new subscribers" or
68 - * "Send subscriber to thank you page" enabled (both scenarios
69 - * include a ck_subscriber_id).
58 + * This function creates and dispatches a custom event with the specified
59 + * event name and detail data.
70 60 *
71 - * @since 1.9.6
61 + * @since 2.5.0
72 62 *
73 - * @param int id Subscriber ID
63 + * @param {string} eventName The name of the custom event to emit.
64 + * @param {Object} [detail={}] Optional detail data to include with the event.
74 65 */
75 -function convertStoreSubscriberIDInCookie( subscriber_id ) {
66 +function convertKitEmitCustomEvent(eventName, detail) {
67 + const event = new CustomEvent(eventName, { detail });
68 + document.dispatchEvent(event);
69 +}
76 70
77 - if ( convertkit.debug ) {
78 - console.log( 'convertStoreSubscriberIDInCookie' );
79 - console.log( subscriber_id );
80 - }
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;
81 78
82 - ( function( $ ) {
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 + }
83 87
84 - $.ajax(
85 - {
86 - type: 'POST',
87 - data: {
88 - action: 'convertkit_store_subscriber_id_in_cookie',
89 - convertkit_nonce: convertkit.nonce,
90 - subscriber_id: subscriber_id
91 - },
92 - url: convertkit.ajaxurl,
93 - success: function ( response ) {
94 - if ( convertkit.debug ) {
95 - console.log( response );
96 - }
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 97
98 - convertKitRemoveSubscriberIDFromURL( window.location.href );
99 - }
100 - }
101 - ).fail(
102 - function (response) {
103 - if ( convertkit.debug ) {
104 - console.log( response );
105 - }
106 -
107 - convertKitRemoveSubscriberIDFromURL( window.location.href );
108 - }
98 +/* eslint-disable no-unused-vars */
99 +/**
100 + * Handles form submissions when reCAPTCHA is enabled.
101 + *
102 + * @param {string} token reCAPTCHA token.
103 + */
104 +function convertKitRecaptchaFormSubmit(token) {
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"]'
109 110 );
110 111
111 - } )( jQuery );
112 + // Get the parent form of the submit button.
113 + const form = submitButton.closest('form');
112 114
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();
113 118 }
114 119
120 +// Scope the function to the window object as webpack will wrap everything in a closure,
121 +// resulting in the function not being available globally.
122 +window.convertKitRecaptchaFormSubmit = convertKitRecaptchaFormSubmit;
123 +
115 124 /**
116 - * Gets the subscriber ID for the given email address, storing
117 - * it in the `ck_subscriber_id` cookie if it exists.
125 + * Holds the form and submit button awaiting a Cloudflare Turnstile token.
118 126 *
119 - * Typically called when the user completes a ConvertKit Form
120 - * that has either "Auto-confirm new subscribers" or
121 - * "Send subscriber to thank you page" enabled (both scenarios
122 - * include a ck_subscriber_id).
123 - *
124 - * @since 1.9.6
125 - *
126 - * @param string emailAddress Email Address
127 + * @since 3.4.6
127 128 */
128 -function convertStoreSubscriberEmailAsIDInCookie( emailAddress ) {
129 +let convertKitTurnstileForm = null;
130 +let convertKitTurnstileSubmitter = null;
129 131
130 - if ( convertkit.debug ) {
131 - console.log( 'convertStoreSubscriberEmailAsIDInCookie' );
132 - console.log( emailAddress );
133 - }
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 + }
134 140
135 - ( function( $ ) {
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 + }
136 148
137 - $.ajax(
138 - {
139 - type: 'POST',
140 - data: {
141 - action: 'convertkit_store_subscriber_email_as_id_in_cookie',
142 - convertkit_nonce: convertkit.nonce,
143 - email: emailAddress
144 - },
145 - url: convertkit.ajaxurl,
146 - success: function ( response ) {
147 - if ( convertkit.debug ) {
148 - console.log( response );
149 - }
150 - }
151 - }
152 - ).fail(
153 - function (response) {
154 - if ( convertkit.debug ) {
155 - console.log( response );
156 - }
157 - }
158 - );
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 + }
159 157
160 - } )( jQuery );
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 + }
161 162
162 -}
163 + // Prevent the submission until a token is generated.
164 + e.preventDefault();
165 + e.stopImmediatePropagation();
163 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 +
164 182 /**
165 - * Remove the url subscriber_id url param
183 + * Resets the given Cloudflare Turnstile widget, so a new token is generated on the
184 + * next submission.
166 185 *
167 - * The 'ck_subscriber_id' should only be set on URLs included on
168 - * links from a ConvertKit email with no other URL parameters.
169 - * This function removes the parameters so a customer won't share
170 - * a URL with their subscriber ID in it.
186 + * @since 3.4.6
171 187 *
172 - * @param url
188 + * @param {Element} widget Turnstile widget.
173 189 */
174 -function convertKitRemoveSubscriberIDFromURL( url ) {
175 -
176 - var clean_url = url.substring( 0, url.indexOf( "?ck_subscriber_id" ) );
177 - var title = document.getElementsByTagName( "title" )[0].innerHTML;
178 - if ( clean_url ) {
179 - window.history.pushState( null, title, clean_url );
190 +function convertKitTurnstileReset(widget) {
191 + if (typeof window.turnstile === 'undefined' || !widget.isConnected) {
192 + return;
180 193 }
181 194
195 + window.turnstile.reset(widget);
182 196 }
183 197
184 198 /**
185 - * Utility function to pause for the given number of milliseconds
199 + * Submits the form awaiting a Cloudflare Turnstile token, once Turnstile has
200 + * generated the token.
186 201 *
187 - * @since 1.9.6
202 + * Turnstile populates a hidden `cf-turnstile-response` input inside the form,
203 + * which is included when the form is submitted.
188 204 *
189 - * @param int milliseconds
205 + * @param {string} token Turnstile response token.
190 206 */
191 -function convertKitSleep( milliseconds ) {
207 +function convertKitTurnstileFormSubmit(token) {
208 + // Bail if no form is awaiting a token.
209 + if (convertKitTurnstileForm === null) {
210 + return;
211 + }
192 212
193 - var start = new Date().getTime();
194 - for (var i = 0; i < 1e7; i++) {
195 - if ((new Date().getTime() - start) > milliseconds) {
196 - break;
197 - }
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();
198 224 }
225 +}
199 226
200 -}
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 */
201 231
202 232 /**
203 - * Register events
233 + * Register events on frontend.
234 + *
235 + * @since 3.2.0
204 236 */
205 -jQuery( document ).ready(
206 - function( $ ) {
237 +if (typeof convertkit !== 'undefined') {
238 + document.addEventListener('DOMContentLoaded', function () {
239 + // Removes `ck_subscriber_id` from the URI.
240 + convertKitRemoveSubscriberIDFromURL(window.location.href);
207 241
208 - if ( convertkit.subscriber_id > 0 && convertkit.tag && convertkit.post_id ) {
209 - // If the user can be detected as a ConvertKit Subscriber (i.e. their Subscriber ID is in a cookie or the URL),
210 - // and the Page/Post they are viewing has a Tag specified, subscribe them to the tag.
211 - convertKitTagSubscriber( convertkit.subscriber_id, convertkit.tag, convertkit.post_id );
212 - } else if ( convertkit.subscriber_id > 0 ) {
213 - // If the user can be detected as a ConvertKit Subscriber (i.e. their Subscriber ID is in a cookie or the URL),
214 - // update the cookie now.
215 - convertStoreSubscriberIDInCookie( convertkit.subscriber_id );
242 + // Set a cookie if any scripts with data-kit-limit-per-session attribute exist.
243 + if (
244 + document.querySelectorAll('script[data-kit-limit-per-session="1"]')
245 + .length > 0
246 + ) {
247 + document.cookie = 'ck_non_inline_form_displayed=1; path=/';
248 + if (convertkit.debug) {
249 + console.log(
250 + 'Set `ck_non_inline_form_displayed` cookie for non-inline form limit'
251 + );
252 + }
216 253 }
217 -
218 - // Store subscriber ID as a cookie from the email address used when a ConvertKit Form is submitted.
219 - $( document ).on(
220 - 'click',
221 - '.formkit-submit',
222 - function() {
223 - var emailAddress = $( 'input[name="email_address"]' ).val();
224 -
225 - // If the email address is empty, don't attempt to get the subscriber ID by email.
226 - if ( ! emailAddress.length ) {
227 - if ( convertkit.debug ) {
228 - console.log( 'email empty' );
229 - }
230 -
231 - return;
232 - }
233 -
234 - // If the email address is invalid, don't attempt to get the subscriber ID by email.
235 - var validator = /^(([^<>()\[\]\\.,;:\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,}))$/;
236 - if ( ! validator.test( emailAddress.toLowerCase() ) ) {
237 - if ( convertkit.debug ) {
238 - console.log( 'email not an email address' );
239 - }
240 -
241 - return;
242 - }
243 -
244 - // Wait a moment before sending the AJAX request.
245 - convertKitSleep( 2000 );
246 - convertStoreSubscriberEmailAsIDInCookie( emailAddress );
247 - }
248 - );
249 -
250 - }
251 -);
254 + });
255 +}