PageSourceSearch

https://aframnews.com/wp-content/plugins/leaky-paywall/js/leaky-paywall-check.js?ver=5.2.0

js aframnews.com collected 2026-10-02 11:43:00 UTC 8,194 bytes, 365 lines download raw bytes

1/**
2 * Leaky Paywall Restriction Check
3 *
4 * Optimized replacement for leaky-paywall-cookie.js
5 * Uses REST API instead of admin-ajax.php for better performance.
6 *
7 * @package Leaky Paywall
8 * @since 4.23.0
9 */
10
11(function () {
12	'use strict';
13
14	var config = window.leaky_paywall_check_config || {};
15	var STORAGE_KEY = 'lp_viewed_content';
16
17	/**
18	 * Dispatch a custom event for other plugins to listen to
19	 *
20	 * @param {string} eventName
21	 * @param {Object} detail
22	 */
23	function dispatchPaywallEvent(eventName, detail) {
24		var event = new CustomEvent(eventName, {
25			detail: detail,
26			bubbles: true,
27			cancelable: true
28		});
29
30		document.dispatchEvent(event);
31	}
32
33	/**
34	 * Get post ID from body classes
35	 *
36	 * @returns {number|null}
37	 */
38	function getPostIdFromBody() {
39		var body = document.body;
40		if (!body) {
41			return null;
42		}
43
44		var classes = body.className.split(/\s+/);
45		var postId = null;
46
47		for (var i = 0; i < classes.length; i++) {
48			var cls = classes[i];
49
50			// Match postid-123 pattern
51			if (cls.indexOf('postid-') === 0) {
52				postId = parseInt(cls.substring(7), 10);
53				if (postId > 0) {
54					return postId;
55				}
56			}
57
58			// Match page-id-123 pattern
59			if (cls.indexOf('page-id-') === 0) {
60				postId = parseInt(cls.substring(8), 10);
61				if (postId > 0) {
62					return postId;
63				}
64			}
65		}
66
67		return null;
68	}
69
70	/**
71	 * Get viewed content from localStorage
72	 *
73	 * @returns {Object}
74	 */
75	function getViewedContent() {
76		try {
77			var stored = localStorage.getItem(STORAGE_KEY);
78			if (stored) {
79				return JSON.parse(stored);
80			}
81		} catch (e) {
82			// localStorage not available or invalid JSON
83		}
84		return {};
85	}
86
87	/**
88	 * Save viewed content to localStorage
89	 *
90	 * @param {Object} content
91	 */
92	function saveViewedContent(content) {
93		try {
94			localStorage.setItem(STORAGE_KEY, JSON.stringify(content));
95		} catch (e) {
96			// localStorage not available
97		}
98	}
99
100	/**
101	 * Update viewed content with new post
102	 *
103	 * @param {number} postId
104	 * @param {string} postType
105	 * @param {number} expiration
106	 */
107	function updateViewedContent(postId, postType, expiration) {
108		var content = getViewedContent();
109
110		if (!content[postType]) {
111			content[postType] = {};
112		}
113
114		content[postType][postId] = expiration;
115		saveViewedContent(content);
116	}
117
118	/**
119	 * Clean expired entries from viewed content
120	 */
121	function cleanExpiredContent() {
122		var content = getViewedContent();
123		var now = Math.floor(Date.now() / 1000);
124		var hasChanges = false;
125
126		for (var postType in content) {
127			if (content.hasOwnProperty(postType)) {
128				for (var postId in content[postType]) {
129					if (content[postType].hasOwnProperty(postId)) {
130						if (content[postType][postId] < now) {
131							delete content[postType][postId];
132							hasChanges = true;
133						}
134					}
135				}
136
137				// Remove empty post type objects
138				if (Object.keys(content[postType]).length === 0) {
139					delete content[postType];
140					hasChanges = true;
141				}
142			}
143		}
144
145		if (hasChanges) {
146			saveViewedContent(content);
147		}
148
149		return content;
150	}
151
152	/**
153	 * Get content containers based on post type
154	 *
155	 * @param {number} postId
156	 * @returns {NodeList}
157	 */
158	function getContentContainers(postId) {
159		var body = document.body;
160		var containerSetting;
161
162		// Check if this is a page or post
163		if (body.classList.contains('page-id-' + postId)) {
164			containerSetting = config.page_container || '';
165		} else {
166			containerSetting = config.post_container || '';
167		}
168
169		if (!containerSetting) {
170			return [];
171		}
172
173		// Handle comma-separated selectors
174		var selectors = containerSetting.split(',').map(function (s) {
175			return s.trim();
176		}).filter(function (s) {
177			return s.length > 0;
178		});
179
180		var containers = [];
181		selectors.forEach(function (selector) {
182			var elements = document.querySelectorAll(selector);
183			for (var i = 0; i < elements.length; i++) {
184				containers.push(elements[i]);
185			}
186		});
187
188		return containers;
189	}
190
191	/**
192	 * Extract lead-in content from container
193	 *
194	 * @param {Element} container
195	 * @param {number} numElements
196	 * @returns {string}
197	 */
198	function getLeadInContent(container, numElements) {
199		if (!numElements || numElements <= 0) {
200			return '';
201		}
202
203		var children = container.children;
204		var leadIn = '';
205
206		for (var i = 0; i < Math.min(numElements, children.length); i++) {
207			leadIn += children[i].outerHTML;
208		}
209
210		return leadIn;
211	}
212
213	/**
214	 * Display the paywall content
215	 *
216	 * @param {string} nagContent
217	 * @param {number} postId
218	 */
219	function displayPaywall(nagContent, postId) {
220		var containers = getContentContainers(postId);
221		var leadInElements = parseInt(config.lead_in_elements, 10) || 0;
222
223		containers.forEach(function (container, index) {
224			var leadIn = getLeadInContent(container, leadInElements);
225
226			if (containers.length > 1 && index > 0) {
227				// For multiple containers, clear all but the first
228				container.innerHTML = '';
229			} else {
230				container.innerHTML = leadIn + nagContent;
231			}
232			container.style.visibility = 'visible';
233		});
234	}
235
236	/**
237	 * Show the content (no paywall needed)
238	 *
239	 * @param {number} postId
240	 */
241	function showContent(postId) {
242		var containers = getContentContainers(postId);
243
244		containers.forEach(function (container) {
245			container.style.visibility = 'visible';
246		});
247	}
248
249	/**
250	 * Check restrictions via REST API
251	 *
252	 * @param {number} postId
253	 */
254	function checkRestrictions(postId) {
255		// Clean expired content first
256		var viewedContent = cleanExpiredContent();
257
258		var restUrl = config.rest_url || '/wp-json/leaky-paywall/v1/check-restrictions';
259
260		// Build request
261		var requestBody = {
262			post_id: postId,
263			viewed_content: viewedContent
264		};
265
266		var headers = {
267			'Content-Type': 'application/json'
268		};
269
270		// Only send the nonce for logged-in users. For anonymous visitors,
271		// omitting the header avoids 403 errors when a page cache serves a stale nonce.
272		if ( config.nonce && document.body.classList.contains('logged-in') ) {
273			headers['X-WP-Nonce'] = config.nonce;
274		}
275
276		fetch(restUrl, {
277			method: 'POST',
278			headers: headers,
279			body: JSON.stringify(requestBody),
280			credentials: 'same-origin'
281		})
282			.then(function (response) {
283				if (!response.ok) {
284					throw new Error('Network response was not ok');
285				}
286				return response.json();
287			})
288			.then(function (data) {
289				if (data.show_paywall && data.nag_content) {
290					// Record the post where the paywall was shown so a later signup is
291					// attributed to it even if the visitor didn't click a link in the nag.
292					if (postId > 0) {
293						document.cookie = 'lp_nag_loc=' + postId + ';path=/';
294					}
295
296					// If List Builder is active and handles this nag type,
297					// skip the in-content nag — the slider will handle display via the event.
298					var lb = window.LP_LIST_BUILDER;
299					var lbHandlesNag = lb && (
300						data.nag_type === 'subscribe' ||
301						(data.nag_type === 'upgrade' && lb.upgradeEnabled)
302					);
303
304					if (lbHandlesNag) {
305						showContent(postId);
306					} else {
307						displayPaywall(data.nag_content, postId);
308					}
309
310					// Dispatch event for other plugins to hook into
311					dispatchPaywallEvent('leaky_paywall_shown', {
312						postId: postId,
313						response: data
314					});
315				} else {
316					showContent(postId);
317
318					// Only update viewed content when access is granted.
319					if (data.viewed_content) {
320						saveViewedContent(data.viewed_content);
321					}
322
323					// Dispatch event for content access
324					dispatchPaywallEvent('leaky_paywall_access_granted', {
325						postId: postId,
326						response: data
327					});
328				}
329			})
330			.catch(function (error) {
331				// On error, show content to avoid blocking users
332				console.error('Leaky Paywall check failed:', error);
333				showContent(postId);
334			});
335	}
336
337	/**
338	 * Initialize
339	 */
340	function init() {
341		var postId = getPostIdFromBody();
342
343		if (!postId) {
344			return;
345		}
346
347		checkRestrictions(postId);
348	}
349
350	// Run on DOM ready
351	if (document.readyState === 'loading') {
352		document.addEventListener('DOMContentLoaded', init);
353	} else {
354		init();
355	}
356
357	// Expose for external use if needed
358	window.LeakyPaywallCheck = {
359		checkRestrictions: checkRestrictions,
360		getViewedContent: getViewedContent,
361		updateViewedContent: updateViewedContent,
362		cleanExpiredContent: cleanExpiredContent
363	};
364
365})();

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.