PageSourceSearch

https://culturacolectiva.com/wp-content/plugins/refinery89-publisher-builder/assets/js/analytics.js?ver=2.3.0

js culturacolectiva.com collected 2026-10-01 10:15:30 UTC 11,208 bytes, 441 lines download raw bytes

1/**
2 * Aggregate-only frontend analytics for Ad Unit performance reports.
3 */
4(function() {
5	'use strict';
6
7	var config = window.R89PBAnalytics || {};
8	var sent = {};
9	var sentKeys = [];
10	// Sized for long infinite-scroll sessions: every appended article mints
11	// fresh per-render dedup keys, and evicting a key for a slot still in the
12	// DOM would let a later click re-send its creative impression.
13	var MAX_SENT_KEYS = 2000;
14	var FETCH_TIMEOUT_MS = 10000;
15	var timers = {};
16	var loadWaits = {};
17
18	function debugEnabled() {
19		// Gate on the localized WP_DEBUG flag only. The r89pb-debug body
20		// class cannot serve as a gate here: isEnabled() suppresses all
21		// tracking (and therefore every debugWarn call site) when that class
22		// is present.
23		return !!config.debug;
24	}
25
26	function debugWarn(message) {
27		if (debugEnabled() && window.console && typeof window.console.warn === 'function') {
28			window.console.warn('[R89PB Analytics] ' + message);
29		}
30	}
31
32	function markSent(key) {
33		if (sent[key]) {
34			return;
35		}
36
37		sent[key] = true;
38		sentKeys.push(key);
39
40		while (sentKeys.length > MAX_SENT_KEYS) {
41			delete sent[sentKeys.shift()];
42		}
43	}
44
45	function isEnabled() {
46		if (!config.enabled || !config.endpoint) {
47			return false;
48		}
49
50		if (!document.body) {
51			return false;
52		}
53
54		return !document.body.classList.contains('r89pb-debug') &&
55			!document.body.classList.contains('r89pb-visual-builder-active');
56	}
57
58	function eventKey(eventName, slot) {
59		// The per-render page token is part of the dedup key: infinite-scroll
60		// themes append later articles whose slots reuse the same
61		// unit/placement/instance values but carry a fresh server-signed
62		// token. Without it, appended slots would be deduped against the
63		// initial page and never counted.
64		return [
65			eventName,
66			slot.getAttribute('data-r89pb-unit') || '',
67			slot.getAttribute('data-r89pb-slot') || '',
68			slot.getAttribute('data-r89pb-instance') || '0',
69			slot.getAttribute('data-r89pb-page-token') || ''
70		].join('|');
71	}
72
73	function viewportDevice() {
74		if (window.matchMedia && window.matchMedia('(min-width: 992px)').matches) {
75			return 'desktop';
76		}
77
78		return 'mobile';
79	}
80
81	function placementDevice(slot) {
82		return slot.getAttribute('data-r89pb-placement-device') || 'both';
83	}
84
85	function slotMatchesViewport(slot) {
86		var configured = placementDevice(slot);
87		var viewport = viewportDevice();
88
89		return configured === 'both' || configured === viewport;
90	}
91
92	function slotIsCssVisible(slot) {
93		if (!window.getComputedStyle) {
94			return true;
95		}
96
97		var style = window.getComputedStyle(slot);
98		return style.display !== 'none' && style.visibility !== 'hidden';
99	}
100
101	function slotCanTrack(slot) {
102		return slotMatchesViewport(slot) && slotIsCssVisible(slot);
103	}
104
105	function payloadFor(eventName, slot) {
106		return {
107			event: eventName,
108			unit_id: slot.getAttribute('data-r89pb-unit') || '',
109			placement_id: slot.getAttribute('data-r89pb-slot') || '',
110			instance: slot.getAttribute('data-r89pb-instance') || '0',
111			signature: slot.getAttribute('data-r89pb-track-signature') || '',
112			page_token: slot.getAttribute('data-r89pb-page-token') || '',
113			placement_device: placementDevice(slot),
114			viewport_device: viewportDevice()
115		};
116	}
117
118	function sendEvent(eventName, slot) {
119		if (!isEnabled() || !slot) {
120			return false;
121		}
122
123		var key = eventKey(eventName, slot);
124		if (sent[key]) {
125			return true;
126		}
127
128		var payload = payloadFor(eventName, slot);
129		if (!payload.unit_id || !payload.placement_id || !payload.signature || !payload.page_token) {
130			return false;
131		}
132
133		markSent(key);
134		var body = JSON.stringify(payload);
135
136		if (window.navigator && typeof window.navigator.sendBeacon === 'function') {
137			var blob = new Blob([body], { type: 'application/json' });
138			if (window.navigator.sendBeacon(config.endpoint, blob)) {
139				return true;
140			}
141
142			debugWarn('sendBeacon rejected the "' + eventName + '" event; falling back to fetch.');
143		}
144
145		postWithFetch(eventName, body);
146
147		return true;
148	}
149
150	function postWithFetch(eventName, body) {
151		if (typeof window.fetch !== 'function') {
152			debugWarn('Dropped "' + eventName + '" event: fetch is unavailable.');
153			return;
154		}
155
156		var options = {
157			method: 'POST',
158			credentials: 'same-origin',
159			headers: {
160				'Content-Type': 'application/json'
161			},
162			body: body,
163			keepalive: true
164		};
165
166		var timeoutId = null;
167		if (typeof window.AbortController === 'function') {
168			var controller = new AbortController();
169			options.signal = controller.signal;
170			timeoutId = window.setTimeout(function() {
171				controller.abort();
172			}, FETCH_TIMEOUT_MS);
173		}
174
175		function clearFetchTimeout() {
176			if (timeoutId !== null) {
177				window.clearTimeout(timeoutId);
178				timeoutId = null;
179			}
180		}
181
182		try {
183			window.fetch(config.endpoint, options).then(function(response) {
184				clearFetchTimeout();
185				if (!response.ok) {
186					debugWarn('Tracking request for "' + eventName + '" failed with status ' + response.status + '.');
187				}
188			}).catch(function(error) {
189				clearFetchTimeout();
190				debugWarn('Tracking request for "' + eventName + '" failed: ' + (error && error.message ? error.message : 'network error') + '.');
191			});
192		} catch (error) {
193			clearFetchTimeout();
194			debugWarn('Tracking request for "' + eventName + '" could not start: ' + (error && error.message ? error.message : 'unknown error') + '.');
195		}
196	}
197
198	function creativeElement(slot) {
199		return slot.querySelector('[data-r89pb-creative="1"]');
200	}
201
202	function creativeLoaded(slot) {
203		var img = creativeElement(slot);
204		return !!(img && img.complete && img.naturalWidth > 0);
205	}
206
207	function sendCreativeImpression(slot) {
208		if (!slotCanTrack(slot) || !creativeLoaded(slot)) {
209			return false;
210		}
211
212		return sendEvent('creative_impression', slot);
213	}
214
215	function waitForCreativeLoad(slot, callback) {
216		var img = creativeElement(slot);
217		var key = eventKey('creative_impression', slot);
218
219		if (!img || loadWaits[key]) {
220			return;
221		}
222
223		loadWaits[key] = true;
224		img.addEventListener('load', function() {
225			delete loadWaits[key];
226			callback();
227		}, { once: true });
228	}
229
230	function slotMeetsVisibilityThreshold(slot, minimumRatio) {
231		if (!slotCanTrack(slot) || !slot.getBoundingClientRect) {
232			return false;
233		}
234
235		var rect = slot.getBoundingClientRect();
236		if (rect.width <= 0 || rect.height <= 0) {
237			return false;
238		}
239
240		var viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
241		var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
242		var visibleWidth = Math.max(0, Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0));
243		var visibleHeight = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0));
244		var visibleArea = visibleWidth * visibleHeight;
245		var totalArea = rect.width * rect.height;
246
247		return totalArea > 0 && (visibleArea / totalArea) >= minimumRatio;
248	}
249
250	function initCreativeImpressions(slots) {
251		var creativeSlots = slots.filter(function(slot) {
252			return creativeElement(slot) && slotCanTrack(slot);
253		});
254
255		if (!creativeSlots.length) {
256			return;
257		}
258
259		if (typeof window.IntersectionObserver !== 'function') {
260			creativeSlots.forEach(function(slot) {
261				if (!sendCreativeImpression(slot)) {
262					waitForCreativeLoad(slot, function() {
263						sendCreativeImpression(slot);
264					});
265				}
266			});
267			return;
268		}
269
270		var observer = new IntersectionObserver(function(entries) {
271			entries.forEach(function(entry) {
272				var slot = entry.target;
273				var key = eventKey('creative_impression', slot);
274
275				if (sent[key]) {
276					observer.unobserve(slot);
277					return;
278				}
279
280				if (entry.isIntersecting && entry.intersectionRatio >= 0.5 && slotCanTrack(slot)) {
281					if (!timers[key]) {
282						timers[key] = window.setTimeout(function() {
283							delete timers[key];
284							if (sendCreativeImpression(slot)) {
285								observer.unobserve(slot);
286								return;
287							}
288
289							waitForCreativeLoad(slot, function() {
290								if (slotMeetsVisibilityThreshold(slot, 0.5) && sendCreativeImpression(slot)) {
291									observer.unobserve(slot);
292								}
293							});
294						}, 1000);
295					}
296				} else if (timers[key]) {
297					window.clearTimeout(timers[key]);
298					delete timers[key];
299				}
300			});
301		}, { threshold: [0, 0.5, 1] });
302
303		creativeSlots.forEach(function(slot) {
304			observer.observe(slot);
305		});
306	}
307
308	function closestCreativeLink(target) {
309		while (target && target !== document) {
310			if (target.matches && target.matches('.r89pb-creative-link')) {
311				return target;
312			}
313
314			target = target.parentNode;
315		}
316
317		return null;
318	}
319
320	function addViewportDeviceToClick(link) {
321		if (!window.URL || !link.href) {
322			return;
323		}
324
325		try {
326			var url = new URL(link.href, window.location.href);
327			url.searchParams.set('vd', viewportDevice());
328			link.href = url.toString();
329		} catch (error) {}
330	}
331
332	function handleCreativeClick(event) {
333		if (!isEnabled()) {
334			return;
335		}
336
337		var link = closestCreativeLink(event.target);
338		if (!link) {
339			return;
340		}
341
342		var slot = link.closest ? link.closest('.r89pb-ad-slot') : null;
343		if (slot) {
344			sendCreativeImpression(slot);
345		}
346
347		addViewportDeviceToClick(link);
348	}
349
350	var TRACKABLE_SLOT_SELECTOR = '.r89pb-ad-slot[data-r89pb-unit][data-r89pb-track-signature]';
351	var BOUND_ATTR = 'data-r89pb-analytics-bound';
352	var rescanTimer = null;
353
354	function bindNewSlots() {
355		if (!isEnabled()) {
356			return;
357		}
358
359		var slots = Array.prototype.slice.call(document.querySelectorAll(TRACKABLE_SLOT_SELECTOR)).filter(function(slot) {
360			return !slot.hasAttribute(BOUND_ATTR);
361		});
362
363		if (!slots.length) {
364			return;
365		}
366
367		slots.forEach(function(slot) {
368			slot.setAttribute(BOUND_ATTR, '1');
369			if (slotCanTrack(slot)) {
370				sendEvent('wrapper_render', slot);
371			}
372		});
373
374		initCreativeImpressions(slots);
375	}
376
377	function nodeBringsSlots(node) {
378		if (node.nodeType !== 1) {
379			return false;
380		}
381
382		if (node.matches && node.matches(TRACKABLE_SLOT_SELECTOR)) {
383			return true;
384		}
385
386		return !!(node.querySelector && node.querySelector(TRACKABLE_SLOT_SELECTOR));
387	}
388
389	function scheduleRescan() {
390		if (rescanTimer !== null) {
391			return;
392		}
393
394		rescanTimer = window.setTimeout(function() {
395			rescanTimer = null;
396			bindNewSlots();
397		}, 50);
398	}
399
400	function watchForAppendedSlots() {
401		var root = document.documentElement || document.body;
402		if (typeof window.MutationObserver !== 'function' || !root) {
403			return;
404		}
405
406		var observer = new MutationObserver(function(mutations) {
407			for (var i = 0; i < mutations.length; i++) {
408				var added = mutations[i].addedNodes;
409				for (var j = 0; j < added.length; j++) {
410					if (nodeBringsSlots(added[j])) {
411						scheduleRescan();
412						return;
413					}
414				}
415			}
416		});
417
418		observer.observe(root, { childList: true, subtree: true });
419	}
420
421	function init() {
422		if (!isEnabled()) {
423			return;
424		}
425
426		bindNewSlots();
427
428		// Infinite-scroll themes (JNews auto-load-next-post, Jetpack infinite
429		// scroll, Ajax Load More) append later articles after DOMContentLoaded;
430		// keep binding their slots so impressions keep counting.
431		watchForAppendedSlots();
432	}
433
434	if (document.readyState === 'loading') {
435		document.addEventListener('DOMContentLoaded', init);
436	} else {
437		init();
438	}
439
440	document.addEventListener('click', handleCreativeClick, true);
441})();

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.