PageSourceSearch

https://birdlife.org.au/wp-content/plugins/atlas-search/public/js/analytics.js?ver=0.3.25

js birdlife.org.au collected 2026-10-01 10:29:03 UTC 9,467 bytes, 328 lines download raw bytes

1class AnalyticsTracker {
2  constructor() {
3    this.url =
4      window.wpeAitkAnalytics.restUrl +
5      'analytics/v1/behavioral-analytics/event';
6    this.isBeaconSupported =
7      navigator && typeof navigator.sendBeacon === 'function';
8  }
9
10  trackEvent(eventType, eventData) {
11    const requestData = {
12      // eslint-disable-next-line no-undef
13      session: { id: userSessionStore.getSessionId() || 'anonymous' },
14      // eslint-disable-next-line no-undef
15      userID: userSessionStore.getUserId() || 'anonymous',
16    };
17
18    const payload = {
19      ...requestData,
20      ...eventData,
21      // eslint-disable-next-line no-undef
22      _wpnonce: wpeAitkAnalytics.nonce,
23    };
24
25    const endpoint = `${this.url}/${eventType}`;
26
27    if (this.isBeaconSupported) {
28      const blob = new Blob([JSON.stringify(payload)], {
29        type: 'application/json',
30      });
31      if (navigator.sendBeacon(endpoint, blob)) {
32        return;
33      }
34    }
35
36    // Fall back to fetch if sendBeacon isn't supported or fails
37    this.sendWithFetch(endpoint, payload);
38  }
39
40  sendWithFetch(endpoint, payload) {
41    fetch(endpoint, {
42      method: 'POST',
43      headers: { 'Content-Type': 'application/json' },
44      body: JSON.stringify(payload),
45      keepalive: true,
46    }).catch((error) => {
47      console.error('Analytics fetch error: ', error);
48    });
49  }
50
51  trackPageView(pageViewData) {
52    return this.trackEvent('page_view', pageViewData);
53  }
54
55  trackSearch(searchData) {
56    return this.trackEvent('search', searchData);
57  }
58
59  trackSearchResultClick(clickData) {
60    return this.trackEvent('search_click', clickData);
61  }
62}
63
64const state = {
65  trackerInstance: null,
66  eventQueue: [],
67  searchResults: [],
68  searchObject: null,
69  clickHandlerInitialized: false,
70};
71
72function removeTrailingSlash(url) {
73  return url.endsWith('/') && url.length > 1 ? url.slice(0, -1) : url;
74}
75
76const AnalyticsTrackerAPI = {
77  createTracker() {
78    if (!state.trackerInstance) {
79      state.trackerInstance = new AnalyticsTracker();
80
81      // Process queued events
82      if (state.eventQueue.length > 0) {
83        state.eventQueue.forEach(([eventType, data]) => {
84          state.trackerInstance.trackEvent(eventType, data);
85        });
86        state.eventQueue = [];
87      }
88    }
89  },
90
91  clickDataParamName: '_recommendations_data',
92
93  trackPageView() {
94    // only track page views for non-search pages that have a document ID
95    const urlParams = new URLSearchParams(window.location.search);
96    // eslint-disable-next-line no-undef
97    if (!wpeAitkAnalytics.documentID || urlParams.has('s')) {
98      return;
99    }
100
101    // Get current URL without click data parameter
102    const currentUrl = new URL(window.location.href);
103    currentUrl.searchParams.delete(this.clickDataParamName);
104    const cleanUrl = currentUrl.toString();
105
106    const pageViewData = {
107      data: {
108        // eslint-disable-next-line no-undef
109        documentID: wpeAitkAnalytics.documentID,
110        page: {
111          referrer: document.referrer,
112          // eslint-disable-next-line no-undef
113          title: wpeAitkAnalytics.title,
114          url: cleanUrl,
115        },
116      },
117    };
118
119    AnalyticsTrackerAPI.createTracker();
120    if (state.trackerInstance) {
121      state.trackerInstance.trackPageView(pageViewData);
122    } else {
123      state.eventQueue.push(['page_view', pageViewData]);
124    }
125  },
126
127  getSearchParams(urlParams) {
128    const searchTerm = urlParams.get('s') || '*';
129    const sortName = urlParams.get('orderby') || 'default';
130    // Get all other parameters as filters
131    const filters = [];
132    const excludeKeys = ['s', 'orderby'];
133
134    [...new Set(urlParams.keys())]
135      .filter((key) => !excludeKeys.includes(key))
136      .forEach((key) => {
137        const items = urlParams.getAll(key).flatMap((item) => item.split(','));
138        if (items.length > 0) {
139          filters.push({
140            filterName: key,
141            items: items,
142          });
143        }
144      });
145    return { searchTerm, filters, sortName };
146  },
147
148  trackSearch(urlParams) {
149    const { searchTerm, filters, sortName } = this.getSearchParams(urlParams);
150
151    // eslint-disable-next-line no-undef
152    state.searchResults = wpeAitkAnalytics.searchResults;
153
154    state.searchObject = {
155      query: searchTerm,
156      filters: filters,
157      sort: { name: sortName },
158      results: {
159        items: state.searchResults.map((item) => ({
160          documentID: item.documentID,
161          page: {
162            title: item.title,
163            referrer: document.referrer,
164            url: item.url,
165          },
166        })),
167      },
168    };
169
170    const searchData = { data: { search: state.searchObject } };
171
172    AnalyticsTrackerAPI.createTracker();
173    if (state.trackerInstance) {
174      state.trackerInstance.trackSearch(searchData);
175    } else {
176      state.eventQueue.push(['search', searchData]);
177    }
178  },
179
180  trackSearchResultClick() {
181    const urlParams = new URLSearchParams(window.location.search);
182    const paramData = urlParams.get(this.clickDataParamName);
183    if (!paramData) {
184      return;
185    }
186
187    const url = new URL(window.location.href);
188    url.searchParams.delete(this.clickDataParamName);
189    const cleanUrl = url.toString();
190    window.history.replaceState(window.history.state, '', cleanUrl);
191
192    if (!document.referrer) {
193      return;
194    }
195    const referrer = new URL(document.referrer);
196
197    if (
198      referrer.hostname !== window.location.hostname ||
199      !referrer.searchParams.has('s')
200    ) {
201      return;
202    }
203
204    const { searchTerm, filters, sortName } = this.getSearchParams(
205      referrer.searchParams
206    );
207
208    let position;
209    try {
210      const decodedData = JSON.parse(atob(paramData));
211      position = decodedData.position;
212    } catch (e) {
213      // If parsing fails, position will be undefined
214    }
215
216    const clickData = {
217      data: {
218        // eslint-disable-next-line no-undef
219        documentID: wpeAitkAnalytics.documentID,
220        ...(position !== undefined && { position }),
221        page: {
222          referrer: document.referrer,
223          // eslint-disable-next-line no-undef
224          title: wpeAitkAnalytics.title,
225          url: cleanUrl,
226        },
227        search: {
228          query: searchTerm,
229          filters: filters,
230          sort: { name: sortName },
231        },
232      },
233    };
234
235    AnalyticsTrackerAPI.createTracker();
236    if (state.trackerInstance) {
237      state.trackerInstance.trackSearchResultClick(clickData);
238    } else {
239      state.eventQueue.push(['search_click', clickData]);
240    }
241  },
242
243  initSearchResultClickTracking() {
244    if (
245      state.searchResults.length === 0 ||
246      !state.searchObject ||
247      state.clickHandlerInitialized
248    ) {
249      return;
250    }
251
252    // Get pagination info to calculate global position
253    // eslint-disable-next-line no-undef
254    const currentPage = wpeAitkAnalytics.currentPage || 1;
255    // eslint-disable-next-line no-undef
256    const postsPerPage = wpeAitkAnalytics.postsPerPage;
257
258    document.querySelectorAll('a').forEach((link) => {
259      if (link.href.startsWith('javascript:')) return;
260
261      const currentPagePath = window.location.href.split('#')[0];
262      const anchorPath = link.href.split('#')[0];
263      if (link.href.includes('#') && anchorPath === currentPagePath) return;
264
265      // Find the position (0-indexed) on current page
266      const localPosition = state.searchResults.findIndex(
267        (result) =>
268          removeTrailingSlash(result.url) === removeTrailingSlash(link.href)
269      );
270
271      if (localPosition !== -1) {
272        // Calculate global position across all pages (1-indexed)
273        let globalPosition;
274        if (postsPerPage === -1) {
275          // No pagination (-1 means show all posts), use local position only
276          globalPosition = localPosition + 1;
277        } else {
278          // With pagination, calculate offset from previous pages
279          const offset = (currentPage - 1) * postsPerPage;
280          globalPosition = offset + localPosition + 1;
281        }
282
283        const paramData = { position: globalPosition };
284        let base64 = btoa(JSON.stringify(paramData));
285        const url = new URL(link.href, window.location.origin);
286        url.searchParams.set(this.clickDataParamName, base64);
287        link.href = url.toString();
288      }
289    });
290    state.clickHandlerInitialized = true;
291  },
292};
293
294function init() {
295  // Track page view on initial load
296  window.addEventListener('pageshow', () => {
297    AnalyticsTrackerAPI.trackPageView();
298    AnalyticsTrackerAPI.trackSearchResultClick();
299  });
300
301  // Track page views on navigation changes
302  const trackPageViewOnNavigation = () => {
303    AnalyticsTrackerAPI.trackPageView();
304  };
305
306  if (window.history) {
307    const originalPushState = window.history.pushState;
308    window.history.pushState = (...args) => {
309      originalPushState.apply(window.history, args);
310      window.dispatchEvent(new Event('ewt:pushstate'));
311    };
312    window.addEventListener('ewt:pushstate', trackPageViewOnNavigation);
313    window.addEventListener('popstate', trackPageViewOnNavigation);
314  } else {
315    window.addEventListener('hashchange', trackPageViewOnNavigation);
316  }
317
318  // Initialize search tracking when the DOM is loaded
319  document.addEventListener('DOMContentLoaded', () => {
320    const urlParams = new URLSearchParams(window.location.search);
321    if (urlParams.has('s')) {
322      AnalyticsTrackerAPI.trackSearch(urlParams);
323      AnalyticsTrackerAPI.initSearchResultClickTracking();
324    }
325  });
326}
327
328init();

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.