PageSourceSearch

https://novafm103.com.br/wp-content/plugins/jpx-ads-manager/assets/js/jpx-ads.js?ver=0.8.0

js novafm103.com.br collected 2026-10-04 07:03:27 UTC 10,536 bytes, 357 lines download raw bytes

1/**
2 * JPX Ads Manager — Frontend engine
3 * Lazy load, IntersectionObserver, sendBeacon, injeção de scripts de agência,
4 * batch requests e tratamento de slot vazio.
5 */
6(function() {
7	'use strict';
8
9	var REST_URL = (window.jpxAdsData && window.jpxAdsData.restUrl) || '/wp-json/jpx-ads/v1/';
10
11	// Contexto da página (categorias/tags) — calculado no PHP no render, pois
12	// dentro do request REST o contexto da página não está disponível.
13	function contextQuery() {
14		var ctx = (window.jpxAdsData && window.jpxAdsData.context) || {};
15		var parts = [];
16		if (ctx.cats && ctx.cats.length) parts.push('cats=' + encodeURIComponent(ctx.cats.join(',')));
17		if (ctx.tags && ctx.tags.length) parts.push('tags=' + encodeURIComponent(ctx.tags.join(',')));
18		return parts.join('&');
19	}
20
21	function init() {
22		var slots = document.querySelectorAll('.jpx-ad-slot');
23		if (!slots.length) return;
24
25		// Agrupa slots para batch request
26		var batchItems = [];
27		var slotMap = [];
28
29		slots.forEach(function(slot, index) {
30			var grupo  = slot.dataset.group;
31			var banner = slot.dataset.banner;
32			if (!grupo && !banner) return;
33
34			if (grupo) {
35				batchItems.push({ type: 'group', id: parseInt(grupo, 10) });
36			} else {
37				batchItems.push({ type: 'banner', id: parseInt(banner, 10) });
38			}
39			slotMap.push({ slot: slot, index: index });
40		});
41
42		if (batchItems.length > 1) {
43			// Tenta batch primeiro
44			var cq = contextQuery();
45			fetch(REST_URL + 'serve-batch' + (cq ? '?' + cq : ''), {
46				method: 'POST',
47				headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
48				body: JSON.stringify(batchItems),
49				cache: 'no-store'
50			})
51			.then(function(r) { return r.json(); })
52			.then(function(data) {
53				if (Array.isArray(data) && data.length === batchItems.length) {
54					data.forEach(function(response, i) {
55						handleSlotResponse(slotMap[i].slot, response);
56					});
57				} else {
58					// Fallback para requisições individuais
59					loadSlotsIndividually(slotMap);
60				}
61			})
62			.catch(function() {
63				// Fallback para requisições individuais
64				loadSlotsIndividually(slotMap);
65			});
66		} else if (batchItems.length === 1) {
67			// Apenas 1 slot: requisição individual direta
68			loadSlotIndividual(slotMap[0].slot);
69		}
70	}
71
72	function loadSlotsIndividually(slotMap) {
73		slotMap.forEach(function(item) {
74			loadSlotIndividual(item.slot);
75		});
76	}
77
78	function loadSlotIndividual(slot) {
79		var grupo  = slot.dataset.group;
80		var banner = slot.dataset.banner;
81		if (!grupo && !banner) return;
82
83		var params = grupo ? 'grupo=' + encodeURIComponent(grupo) : 'banner=' + encodeURIComponent(banner);
84		var cq = contextQuery();
85		if (cq) params += '&' + cq;
86		fetch(REST_URL + 'serve?' + params, {
87			method: 'GET',
88			cache: 'no-store',
89			headers: { 'Accept': 'application/json' }
90		})
91		.then(function(r) { return r.json(); })
92		.then(function(data) {
93			handleSlotResponse(slot, data);
94		})
95		.catch(function() {
96			// REST falhou (rede, WAF, erro 500...): aplica o empty_behavior
97			// do próprio slot em vez de deixar o placeholder cru reservado.
98			handleEmptySlot(slot, {});
99		});
100	}
101
102	function handleSlotResponse(slot, data) {
103		if (window.jpxAdsData && window.jpxAdsData.debug) {
104			console.log('JPX Ads slot response:', slot.dataset.group || slot.dataset.banner, data);
105		}
106		if (data.error === 'no_ad') {
107			handleEmptySlot(slot, data);
108			return;
109		}
110		if (data.error) {
111			if (window.jpxAdsData && window.jpxAdsData.debug) {
112				console.warn('JPX Ads error:', data.error);
113			}
114			return;
115		}
116
117		if (Array.isArray(data)) {
118			// Modo dinâmico — array de anúncios.
119			handleDynamic(slot, data);
120		} else {
121			handleSingle(slot, data);
122		}
123	}
124
125	function handleEmptySlot(slot, data) {
126		var behavior = data.empty_behavior || slot.dataset.emptyBehavior || 'reserve';
127		if (behavior === 'collapse') {
128			slot.style.display = 'none';
129			collapseThemeWrapper(slot);
130			return;
131		}
132		if (behavior === 'house_ad' && data.house_ad) {
133			injectAd(slot, { tipo: 'codigo', conteudo: data.house_ad, id: 0, grupo_id: 0, titulo: 'House Ad', anunciante: '' });
134			return;
135		}
136		// reserve: mantém o placeholder como está
137	}
138
139	// Esconde o wrapper do TEMA (ex: .ad-super-banner) que reserva espaço
140	// ao redor do slot, quando configurado no grupo. Permite portar o
141	// colapso pra qualquer tema sem editar CSS/PHP do tema.
142	function collapseThemeWrapper(slot) {
143		var selector = slot.dataset.wrapperSelector;
144		if (!selector) return;
145		try {
146			var wrapper = slot.closest(selector);
147			if (wrapper) wrapper.style.display = 'none';
148		} catch (e) {
149			// seletor CSS inválido digitado no admin — ignora silenciosamente
150		}
151	}
152
153	function handleSingle(slot, ad) {
154		injectAd(slot, ad);
155		observeViewable(slot, ad);
156		bindClick(slot, ad);
157	}
158
159	function handleDynamic(slot, ads) {
160		if (!ads.length) return;
161		var idx = 0;
162		var interval = parseInt(slot.dataset.interval || '6', 10) * 1000;
163
164		function show(i) {
165			var ad = ads[i % ads.length];
166			injectAd(slot, ad);
167			observeViewable(slot, ad);
168			bindClick(slot, ad);
169		}
170
171		show(0);
172		if (ads.length > 1 && interval >= 1000) {
173			setInterval(function() {
174				idx++;
175				show(idx);
176			}, interval);
177		}
178	}
179
180	function injectAd(slot, ad) {
181		var label = slot.querySelector('.jpx-ad-label');
182		slot.innerHTML = '';
183		if (label) slot.appendChild(label);
184
185		var wrap = document.createElement('div');
186		wrap.className = 'jpx-ad-content';
187
188		if (ad.tipo === 'codigo') {
189			injectWithScripts(wrap, ad.conteudo);
190		} else {
191			wrap.innerHTML = ad.conteudo;
192		}
193
194		slot.appendChild(wrap);
195	}
196
197	function injectWithScripts(container, html) {
198		container.innerHTML = html;
199		var scripts = container.querySelectorAll('script');
200		scripts.forEach(function(oldScript) {
201			var newScript = document.createElement('script');
202			Array.prototype.forEach.call(oldScript.attributes, function(attr) {
203				newScript.setAttribute(attr.name, attr.value);
204			});
205			if (oldScript.src) {
206				newScript.src = oldScript.src;
207				newScript.async = false;
208			} else {
209				newScript.textContent = oldScript.textContent;
210			}
211			oldScript.parentNode.replaceChild(newScript, oldScript);
212		});
213	}
214
215	function observeViewable(slot, ad) {
216		if (!window.IntersectionObserver) {
217			trackEvent(ad, 'i');
218			return;
219		}
220
221		var observed = false;
222		var observer = new IntersectionObserver(function(entries) {
223			entries.forEach(function(entry) {
224				if (entry.isIntersecting && !observed) {
225					observed = true;
226					// Aguarda 1s visível para considerar viewable.
227					setTimeout(function() {
228						trackEvent(ad, 'i');
229						tryGtag('view_promotion', ad);
230					}, 1000);
231					observer.disconnect();
232				}
233			});
234		}, { threshold: 0.5 });
235
236		observer.observe(slot);
237	}
238
239	function bindClick(slot, ad) {
240		if (ad.tipo === 'codigo') return; // clique da agência
241		slot.addEventListener('click', function(e) {
242			var link = e.target.closest('a.jpx-ad-link');
243			if (!link) return;
244			trackEvent(ad, 'c');
245			tryGtag('select_promotion', ad);
246		});
247	}
248
249	function trackEvent(ad, tipo) {
250		var payload = {
251			ad_id:    ad.id,
252			grupo_id: ad.grupo_id || 0,
253			tipo:     tipo
254		};
255		var endpoint = REST_URL + 'event';
256		var body = JSON.stringify(payload);
257
258		function doFetch() {
259			fetch(endpoint, {
260				method: 'POST',
261				headers: { 'Content-Type': 'application/json' },
262				body: body,
263				keepalive: true
264			})
265			.then(function(r) { return r.json(); })
266			.then(function(data) {
267				if (window.jpxAdsData && window.jpxAdsData.debug) {
268					console.log('JPX Ads trackEvent:', data, payload);
269				}
270			})
271			.catch(function(err) {
272				if (window.jpxAdsData && window.jpxAdsData.debug) {
273					console.warn('JPX Ads trackEvent error:', err);
274				}
275				// Fallback para sendBeacon se fetch falhar
276				if (navigator.sendBeacon) {
277					navigator.sendBeacon(endpoint, new Blob([body], { type: 'application/json' }));
278				}
279			});
280		}
281
282		doFetch();
283	}
284
285	function tryGtag(eventName, ad) {
286		// Gating de consentimento LGPD
287		if (window.jpxAdsData && window.jpxAdsData.consentMode === 'respect_banner') {
288			if (!window.jpxAdsConsent || !window.jpxAdsConsent.granted) {
289				return; // não envia ao GA4 sem consentimento
290			}
291		}
292
293		var mode = (window.jpxAdsData && window.jpxAdsData.eventMode) || 'gtag';
294
295		if (mode === 'gtag' && typeof gtag === 'function') {
296			gtag('event', eventName, {
297				creative_name:   ad.titulo,
298				creative_slot:   'grupo_' + (ad.grupo_id || 0),
299				promotion_id:    'ad_' + ad.id,
300				promotion_name:  ad.anunciante || ''
301			});
302		} else if (mode === 'dataLayer' && window.dataLayer) {
303			window.dataLayer.push({
304				event: 'jpx_ads_' + eventName,
305				jpx_creative_name:  ad.titulo,
306				jpx_creative_slot:  'grupo_' + (ad.grupo_id || 0),
307				jpx_promotion_id:   'ad_' + ad.id,
308				jpx_promotion_name: ad.anunciante || ''
309			});
310		}
311	}
312
313	// Objeto global de consentimento — o banner de cookies do portal pode chamar setGranted(true)
314	window.jpxAdsConsent = {
315		granted: false,
316		setGranted: function(v) { this.granted = !!v; }
317	};
318
319	// Integração automática com Complianz (se ativo)
320	if ( typeof window.cmplz_get_consent === 'function' || typeof window.cmplz_has_consent === 'function' ) {
321		function jpxUpdateComplianzConsent() {
322			var granted = false;
323			if ( typeof cmplz_has_consent === 'function' ) {
324				granted = cmplz_has_consent( 'marketing' );
325			} else if ( typeof cmplz_get_consent === 'function' ) {
326				var c = cmplz_get_consent();
327				granted = !!(c && c.marketing);
328			}
329			window.jpxAdsConsent.setGranted( granted );
330		}
331		document.addEventListener( 'cmplzConsentSaved', jpxUpdateComplianzConsent );
332		document.addEventListener( 'cmplzBannerStatusChanged', jpxUpdateComplianzConsent );
333		jpxUpdateComplianzConsent();
334	}
335
336	// Integração via WP Consent API (padrão universal WordPress)
337	if ( typeof window.wpConsent === 'object' && typeof window.wpConsentApi === 'object' ) {
338		function jpxUpdateWpConsent() {
339			var granted = false;
340			if ( typeof wp_has_consent === 'function' ) {
341				granted = wp_has_consent( 'marketing' );
342			} else if ( window.wpConsentApi && typeof window.wpConsentApi.hasConsent === 'function' ) {
343				granted = window.wpConsentApi.hasConsent( 'marketing' );
344			}
345			window.jpxAdsConsent.setGranted( granted );
346		}
347		document.addEventListener( 'wp_consent_type_defined', jpxUpdateWpConsent );
348		document.addEventListener( 'wp_listen_for_consent_change', jpxUpdateWpConsent );
349		jpxUpdateWpConsent();
350	}
351
352	if (document.readyState === 'loading') {
353		document.addEventListener('DOMContentLoaded', init);
354	} else {
355		init();
356	}
357})();

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.