PageSourceSearch

https://gardenclub.org/modules/custom/ai_subscribe_rest/js/ai_subscribe_rest.js?v=1.1

js gardenclub.org collected 2026-10-01 10:37:33 UTC 16,689 bytes, 533 lines download raw bytes

1/**
2 * @file
3 * Global utilities.
4 */
5
6/* ==========================================================================
7 * Turnstile configuration
8 * ========================================================================== */
9var TURNSTILE_SITE_KEY = '0x4AAAAAAAFOCViB_9IxPWEY';
10var TURNSTILE_SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
11var turnstileScriptPromise = null;
12
13(function ($, Drupal) {
14
15  'use strict';
16
17  /* ==========================================================================
18   * Generic helpers
19   * ======================================================================== */
20
21  /**
22   * Provides a resolved promise regardless of browser support.
23   */
24  function resolvedPromise(value) {
25    if (typeof window.Promise !== 'undefined') {
26      return window.Promise.resolve(value);
27    }
28    if ($.Deferred) {
29      var deferred = $.Deferred();
30      deferred.resolve(value);
31      return deferred.promise();
32    }
33    return null;
34  }
35
36  /**
37   * Fetches a CSRF token from Drupal.
38   */
39  function requestCsrfToken() {
40    return axios.get('/session/token')
41      .then(function (response) {
42        return response.data;
43      });
44  }
45
46  /**
47   * Sends the subscription payload to the REST endpoint.
48   */
49  function postSubscription(form, payload, axiosConfig) {
50    var endpoint = !form.action ? 'api/subscribe' : form.action;
51    return axios.post(endpoint, payload, axiosConfig);
52  }
53
54  /* ==========================================================================
55   * Turnstile lifecycle helpers
56   * ======================================================================== */
57
58  /**
59   * Builds initial Turnstile state and attaches DOM scaffolding.
60   */
61  function createTurnstileState($form) {
62    var state = {
63      widgetId: null,
64      token: '',
65      wrapper: null,
66      container: null,
67      tokenInput: null,
68      mirrorInput: null,
69    };
70
71    if (!TURNSTILE_SITE_KEY) {
72      return state;
73    }
74
75    state.wrapper = $('<div class="turnstile-wrapper" hidden></div>');
76    state.container = $('<div class="cf-turnstile js-turnstile-container"></div>');
77    state.wrapper.append(state.container);
78
79    var $submitButton = $form.find('.form-submit').first();
80    if ($submitButton.length) {
81      state.wrapper.insertBefore($submitButton);
82    }
83    else {
84      $form.append(state.wrapper);
85    }
86
87    state.tokenInput = $('<input type="hidden" name="cf-turnstile-response" value="">');
88    state.mirrorInput = $('<input type="hidden" name="turnstile_token" value="">');
89    $form.append(state.tokenInput, state.mirrorInput);
90
91    return state;
92  }
93
94  /**
95   * Resets the current Turnstile token and UI state.
96   */
97  function resetTurnstile(state) {
98    state.token = '';
99    if (state.tokenInput && state.tokenInput.length) {
100      state.tokenInput.val('');
101    }
102    if (state.mirrorInput && state.mirrorInput.length) {
103      state.mirrorInput.val('');
104    }
105    if (state.container && state.container.length) {
106      state.container.removeClass('error');
107    }
108  }
109
110  /**
111   * Loads the Turnstile script once and shares the promise across forms.
112   */
113  function loadTurnstileScript() {
114    if (!TURNSTILE_SITE_KEY) {
115      return resolvedPromise();
116    }
117    if (window.turnstile) {
118      return resolvedPromise();
119    }
120    if (turnstileScriptPromise) {
121      return turnstileScriptPromise;
122    }
123
124    if (typeof window.Promise === 'undefined' && !$.Deferred) {
125      console.error('Turnstile requires Promise or jQuery Deferred support in this browser.');
126      return null;
127    }
128
129    var clearPromise = function () {
130      turnstileScriptPromise = null;
131    };
132
133    if (typeof window.Promise === 'undefined') {
134      var deferred = $.Deferred();
135      turnstileScriptPromise = deferred.promise();
136
137      var existingScript = document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]');
138      var onLoad = function () {
139        clearPromise();
140        deferred.resolve();
141      };
142      var onError = function (event) {
143        clearPromise();
144        deferred.reject(event);
145      };
146
147      if (existingScript) {
148        existingScript.addEventListener('load', onLoad, { once: true });
149        existingScript.addEventListener('error', onError, { once: true });
150        return turnstileScriptPromise;
151      }
152
153      var legacyScript = document.createElement('script');
154      legacyScript.src = TURNSTILE_SCRIPT_URL;
155      legacyScript.async = true;
156      legacyScript.defer = true;
157      legacyScript.addEventListener('load', onLoad, { once: true });
158      legacyScript.addEventListener('error', onError, { once: true });
159      document.head.appendChild(legacyScript);
160
161      return turnstileScriptPromise;
162    }
163
164    turnstileScriptPromise = new window.Promise(function (resolve, reject) {
165      var existing = document.querySelector('script[src^="https://challenges.cloudflare.com/turnstile/v0/api.js"]');
166
167      var handleLoad = function () {
168        clearPromise();
169        resolve();
170      };
171      var handleError = function (event) {
172        clearPromise();
173        reject(event);
174      };
175
176      if (existing) {
177        if (existing.getAttribute('data-loaded') === 'true' || existing.readyState === 'complete') {
178          clearPromise();
179          resolve();
180          return;
181        }
182        existing.addEventListener('load', handleLoad, { once: true });
183        existing.addEventListener('error', handleError, { once: true });
184        return;
185      }
186
187      var script = document.createElement('script');
188      script.src = TURNSTILE_SCRIPT_URL;
189      script.async = true;
190      script.defer = true;
191      script.addEventListener('load', function () {
192        script.setAttribute('data-loaded', 'true');
193        handleLoad();
194      }, { once: true });
195      script.addEventListener('error', handleError, { once: true });
196      document.head.appendChild(script);
197    });
198
199    return turnstileScriptPromise;
200  }
201
202  /**
203   * Renders the Turnstile widget if it has not already been rendered.
204   */
205  function renderTurnstile(state) {
206    if (!TURNSTILE_SITE_KEY || !state.container || !state.container.length || !window.turnstile) {
207      return;
208    }
209    if (state.widgetId !== null) {
210      return;
211    }
212    try {
213      state.widgetId = window.turnstile.render(state.container[0], {
214        sitekey: TURNSTILE_SITE_KEY,
215        callback: function (token) {
216          state.token = token;
217          if (state.tokenInput && state.tokenInput.length) {
218            state.tokenInput.val(token);
219          }
220          if (state.mirrorInput && state.mirrorInput.length) {
221            state.mirrorInput.val(token);
222          }
223          if (state.container && state.container.length) {
224            state.container.removeClass('error');
225          }
226        },
227        'expired-callback': function () {
228          resetTurnstile(state);
229        },
230        'error-callback': function () {
231          resetTurnstile(state);
232          if (state.container && state.container.length) {
233            state.container.addClass('error');
234          }
235        }
236      });
237    }
238    catch (err) {
239      console.error('Turnstile render failed', err);
240    }
241  }
242
243  /**
244   * Makes the Turnstile widget visible and ensures the script is ready.
245   */
246  function showTurnstile(state) {
247    if (!TURNSTILE_SITE_KEY || !state.wrapper) {
248      return;
249    }
250    state.wrapper.removeAttr('hidden');
251    var scriptLoader = loadTurnstileScript();
252    if (scriptLoader && typeof scriptLoader.then === 'function') {
253      scriptLoader.then(function () {
254        renderTurnstile(state);
255      }).catch(function (error) {
256        console.error('Turnstile script failed to load', error);
257      });
258    }
259    else {
260      renderTurnstile(state);
261    }
262  }
263
264  /* ==========================================================================
265   * Form validation helpers
266   * ======================================================================== */
267
268  /**
269   * Evaluates a single element for the serialized payload.
270   */
271  function evaluateField(element, pattern, context) {
272    var $element = $(element);
273    var value = (element.type === 'checkbox') ? element.checked : element.value;
274    var passes = false;
275
276    if (element.name === 'redirect') {
277      context.redirect = $element;
278    }
279    if (element.name === 'message') {
280      context.message = $element;
281    }
282
283    if (!element.hidden) {
284      if (!element.required) {
285        passes = true;
286      }
287      else if ($element.attr('data-pattern') && typeof pattern[$element.data('pattern')] !== 'undefined') {
288        if (element.type === 'checkbox') {
289          passes = pattern[$element.attr('data-pattern')] === value;
290        }
291        else if (pattern[$element.attr('data-pattern')].test(String(value).toLowerCase())) {
292          passes = true;
293          if (element.name === 'email') {
294            context.email = value;
295          }
296        }
297      }
298      else {
299        passes = value !== '';
300        if (element.name === 'fname') {
301          context.fname = value;
302        }
303        if (element.name === 'lname') {
304          context.lname = value;
305        }
306      }
307    }
308    else {
309      passes = true;
310    }
311
312    context.values[element.name] = value;
313
314    if (passes) {
315      $element.removeClass('error');
316    }
317    else {
318      $element.addClass('error');
319      context.err = true;
320    }
321  }
322
323  /**
324   * Serializes and validates all fields for the outgoing payload.
325   */
326  function validateAndSerializeFields($fields, pattern) {
327    var context = {
328      values: {},
329      err: false,
330      redirect: null,
331      message: null,
332      email: '',
333      fname: '',
334      lname: ''
335    };
336
337    $fields.each(function () {
338      evaluateField(this, pattern, context);
339    });
340
341    return context;
342  }
343
344  /* ==========================================================================
345   * Behavior entrypoint helpers
346   * ======================================================================== */
347
348  /**
349   * Hooks the Turnstile trigger to the first email field.
350   */
351  function attachEmailTrigger($form, turnstileState) {
352    if (!TURNSTILE_SITE_KEY || !turnstileState.wrapper || !turnstileState.container) {
353      return;
354    }
355    var $emailField = $form.find('input[type="email"]').first();
356    if ($emailField.length) {
357      $emailField.one('focus', function () {
358        showTurnstile(turnstileState);
359      });
360    }
361  }
362
363  /**
364   * Builds the submit handler with Turnstile and REST integration.
365   */
366  function createSubmitHandler($form, formState, pattern, $fields) {
367    return function () {
368      var callbacks = {
369        fok: $.noop,
370        ferr: $.noop
371      };
372      if ($(this).data('ai_subscribe') && $.isFunction($(this).data('ai_subscribe').fok)) {
373        callbacks.fok = $(this).data('ai_subscribe').fok;
374      }
375      if ($(this).data('ai_subscribe') && $.isFunction($(this).data('ai_subscribe').ferr)) {
376        callbacks.ferr = $(this).data('ai_subscribe').ferr;
377      }
378
379      var validation = validateAndSerializeFields($fields, pattern);
380      var hasErrors = validation.err;
381
382      if (TURNSTILE_SITE_KEY && !formState.token) {
383        hasErrors = true;
384        if (formState.container && formState.container.length) {
385          formState.container.addClass('error');
386        }
387        showTurnstile(formState);
388      }
389
390      var contactData = Object.assign({}, validation.values);
391      if (TURNSTILE_SITE_KEY) {
392        delete contactData['cf-turnstile-response'];
393        delete contactData['turnstile_token'];
394      }
395
396      var payload = {
397        contact: contactData
398      };
399
400      var currentPageUri = '';
401      if (typeof window !== 'undefined' && window.location) {
402        currentPageUri = (window.location.pathname || '') +
403          (window.location.search || '') +
404          (window.location.hash || '');
405      }
406      if (payload.contact && typeof payload.contact === 'object') {
407        var existingPageUri = payload.contact.page_uri;
408        if (typeof payload.contact['page_uri[]'] !== 'undefined') {
409          existingPageUri = payload.contact['page_uri[]'];
410        }
411        var pageUriList = [];
412        if (Array.isArray(existingPageUri)) {
413          pageUriList = existingPageUri.filter(function (value) {
414            return value;
415          });
416        }
417        else if (typeof existingPageUri === 'string' && existingPageUri.length) {
418          pageUriList = [existingPageUri];
419        }
420        if (currentPageUri) {
421          if (pageUriList.indexOf(currentPageUri) === -1) {
422            pageUriList.push(currentPageUri);
423          }
424        }
425        if (pageUriList.length) {
426          payload.contact.page_uri = pageUriList;
427          if (typeof payload.contact['page_uri[]'] !== 'undefined') {
428            payload.contact['page_uri[]'] = pageUriList;
429          }
430        }
431        else if (currentPageUri) {
432          payload.contact.page_uri = [currentPageUri];
433          if (typeof payload.contact['page_uri[]'] !== 'undefined') {
434            payload.contact['page_uri[]'] = [currentPageUri];
435          }
436        }
437      }
438
439      if (TURNSTILE_SITE_KEY) {
440        payload.turnstile = {
441          token: formState.token || (formState.tokenInput ? formState.tokenInput.val() : ''),
442          site_key: TURNSTILE_SITE_KEY
443        };
444      }
445
446      if (!hasErrors) {
447        requestCsrfToken()
448          .then(function (token) {
449            var axiosConfig = {
450              headers: {
451                'content-type': 'application/json',
452                'accept': 'application/json',
453                'X-CSRF-Token': token
454              }
455            };
456            return postSubscription($form[0], payload, axiosConfig);
457          })
458          .then(function (res) {
459            if (validation.redirect && validation.redirect[0].value && res.data.result.status === 'Subscribed' && res.data.result.redirect) {
460              window.location.href = res.data.result.redirect;
461            }
462            if (!validation.redirect && validation.message && validation.message[0].value && res.data.result === 'Subscribed') {
463              var subscriptionMessage = validation.message[0].value;
464              var $footerSubscribe = $('.b-footer-subscribe');
465              if ($footerSubscribe.length) {
466                $footerSubscribe.find('h2').attr('style', 'opacity:0; -moz-opacity:0; filter:alpha(opacity=0)');
467                $footerSubscribe.find('form').empty().append($('<h2>').append($('<strong>').text(subscriptionMessage)));
468              }
469            }
470            if (!validation.redirect && validation.message && validation.message[0].value && res.data.result !== 'Subscribed') {
471              Swal.fire(res.data.result);
472            }
473            if (TURNSTILE_SITE_KEY) {
474              resetTurnstile(formState);
475              if (window.turnstile && typeof window.turnstile.reset === 'function' && formState.widgetId !== null) {
476                try {
477                  window.turnstile.reset(formState.widgetId);
478                }
479                catch (turnstileError) {
480                  console.error('Turnstile reset failed', turnstileError);
481                }
482              }
483            }
484            if ($.isFunction(callbacks.fok)) {
485              callbacks.fok.call(this);
486            }
487            console.log(res);
488          }.bind(this))
489          .catch(function (error) {
490            console.log(error.response || error);
491          });
492      }
493      else if ($.isFunction(callbacks.ferr)) {
494        callbacks.ferr.call(this);
495      }
496    };
497  }
498
499  /* ==========================================================================
500   * Drupal behavior
501   * ======================================================================== */
502
503  Drupal.behaviors.ai_subscribe_rest = {
504    attach: function (context) {
505      $('.subscribe-rest', context).each(function () {
506        var $form = $(this);
507
508        if ($form.data('aiSubscribeInitialized')) {
509          return;
510        }
511        $form.data('aiSubscribeInitialized', true);
512
513        var form = $form[0];
514        var submitButton = $form.find('.form-submit')[0];
515        if (!form || !submitButton) {
516          return;
517        }
518
519        var pattern = {
520          'false': false,
521          'true': true,
522          'email': /^(([^<>()\[\]\\.,;:\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,}))$/,
523        };
524        var $fields = $form.find('input, textarea, select');
525        var turnstileState = createTurnstileState($form);
526
527        attachEmailTrigger($form, turnstileState);
528        submitButton.onclick = createSubmitHandler($form, turnstileState, pattern, $fields);
529      });
530    }
531  };
532
533})(jQuery, Drupal);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.