PageSourceSearch

https://museuminsider.co.uk/wp-content/plugins/advanced-search-analytics/assets/js/tracking.js

js museuminsider.co.uk collected 2026-10-02 13:04:19 UTC 25,784 bytes, 564 lines download raw bytes

1/**
2 * Advanced Search Filter Analytics - Tracking Script
3 * Automatically tracks search queries and results
4 */
5
6jQuery(document).ready(function($) {
7    
8    console.log('✓ ASF Analytics tracking initialized');
9    
10    // Check if tracking is enabled
11    if (typeof asfAnalytics === 'undefined') {
12        console.log('✗ ASF Analytics not configured');
13        return;
14    }
15    
16    if (!asfAnalytics.settings.trackingEnabled) {
17        console.log('⊘ ASF Analytics tracking is disabled');
18        return;
19    }
20    
21    // Track page load time and views
22    var pageLoadTime = new Date().getTime();
23    var pageViews = 1;
24    
25    // Detect device type
26    var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
27    var deviceType = isMobile ? 'mobile' : 'desktop';
28    console.log('  Device Type:', deviceType);
29    
30    // Get traffic source (referrer)
31    var trafficSource = document.referrer || 'Direct';
32    if (trafficSource && trafficSource !== 'Direct') {
33        try {
34            var referrerUrl = new URL(trafficSource);
35            trafficSource = referrerUrl.hostname;
36        } catch(e) {
37            trafficSource = trafficSource.substring(0, 100);
38        }
39    }
40    console.log('  Traffic Source:', trafficSource);
41    
42    // Function to calculate time spent on page (in seconds)
43    function getTimeSpent() {
44        var currentTime = new Date().getTime();
45        return Math.round((currentTime - pageLoadTime) / 1000);
46    }
47    
48    // Function to collect all active filters
49    function collectFilters(viewType) {
50        var filters = {};
51        
52        try {
53            // Get post types
54            filters.post_types = [];
55            $('input[name=asf-posttype]:checked').each(function() {
56                filters.post_types.push($(this).val());
57            });
58            
59            // Set view type - use passed parameter first, then fallback
60            if (viewType) {
61                filters.view_type = viewType;
62                console.log('  View type set from parameter:', viewType);
63            } else if (window.lastClickedButton) {
64                filters.view_type = window.lastClickedButton;
65                console.log('  View type set from lastClickedButton:', window.lastClickedButton);
66            } else if ($('.map-view-active').length || $('#map-container').is(':visible')) {
67                filters.view_type = 'map';
68                console.log('  View type detected: map');
69            } else {
70                filters.view_type = 'list';
71                console.log('  View type default: list');
72            }
73            
74            // Get regions - ONLY from the dropdown matching the selected post type
75            var regions = [];
76            var regionSelector = '';
77            
78            // Determine which region dropdown to use based on selected post type
79            // If ONLY upstream is selected, use upstream dropdown
80            // Otherwise (tenders, opportunities, or all-post-types), use the general region dropdown
81            if (filters.post_types.length === 1 && filters.post_types[0] === 'upstream') {
82                regionSelector = 'select[name="asf-upstream-region-category[]"]';
83            } else {
84                // For tenders, opportunities, or all-post-types, use the general region dropdown
85                regionSelector = 'select[name="asf-region-category[]"]';
86            }
87            
88            console.log('  DEBUG: Using region selector:', regionSelector);
89            
90            $(regionSelector).each(function() {
91                var $select = $(this);
92                var selectName = $select.attr('name') || '';
93                
94                console.log('  DEBUG: Processing region select:', selectName);
95                console.log('  DEBUG: Selected options count:', $select.find('option:selected').length);
96                
97                $select.find('option:selected').each(function() {
98                    var val = $(this).val();
99                    var text = $(this).text().trim();
100                    if (val && val !== '' && val !== 'all' && val !== '0' && text) {
101                        regions.push(text);
102                    }
103                });
104            });
105            
106            // Remove duplicates
107            regions = regions.filter(function(item, pos, self) {
108                return self.indexOf(item) === pos;
109            });
110            
111            if (regions.length > 0) {
112                filters.regions = regions;
113                console.log('  ✓ Captured regions:', regions);
114            } else {
115                console.log('  No regions captured');
116            }
117            
118            // Get countries - ONLY from the dropdown matching the selected post type
119            var countries = [];
120            var countrySelector = '';
121            
122            // Determine which country dropdown to use based on selected post type
123            // Priority: tenders > opportunities > upstream
124            if (filters.post_types.indexOf('tenders') !== -1) {
125                countrySelector = 'select[name="asf-tenders-country[]"]';
126            } else if (filters.post_types.indexOf('opportunities') !== -1) {
127                countrySelector = 'select[name="asf-opp-country[]"]';
128            } else if (filters.post_types.length === 1 && filters.post_types[0] === 'upstream') {
129                countrySelector = 'select[name="asf-upstream-country[]"]';
130            }
131            
132            console.log('  DEBUG: Using country selector:', countrySelector);
133            
134            if (countrySelector) {
135                $(countrySelector).each(function() {
136                    var $select = $(this);
137                    var selectName = $select.attr('name') || '';
138                    
139                    console.log('  DEBUG: Processing country select:', selectName);
140                    console.log('  DEBUG: Selected options count:', $select.find('option:selected').length);
141                    
142                    $select.find('option:selected').each(function(index) {
143                        var val = $(this).val();
144                        var text = $(this).text().trim();
145                        if (index < 5) { // Only log first 5 to avoid clutter
146                            console.log('  DEBUG: Selected option', index, '- val:', val, 'text:', text);
147                        }
148                        if (val && val !== '' && val !== 'all' && val !== '0' && text) {
149                            countries.push(text);
150                        }
151                    });
152                });
153            }
154            
155            // Remove duplicates
156            countries = countries.filter(function(item, pos, self) {
157                return self.indexOf(item) === pos;
158            });
159            
160            if (countries.length > 0) {
161                filters.countries = countries;
162                console.log('  ✓ Captured countries:', countries);
163            } else {
164                console.log('  No countries captured');
165            }
166            
167            // NOTE: We deliberately do NOT capture all checkboxes and dropdowns
168            // Only capture specific filters that are meaningful (regions, countries, view_type, etc.)
169            // This prevents storing massive arrays of unselected options
170            
171            // If you need to track specific additional filters, add them explicitly here
172            // Example: Get awards checkbox
173            // if ($('.asf-chk-awards').is(':checked')) {
174            //     filters.awards_only = true;
175            // }
176            
177        } catch (error) {
178            console.error('Error collecting filters:', error);
179        }
180        
181        return filters;
182    }
183    
184    // Function to count results
185    function countResults() {
186        try {
187            var count = 0;
188            
189            console.log('  Searching for results count...');
190            
191            // FIRST: Check for asf-records-found element (priority)
192            var $asfRecords = $('.asf-records-found, #asf-records-found, .asf-recounds-found, #asf-recounds-found');
193            if ($asfRecords.length > 0) {
194                var asfText = $asfRecords.text();
195                console.log('  Found asf-records-found element:', asfText);
196                
197                // Check if text contains multiple post type counts (e.g., "81 tenders, 0 opportunities and 7 upstreams")
198                // Pattern: "X tenders, Y opportunities and Z upstreams"
199                var multiTypeMatch = asfText.match(/(\d+)\s+tenders?,\s*(\d+)\s+opportunities\s+and\s+(\d+)\s+upstreams?/i);
200                if (multiTypeMatch) {
201                    var tenders = parseInt(multiTypeMatch[1]) || 0;
202                    var opportunities = parseInt(multiTypeMatch[2]) || 0;
203                    var upstreams = parseInt(multiTypeMatch[3]) || 0;
204                    count = tenders + opportunities + upstreams;
205                    console.log('  ✓ Found multi-type count - Tenders:', tenders, 'Opportunities:', opportunities, 'Upstreams:', upstreams, 'Total:', count);
206                    return count;
207                }
208                
209                // Pattern: "X tenders and Y opportunities" (no upstreams)
210                var twoTypeMatch = asfText.match(/(\d+)\s+tenders?\s+and\s+(\d+)\s+opportunities/i);
211                if (twoTypeMatch) {
212                    var tenders = parseInt(twoTypeMatch[1]) || 0;
213                    var opportunities = parseInt(twoTypeMatch[2]) || 0;
214                    count = tenders + opportunities;
215                    console.log('  ✓ Found two-type count - Tenders:', tenders, 'Opportunities:', opportunities, 'Total:', count);
216                    return count;
217                }
218                
219                // Fallback: just get first number if no pattern matches
220                var asfMatch = asfText.match(/(\d+)/);
221                if (asfMatch) {
222                    count = parseInt(asfMatch[0]);
223                    console.log('  ✓ Using first number from asf-records-found:', count);
224                    return count;
225                }
226            }
227            
228            // Try to find total count in text (like "Total 895 records foun
228d")
229            var totalText = $('body').text();
230            console.log('  Searching for total in page text...');
231            
232            // Try multiple patterns
233            var patterns = [
234                /Total\s+(\d+)\s+tenders?\s+found/i,
235                /Total\s+(\d+)\s+records?\s+found/i,
236                /(\d+)\s+tenders?\s+found/i,
237                /(\d+)\s+records?\s+found/i,
238                /(\d+)\s+results?\s+found/i,
239                /Found\s+(\d+)\s+results?/i,
240                /Showing\s+\d+[-–]\d+\s+of\s+(\d+)/i,
241                /Displaying\s+\d+[-–]\d+\s+of\s+(\d+)/i,
242                /of\s+(\d+)\s+total/i,
243                /(\d+)\s+total\s+results?/i,
244                /(\d+)\s+opportunities/i
245            ];
246            
247            for (var i = 0; i < patterns.length; i++) {
248                var matches = totalText.match(patterns[i]);
249                if (matches && matches[1]) {
250                    count = parseInt(matches[1]);
251                    console.log('  ✓ Found total in text (pattern ' + i + '):', count);
252                    return count;
253                }
254            }
255            
256            // Try to find in specific elements with common class names
257            var selectors = [
258                '.asf-records-found', '#asf-records-found',
259                '.results-count', '.result-count', '.total-results', 
260                '#results-count', '.asf-result-count', '.search-count',
261                '.found-posts', '.wp-pagenavi', '.pagination-info',
262                '[class*="result"][class*="count"]', '[id*="result"][id*="count"]',
263                '[class*="records"][class*="found"]'
264            ];
265            
266            for (var j = 0; j < selectors.length; j++) {
267                var $elem = $(selectors[j]);
268                if ($elem.length > 0) {
269                    var text = $elem.text();
270                    var numMatch = text.match(/(\d+)/);
271                    if (numMatch) {
272                        count = parseInt(numMatch[0]);
273                        console.log('  ✓ Found count in element ' + selectors[j] + ':', count);
274                        return count;
275                    }
276                }
277            }
278            
279            // Check for AJAX response data (if available in window object)
280            if (window.asfSearchResults && window.asfSearchResults.total) {
281                count = parseInt(window.asfSearchResults.total);
282                console.log('  ✓ Found count in window object:', count);
283                return count;
284            }
285            
286            // Fallback: count DOM elements
287            var selector = asfAnalytics.settings.resultSelector;
288            count = $(selector).length;
289            
290            if (count === 0) {
291                count = $('.result-item').length;
292            }
293            if (count === 0) {
294                count = $('.search-result').length;
295            }
296            if (count === 0) {
297                count = $('.post-item').length;
298            }
299            if (count === 0) {
300                count = $('article').length;
301            }
302            
303            console.log('  ⚠ Fallback: Counted DOM elements:', count);
304            console.warn('  Could not find total results text. Consider checking page HTML structure.');
305            return count;
306        } catch (error) {
307            console.error('Error counting results:', error);
308            return 0;
309        }
310    }
311    
312    // Tracking state to prevent duplicates
313    var lastTrackedSearch = null;
314    var lastTrackedTime = 0;
315    var trackingInProgress = false;
316    
317    // Function to track search
318    function trackSearch(keyword, postType, filters, resultsCount) {
319        // Keyword already validated in handleSearchTracking, so just use it
320        keyword = keyword.trim();
321        
322        // Create a unique identifier for this search
323        var searchId = keyword + '|' + postType + '|' + JSON.stringify(filters);
324        var currentTime = Date.now();
325        
326        // Prevent duplicate tracking of the same search within 10 seconds
327        if (lastTrackedSearch === searchId && (currentTime - lastTrackedTime) < 10000) {
328            console.log('⊘ Skipping duplicate search tracking (same search within 10 seconds)');
329            return;
330        }
331        
332        // Prevent any tracking while previous request is in progress
333        if (trackingInProgress) {
334            console.log('⊘ Skipping: tracking already in progress');
335            return;
336        }
337        
338        console.log('→ Proceeding with tracking:', keyword);
339        console.log('  Results count:', resultsCount);
340        console.log('  Post type:', postType);
341        
342        trackingInProgress = true;
343        lastTrackedSearch = searchId;
344        lastTrackedTime = currentTime;
345        
346        var filtersJson = JSON.stringify(filters);
347        var timeSpent = getTimeSpent();
348        
349        console.log('  Sending filters JSON:', filtersJson);
350        console.log('  Filters object:', filters);
351        console.log('  Time spent on page:', timeSpent, 'seconds');
352        console.log('  Page views:', pageViews);
353        console.log('  Traffic source:', trafficSource);
354        console.log('  Device type:', deviceType);
355        
356        console.log('📤 Sending AJAX request to:', asfAnalytics.ajaxUrl);
357        
358        $.ajax({
359            url: asfAnalytics.ajaxUrl,
360            type: 'POST',
361            data: {
362                action: 'asf_save_search_analytics',
363                nonce: asfAnalytics.nonce,
364                search_keyword: keyword.trim(),
365                post_type: postType || 'unknown',
366                filters_applied: filtersJson,
367                results_count: resultsCount || 0,
368                page_views: pageViews,
369                time_spent: timeSpent,
370                traffic_source: trafficSource,
371                device_type: deviceType
372            },
373            success: function(response) {
374                console.log('📥 AJAX Response received:', response);
375                
376                if (response.success) {
377                    console.log('✓ Search tracked successfully (ID: ' + response.data.id + ')');
378                    if (response.data.filters_saved) {
379                        console.log('  Filters saved to DB:', response.data.filters_saved);
380                    }
381                } else {
382                    console.error('✗ Tracking failed:', response.data.message);
383                    if (response.data.error) {
384                        console.error('  Error details:', response.data.error);
385                    }
386                    if (response.data.query) {
387                        console.error('  Failed query:', response.data.query);
388                    }
389                }
390                trackingInProgress = false;
391            },
392            error: function(xhr, status, error) {
393                console.error('✗ AJAX Request Failed');
394                console.error('  Status:', status);
395                console.error('  Error:', error);
396                console.error('  Response:', xhr.responseText);
397                console.error('  Status Code:', xhr.status);
398                
399                try {
400                    var errorData = JSON.parse(xhr.responseText);
401                    console.error('  Parsed error:', errorData);
402                } catch(e) {
403                    console.error('  Could not parse error response');
404                }
405                
406                trackingInProgress = false;
407            },
408            complete: function() {
409                // Always reset tracking flag after completion
410                trackingInProgress = false;
411            }
412        });
413    }
414    
415    // Function to handle tracking (called directly from button click)
416    function handleSearchTracking(viewType) {
417        console.log('→ Search button clicked, will track after results load...');
418        console.log('  View type parameter:', viewType);
419        
420        // Collect search data immediately (BEFORE any delays)
421        var keyword = $('#global_search_keyword_as').val() || 
422                     $('#global_search_keyword').val() || 
423                     $('input[name="global_search_keyword"]').val() ||
424                     $('input[type="search"]').val() ||
425                     $('input.search-field').val() || '';
426        
427        // Trim and store immediately
428        keyword = keyword.trim();
429        
430        // Check keyword length immediately
431        if (!keyword || keyword.length < 3) {
432            console.log('⊘ Skipping tracking: keyword too short ("' + keyword + '" - min 3 chars required)');
433            return;
434        }
435        
436        var postType = $('#hidden-asf-posttype').val() || 
437                      $('input[name="asf-posttype"]:checked').val() || 
438                      'all-post-types';
439        
440        var filters = collectFilters(viewType);
441        
442        console.log('  ✓ Keyword captured:', keyword);
443        console.log('  ✓ Post Type:', postType);
444        console.log('  ✓ View Type:', filters.view_type || 'unknown');
445        console.log('  ✓ Regions:', filters.regions || 'none');
446        console.log('  ✓ Countries:', filters.countries || 'none');
447        console.log('  ✓ All Filters:', filters);
448        
449        // Wait for results to load before counting and tracking
450        var timeout = parseInt(asfAnalytics.settings.resultTimeout) || 8000;
451        
452        setTimeout(function() {
453            var resultsCount = countResults();
454            console.log('  Final results count:', resultsCount);
455            
456            if (resultsCount === 0) {
457                // Map view may take longer to update, retry with longer delays
458                console.log('  Results count is 0, waiting 3 more seconds...');
459                setTimeout(function() {
460                    resultsCount = countResults();
461                    console.log('  Retry 1: Final results count:', resultsCount);
462                    
463                    if (resultsCount === 0) {
464                        // One final retry for map view
465                        console.log('  Still 0, waiting 3 more seconds...');
466                        setTimeout(function() {
467                            resultsCount = countResults();
468                            console.log('  Retry 2: Final results count:', resultsCount);
469                            trackSearch(keyword, postType, filters, resultsCount);
470                        }, 3000);
471                    } else {
472                        trackSearch(keyword, postType, filters, resultsCount);
473                    }
474                }, 3000);
475            } else {
476                trackSearch(keyword, postType, filters, resultsCount);
477            }
478        }, timeout);
479    }
480    
481    // Hook into specific search submit buttons (including map/list view buttons and search icon)
482    // Use both 'click' and 'touchend' events for mobile support
483    $(document).on('click touchend', '#submit, #submit-map, #global_btn_search, .btn-map-view, .btn-list-view, .lnk-submit, .search-icon, .fa-search, button[type="submit"]', function(e) {
484        var buttonId = $(this).attr('id');
485        var buttonClass = $(this).attr('class') || '';
486        console.log('✓ ASF: Search button clicked - ID:', buttonId, 'Class:', buttonClass);
487        console.log('  Event type:', e.type);
488        console.log('  Device: Mobile =', /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent));
489        
490        // Determine view type based on button
491        var viewType = 'list'; // default
492        if (buttonId === 'submit-map' || buttonClass.indexOf('btn-map-view') !== -1 || buttonClass.indexOf('map') !== -1) {
493            viewType = 'map';
494            console.log('  View type: MAP');
495        } else if (buttonId === 'submit' || buttonClass.indexOf('btn-list-view') !== -1 || buttonClass.indexOf('list') !== -1) {
496            viewType = 'list';
497            console.log('  View type: LIST');
498        }
499        
500        // Store for backup
501        window.lastClickedButton = viewType;
502        
503        // Call tracking directly with view type
504        handleSearchTracking(viewType);
505    });
506    
507    // Track when user presses Enter in search input (mobile keyboard support)
508    $(document).on('keypress keyup', '#global_search_keyword_as, #global_search_keyword, input[name="global_search_keyword"]', function(e) {
509        if (e.which === 13 || e.keyCode === 13) { // Enter key
510            console.log('✓ ASF: Enter key pressed in search field');
511            console.log('  Event type:', e.type);
512            
513            // Prevent duplicate triggering from both keypress and keyup
514            if (e.type === 'keyup') return;
515            
516            // Determine view type - check which view is currently active
517            var viewType = 'list'; // default
518            if ($('.btn-map-view').is(':visible') && $('.btn-list-view').css('display') === 'none') {
519                viewType = 'map';
520                console.log('  View type: MAP (map view active)');
521            } else {
522                console.log('  View type: LIST (list view active or default)');
523            }
524            
525            // Call tracking with determined view type
526            handleSearchTracking(viewType);
527            
528            // Note: We don't prevent default because the search needs to actually execute
529            // The duplicate prevention in trackSearch() will handle any double-tracking
530        }
531    });
532    
533    // Debug: Log ALL button clicks/touches to see what's being clicked on mobile
534    $(document).on('click touchend', 'button, input[type="submit"], input[type="button"], a.button', function(e) {
535        var elem = $(this);
536        console.log('ASF Debug: Button interacted -', 
537                   'Event:', e.type,
538                   'ID:', elem.attr('id') || 'none', 
539                   'Class:', elem.attr('class') || 'none',
540                   'Text:', elem.text().substring(0, 20));
541    });
542    
543    // Also try to hook into any form submissions
544    $(document).on('submit', 'form', function(e) {
545        var $form = $(this);
546        console.log('ASF Debug: Form submitted -', 
547                   'ID:', $form.attr('id') || 'none',
548                   'Action:', $form.attr('action') || 'none');
549        
550        // Check if it's a search form
551        if ($form.find('#global_search_keyword_as, #global_search_keyword').length > 0) {
552            console.log('✓ ASF: Search form submitted, tracking...');
553            handleSearchTracking();
554        }
555    });
556    
557    console.log('✓ Event handlers attached to document');
558    console.log('✓ Debug logging enabled - will show all button clicks');
559    
560    // AJAX monitoring disabled - tracking happens via button clicks only
561    // This prevents duplicate tracking when both button and AJAX fire
562    console.log('✓ Tracking via button clicks only (AJAX monitoring disabled)');
563    
564});

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.